768e6a76be
shutdowner at any time. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@4915 542714f4-19e9-0310-aa3c-eee0fc999fb1
406 lines
14 KiB
Java
406 lines
14 KiB
Java
//
|
|
// $Id$
|
|
//
|
|
// Narya library - tools for developing networked games
|
|
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
|
|
// http://www.threerings.net/code/narya/
|
|
//
|
|
// This library is free software; you can redistribute it and/or modify it
|
|
// under the terms of the GNU Lesser General Public License as published
|
|
// by the Free Software Foundation; either version 2.1 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public
|
|
// License along with this library; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
package com.threerings.presents.server;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.logging.Level;
|
|
|
|
import com.samskivert.util.Interval;
|
|
import com.samskivert.util.ObserverList;
|
|
import com.samskivert.util.StringUtil;
|
|
import com.samskivert.util.SystemInfo;
|
|
|
|
import com.threerings.presents.client.Client;
|
|
import com.threerings.presents.dobj.AccessController;
|
|
import com.threerings.presents.server.net.ConnectionManager;
|
|
|
|
import static com.threerings.presents.Log.log;
|
|
|
|
/**
|
|
* The presents server provides a central point of access to the various facilities that make up
|
|
* the presents framework. To facilitate extension and customization, a single instance of the
|
|
* presents server should be created and initialized in a process. To facilitate easy access to the
|
|
* services provided by the presents server, static references to the various managers are made
|
|
* available in the <code>PresentsServer</code> class. These will be configured when the singleton
|
|
* instance is initialized.
|
|
*/
|
|
public class PresentsServer
|
|
{
|
|
/** Used to generate "state of the server" reports. See {@link #registerReporter}. */
|
|
public static interface Reporter
|
|
{
|
|
/**
|
|
* Requests that this reporter append its report to the supplied string buffer.
|
|
*
|
|
* @param buffer the string buffer to which the report text should be appended.
|
|
* @param now the time at which the report generation began, in epoch millis.
|
|
* @param sinceLast number of milliseconds since the last time we generated a report.
|
|
* @param reset if true, all accumulating stats should be reset, if false they should be
|
|
* allowed to continue to accumulate.
|
|
*/
|
|
public void appendReport (StringBuilder buffer, long now, long sinceLast, boolean reset);
|
|
}
|
|
|
|
/** Implementers of this interface will be notified when the server is shutting down. */
|
|
public static interface Shutdowner
|
|
{
|
|
/**
|
|
* Called when the server is shutting down.
|
|
*/
|
|
public void shutdown ();
|
|
}
|
|
|
|
/** The manager of network connections. */
|
|
public static ConnectionManager conmgr;
|
|
|
|
/** The manager of clients. */
|
|
public static ClientManager clmgr;
|
|
|
|
/** The distributed object manager. */
|
|
public static PresentsDObjectMgr omgr;
|
|
|
|
/** The invocation manager. */
|
|
public static InvocationManager invmgr;
|
|
|
|
/** This is used to invoke background tasks that should not be allowed to tie up the
|
|
* distributed object manager thread. This is generally used to talk to databases and other
|
|
* (relatively) slow entities. */
|
|
public static PresentsInvoker invoker;
|
|
|
|
/**
|
|
* Registers an entity that will be notified when the server is shutting down.
|
|
*/
|
|
public static void registerShutdowner (Shutdowner downer)
|
|
{
|
|
_downers.add(downer);
|
|
}
|
|
|
|
/**
|
|
* Unregisters the shutdowner from hearing when the server is shutdown.
|
|
*/
|
|
public static void unregisterShutdowner (Shutdowner downer)
|
|
{
|
|
_downers.remove(downer);
|
|
}
|
|
|
|
/**
|
|
* The default entry point for the server.
|
|
*/
|
|
public static void main (String[] args)
|
|
{
|
|
log.info("Presents server starting...");
|
|
|
|
PresentsServer server = new PresentsServer();
|
|
try {
|
|
// initialize the server
|
|
server.init();
|
|
|
|
// check to see if we should load and invoke a test module before running the server
|
|
String testmod = System.getProperty("test_module");
|
|
if (testmod != null) {
|
|
try {
|
|
log.info("Invoking test module [mod=" + testmod + "].");
|
|
Class tmclass = Class.forName(testmod);
|
|
Runnable trun = (Runnable)tmclass.newInstance();
|
|
trun.run();
|
|
} catch (Exception e) {
|
|
log.log(Level.WARNING, "Unable to invoke test module '" + testmod + "'.", e);
|
|
}
|
|
}
|
|
|
|
// start the server to running (this method call won't return until the server is shut
|
|
// down)
|
|
server.run();
|
|
|
|
} catch (Exception e) {
|
|
log.log(Level.WARNING, "Unable to initialize server.", e);
|
|
System.exit(-1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initializes all of the server services and prepares for operation.
|
|
*/
|
|
public void init ()
|
|
throws Exception
|
|
{
|
|
// output general system information
|
|
SystemInfo si = new SystemInfo();
|
|
log.info("Starting up server [os=" + si.osToString() + ", jvm=" + si.jvmToString() +
|
|
", mem=" + si.memoryToString() + "].");
|
|
|
|
// register SIGINT (ctrl-c) and a SIGHUP handlers
|
|
boolean registered = false;
|
|
try {
|
|
registered = new SunSignalHandler().init(this);
|
|
} catch (Throwable t) {
|
|
log.warning("Unable to register Sun signal handlers [error=" + t + "].");
|
|
}
|
|
if (!registered) {
|
|
new NativeSignalHandler().init(this);
|
|
}
|
|
|
|
// create our distributed object manager
|
|
omgr = createDObjectManager();
|
|
|
|
// configure the dobject manager with our access controller
|
|
omgr.setDefaultAccessController(createDefaultObjectAccessController());
|
|
|
|
// create and start up our invoker
|
|
invoker = new PresentsInvoker(omgr) {
|
|
protected void didShutdown () {
|
|
invokerDidShutdown();
|
|
}
|
|
};
|
|
invoker.start();
|
|
|
|
// create our connection manager
|
|
conmgr = new ConnectionManager(getListenPorts());
|
|
conmgr.setAuthenticator(createAuthenticator());
|
|
|
|
// create our client manager
|
|
clmgr = createClientManager(conmgr);
|
|
|
|
// create our invocation manager
|
|
invmgr = new InvocationManager(omgr);
|
|
|
|
// initialize the time base services
|
|
TimeBaseProvider.init(invmgr, omgr);
|
|
|
|
// queue up an interval which will generate reports
|
|
new Interval(omgr) {
|
|
public void expired () {
|
|
logReport(generateReport(System.currentTimeMillis(), true));
|
|
}
|
|
}.schedule(REPORT_INTERVAL, true);
|
|
}
|
|
|
|
/**
|
|
* Creates the client Authenticator to be used on this server.
|
|
*/
|
|
protected Authenticator createAuthenticator ()
|
|
{
|
|
return new DummyAuthenticator();
|
|
}
|
|
|
|
/**
|
|
* Creates the client manager to be used on this server.
|
|
*/
|
|
protected ClientManager createClientManager (ConnectionManager conmgr)
|
|
{
|
|
return new ClientManager(conmgr);
|
|
}
|
|
|
|
/**
|
|
* Creates the distributed object manager to be used on this server.
|
|
*/
|
|
protected PresentsDObjectMgr createDObjectManager ()
|
|
{
|
|
return new PresentsDObjectMgr();
|
|
}
|
|
|
|
/**
|
|
* Defines the default object access policy for all {@link DObject} instances. The default
|
|
* default policy is to allow all subscribers but reject all modifications by the client.
|
|
*/
|
|
protected AccessController createDefaultObjectAccessController ()
|
|
{
|
|
return PresentsObjectAccess.DEFAULT;
|
|
}
|
|
|
|
/**
|
|
* Returns the port on which the connection manager will listen for client connections.
|
|
*/
|
|
protected int[] getListenPorts ()
|
|
{
|
|
return Client.DEFAULT_SERVER_PORTS;
|
|
}
|
|
|
|
/**
|
|
* Starts up all of the server services and enters the main server event loop.
|
|
*/
|
|
public void run ()
|
|
{
|
|
// post a unit that will start up the connection manager when everything else in the
|
|
// dobjmgr queue is processed
|
|
omgr.postRunnable(new Runnable() {
|
|
public void run () {
|
|
// start up the connection manager
|
|
conmgr.start();
|
|
}
|
|
});
|
|
// invoke the dobjmgr event loop
|
|
omgr.run();
|
|
}
|
|
|
|
/**
|
|
* A report is generated by the presents server periodically in which server entities can
|
|
* participate by registering a {@link Reporter} with this method.
|
|
*/
|
|
public static void registerReporter (Reporter reporter)
|
|
{
|
|
_reporters.add(reporter);
|
|
}
|
|
|
|
/**
|
|
* Generates a report for all system services registered as a {@link Reporter}.
|
|
*/
|
|
public static String generateReport ()
|
|
{
|
|
return generateReport(System.currentTimeMillis(), false);
|
|
}
|
|
|
|
/**
|
|
* Generates and logs a "state of server" report.
|
|
*/
|
|
protected static String generateReport (long now, boolean reset)
|
|
{
|
|
long sinceLast = now - _lastReportStamp;
|
|
long uptime = now - _serverStartTime;
|
|
StringBuilder report = new StringBuilder("State of server report:\n");
|
|
|
|
report.append("- Uptime: ");
|
|
report.append(StringUtil.intervalToString(uptime)).append("\n");
|
|
report.append("- Report period: ");
|
|
report.append(StringUtil.intervalToString(sinceLast)).append("\n");
|
|
|
|
// report on the state of memory
|
|
Runtime rt = Runtime.getRuntime();
|
|
long total = rt.totalMemory(), max = rt.maxMemory();
|
|
long used = (total - rt.freeMemory());
|
|
report.append("- Memory: ").append(used/1024).append("k used, ");
|
|
report.append(total/1024).append("k total, ");
|
|
report.append(max/1024).append("k max\n");
|
|
|
|
for (int ii = 0; ii < _reporters.size(); ii++) {
|
|
Reporter rptr = _reporters.get(ii);
|
|
try {
|
|
rptr.appendReport(report, now, sinceLast, reset);
|
|
} catch (Throwable t) {
|
|
log.log(Level.WARNING, "Reporter choked [rptr=" + rptr + "].", t);
|
|
}
|
|
}
|
|
|
|
/* The following Interval debug methods are no longer supported,
|
|
* but they could be added back easily if needed.
|
|
report.append("* samskivert.Interval:\n");
|
|
report.append("- Registered intervals: ");
|
|
report.append(Interval.registeredIntervalCount());
|
|
report.append("\n- Fired since last report: ");
|
|
report.append(Interval.getAndClearFiredIntervals());
|
|
report.append("\n");
|
|
*/
|
|
|
|
// strip off the final newline
|
|
int blen = report.length();
|
|
if (report.charAt(blen-1) == '\n') {
|
|
report.delete(blen-1, blen);
|
|
}
|
|
|
|
// only reset the last report time if this is a periodic report
|
|
if (reset) {
|
|
_lastReportStamp = now;
|
|
}
|
|
|
|
return report.toString();
|
|
}
|
|
|
|
/**
|
|
* Logs the state of the server report via the default logging mechanism. Derived classes may
|
|
* wish to log the state of the server report via a different means.
|
|
*/
|
|
protected void logReport (String report)
|
|
{
|
|
log.info(report);
|
|
}
|
|
|
|
/**
|
|
* Requests that the server shut down. All registered shutdown participants will be shut down,
|
|
* following which the server process will be terminated.
|
|
*/
|
|
public void shutdown ()
|
|
{
|
|
ObserverList<Shutdowner> downers = _downers;
|
|
if (downers == null) {
|
|
log.warning("Refusing repeat shutdown request.");
|
|
return;
|
|
}
|
|
_downers = null;
|
|
|
|
// shut down all shutdown participants
|
|
downers.apply(new ObserverList.ObserverOp<Shutdowner>() {
|
|
public boolean apply (Shutdowner downer) {
|
|
downer.shutdown();
|
|
return true;
|
|
}
|
|
});
|
|
|
|
// shut down the connection manager (this will cease all network activity but not actually
|
|
// close the connections)
|
|
if (conmgr.isRunning()) {
|
|
conmgr.shutdown();
|
|
}
|
|
|
|
// finally shut down the invoker and dobj manager (The invoker does both for us.)
|
|
invoker.shutdown();
|
|
}
|
|
|
|
/**
|
|
* Queues up a request to shutdown on the dobjmgr thread. This method may be safely called from
|
|
* any thread.
|
|
*/
|
|
public void queueShutdown ()
|
|
{
|
|
omgr.postRunnable(new Runnable() {
|
|
public void run () {
|
|
shutdown();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Called once the invoker and distributed object manager have both completed processing all
|
|
* remaining events and are fully shutdown. <em>Note:</em> this is called as the last act of
|
|
* the invoker <em>on the invoker thread</em>. In theory no other (important) threads are
|
|
* running, so thread safety should not be an issue, but be careful!
|
|
*/
|
|
protected void invokerDidShutdown ()
|
|
{
|
|
}
|
|
|
|
/** The time at which the server was started. */
|
|
protected static long _serverStartTime = System.currentTimeMillis();
|
|
|
|
/** The last time at which {@link #generateReport} was run. */
|
|
protected static long _lastReportStamp = _serverStartTime;
|
|
|
|
/** Used to generate "state of server" reports. */
|
|
protected static ArrayList<Reporter> _reporters = new ArrayList<Reporter>();
|
|
|
|
/** A list of shutdown participants. */
|
|
protected static ObserverList<Shutdowner> _downers =
|
|
new ObserverList<Shutdowner>(ObserverList.SAFE_IN_ORDER_NOTIFY);
|
|
|
|
/** The frequency with which we generate "state of server" reports. */
|
|
protected static final long REPORT_INTERVAL = 15 * 60 * 1000L;
|
|
}
|