diff --git a/src/java/com/threerings/presents/server/AbstractSignalHandler.java b/src/java/com/threerings/presents/server/AbstractSignalHandler.java new file mode 100644 index 000000000..90b1f0b48 --- /dev/null +++ b/src/java/com/threerings/presents/server/AbstractSignalHandler.java @@ -0,0 +1,63 @@ +// +// $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 static com.threerings.presents.Log.log; + +/** + * A base class that is used to wire up signal handling in one of a couple of possible ways. + */ +public abstract class AbstractSignalHandler +{ + public boolean init (PresentsServer server) + { + _server = server; + return registerHandlers(); + } + + /** + * Signal handler implementations should wire themselves up in the call to this method. + * + * @return true if the handlers were successfully wired up, false if they were not able to be + * wired up. + */ + protected abstract boolean registerHandlers (); + + /** + * Implementations should call this method when a SIGINT is received. + */ + protected void intReceived () + { + log.info("Shutdown initiated by INT signal."); + _server.queueShutdown(); + } + + /** + * Implementations should call this method when a SIGHUP is received. + */ + protected void hupReceived () + { + log.info(_server.generateReport()); + } + + protected PresentsServer _server; +} diff --git a/src/java/com/threerings/presents/server/NativeSignalHandler.java b/src/java/com/threerings/presents/server/NativeSignalHandler.java new file mode 100644 index 000000000..173d84181 --- /dev/null +++ b/src/java/com/threerings/presents/server/NativeSignalHandler.java @@ -0,0 +1,60 @@ +// +// $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 com.threerings.util.signal.SignalManager; + +import static com.threerings.presents.Log.log; + +/** + * Handles signals using Narya's native libsignal. + */ +public class NativeSignalHandler extends AbstractSignalHandler + implements SignalManager.SignalHandler +{ + // from interface SignalManager.SignalHandler + public boolean signalReceived (int signo) + { + switch (signo) { + case SignalManager.SIGINT: + intReceived(); + break; + case SignalManager.SIGHUP: + hupReceived(); + break; + default: + log.warning("Received unknown signal [signo=" + signo + "]."); + break; + } + return true; + } + + protected boolean registerHandlers () + { + if (!SignalManager.servicesAvailable()) { + return false; + } + SignalManager.registerSignalHandler(SignalManager.SIGINT, this); + SignalManager.registerSignalHandler(SignalManager.SIGHUP, this); + return true; + } +} diff --git a/src/java/com/threerings/presents/server/PresentsServer.java b/src/java/com/threerings/presents/server/PresentsServer.java index 072f7f4ff..ec241c271 100644 --- a/src/java/com/threerings/presents/server/PresentsServer.java +++ b/src/java/com/threerings/presents/server/PresentsServer.java @@ -28,49 +28,37 @@ import com.samskivert.util.ObserverList; import com.samskivert.util.StringUtil; import com.samskivert.util.SystemInfo; -import com.threerings.util.signal.SignalManager; - import com.threerings.presents.Log; import com.threerings.presents.client.Client; import com.threerings.presents.dobj.AccessController; import com.threerings.presents.server.net.ConnectionManager; /** - * 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 PresentsServer - * class. These will be configured when the singleton instance is - * initialized. + * 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 PresentsServer class. These will be configured when the singleton + * instance is initialized. */ public class PresentsServer - implements SignalManager.SignalHandler { - /** Used to generate "state of the server" reports. See {@link - * #registerReporter}. */ + /** 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. + * 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. + * @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); + public void appendReport (StringBuilder buffer, long now, long sinceLast, boolean reset); } - /** Implementers of this interface will be notified when the server is - * shutting down. */ + /** Implementers of this interface will be notified when the server is shutting down. */ public static interface Shutdowner { /** @@ -91,9 +79,9 @@ public class PresentsServer /** 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. */ + /** 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; /** @@ -104,17 +92,22 @@ public class PresentsServer { // output general system information SystemInfo si = new SystemInfo(); - Log.info("Starting up server [os=" + si.osToString() + - ", jvm=" + si.jvmToString() + + Log.info("Starting up server [os=" + si.osToString() + ", jvm=" + si.jvmToString() + ", mem=" + si.memoryToString() + "]."); // register SIGINT (ctrl-c) and a SIGHUP handlers - SignalManager.registerSignalHandler(SignalManager.SIGINT, this); - SignalManager.registerSignalHandler(SignalManager.SIGHUP, this); + boolean registered = false; + try { + registered = new SunSignalHandler().init(this); + } catch (Throwable t) { + t.printStackTrace(System.err); + } + if (!registered) { + new NativeSignalHandler().init(this); + } // create our list of shutdowners - _downers = new ObserverList( - ObserverList.SAFE_IN_ORDER_NOTIFY); + _downers = new ObserverList(ObserverList.SAFE_IN_ORDER_NOTIFY); // create our distributed object manager omgr = createDObjectManager(); @@ -181,8 +174,7 @@ public class PresentsServer } /** - * Returns the port on which the connection manager will listen for - * client connections. + * Returns the port on which the connection manager will listen for client connections. */ protected int[] getListenPorts () { @@ -190,13 +182,12 @@ public class PresentsServer } /** - * Starts up all of the server services and enters the main server - * event loop. + * 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 + // 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 @@ -207,33 +198,9 @@ public class PresentsServer omgr.run(); } - // documentation inherited from interface - public boolean signalReceived (int signo) - { - switch (signo) { - case SignalManager.SIGINT: - // this is called when we receive a ctrl-c - Log.info("Shutdown initiated by received signal (" + signo + ")"); - queueShutdown(); - break; - - case SignalManager.SIGHUP: - // generate a system status report - Log.info(generateReport()); - break; - - default: - Log.warning("Received unknown signal [signo=" + signo + "]."); - break; - } - - return true; - } - /** - * A report is generated by the presents server periodically in which - * server entities can participate by registering a {@link Reporter} - * with this method. + * 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) { @@ -241,8 +208,7 @@ public class PresentsServer } /** - * Generates a report for all system services registered as a {@link - * Reporter}. + * Generates a report for all system services registered as a {@link Reporter}. */ public static String generateReport () { @@ -306,9 +272,8 @@ public class PresentsServer } /** - * 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. + * 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) { @@ -316,9 +281,8 @@ public class PresentsServer } /** - * Requests that the server shut down. All registered shutdown - * participants will be shut down, following which the server process - * will be terminated. + * 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 () { @@ -329,8 +293,8 @@ public class PresentsServer } _downers = null; - // shut down the connection manager (this will cease all network - // activity but not actually close the connections) + // shut down the connection manager (this will cease all network activity but not actually + // close the connections) if (conmgr.isRunning()) { conmgr.shutdown(); } @@ -343,14 +307,14 @@ public class PresentsServer } }); - // finally shut down the invoker and distributed object manager - // (The invoker does both for us.) + // finally shut down the invoker and distributed object 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. + * Queues up a request to shutdown on the dobjmgr thread. This method may be safely called from + * any thread. */ public void queueShutdown () { @@ -362,8 +326,7 @@ public class PresentsServer } /** - * Registers an entity that will be notified when the server is - * shutting down. + * Registers an entity that will be notified when the server is shutting down. */ public static void registerShutdowner (Shutdowner downer) { @@ -387,8 +350,7 @@ public class PresentsServer // initialize the server server.init(); - // check to see if we should load and invoke a test module - // before running the server + // 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 { @@ -403,8 +365,8 @@ public class PresentsServer } } - // start the server to running (this method call won't return - // until the server is shut down) + // start the server to running (this method call won't return until the server is shut + // down) server.run(); } catch (Exception e) { diff --git a/src/java/com/threerings/presents/server/SunSignalHandler.java b/src/java/com/threerings/presents/server/SunSignalHandler.java new file mode 100644 index 000000000..d99e17941 --- /dev/null +++ b/src/java/com/threerings/presents/server/SunSignalHandler.java @@ -0,0 +1,56 @@ +// +// $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 sun.misc.Signal; +import sun.misc.SignalHandler; + +import static com.threerings.presents.Log.log; + +/** + * Handles signals using Sun's undocumented Signal class. + */ +public class SunSignalHandler extends AbstractSignalHandler + implements SignalHandler +{ + // from interface SignalHandler + public void handle (Signal sig) + { + SignalHandler chain = null; + if (sig.getName().equals("INT")) { + intReceived(); + } else if (sig.getName().equals("HUP")) { + hupReceived(); + } else { + log.warning("Received unknown signal '" + sig.getName() + "'."); + } + } + + protected boolean registerHandlers () + { + // we don't track and call the chained handlers for INT and HUP because those exit the JVM + // which we do not want to do + Signal.handle(new Signal("INT"), this); + Signal.handle(new Signal("HUP"), this); + return true; + } +} diff --git a/src/java/com/threerings/util/signal/SignalManager.java b/src/java/com/threerings/util/signal/SignalManager.java index e78532cb8..be54a2d0c 100644 --- a/src/java/com/threerings/util/signal/SignalManager.java +++ b/src/java/com/threerings/util/signal/SignalManager.java @@ -27,10 +27,9 @@ import com.samskivert.util.ObserverList; import static com.threerings.NaryaLog.log; /** - * Uses native code to catch Unix signals and invoke callbacks on a - * separate signal handler thread. If the native library cannot be loaded, - * signal handlers will be allowed to be registered but will never be - * called. + * Uses native code to catch Unix signals and invoke callbacks on a separate signal handler thread. + * If the native library cannot be loaded, signal handlers will be allowed to be registered but + * will never be called. */ public class SignalManager { @@ -139,17 +138,17 @@ public class SignalManager /** * Called when the specified signal is received. * - * @return true if the signal handler should remain registered, - * false if it should be removed. + * @return true if the signal handler should remain registered, false if it should be + * removed. */ public boolean signalReceived (int signal); } /** - * Returns true if signal dispatching services are available, false if - * we could not load our native library. + * Returns true if signal dispatching services are available, false if we could not load our + * native library. */ - public boolean servicesAvailable () + public static boolean servicesAvailable () { return _haveLibrary; } @@ -157,8 +156,7 @@ public class SignalManager /** * Registers a signal handler for the specified signal. */ - public synchronized static void registerSignalHandler ( - int signal, SignalHandler handler) + public synchronized static void registerSignalHandler (int signal, SignalHandler handler) { ObserverList list = _handlers.get(signal); if (list == null) { @@ -186,13 +184,12 @@ public class SignalManager /** * Removes the registration for the specified signal handler. */ - public synchronized static void removeSignalHandler ( - int signal, SignalHandler handler) + public synchronized static void removeSignalHandler (int signal, SignalHandler handler) { ObserverList list = _handlers.get(signal); if (list == null || !list.contains(handler)) { - log.warning("Requested to remove non-registered handler " + - "[signal=" + signal + ", handler=" + handler + "]."); + log.warning("Requested to remove non-registered handler [signal=" + signal + + ", handler=" + handler + "]."); return; } list.remove(handler); @@ -204,9 +201,8 @@ public class SignalManager */ protected synchronized static void signalReceived (final int signal) { - // this is hack, but we seem to get a call to our signal handler - // for each thread if the user presses ctrl-c in the terminal, so - // we "collapse" those into one call back + // this is hack, but we seem to get a call to our signal handler for each thread if the + // user presses ctrl-c in the terminal, so we "collapse" those into one call back long now = System.currentTimeMillis(); if (signal == SIGINT) { if (now - _lastINTed < 10) { @@ -251,8 +247,7 @@ public class SignalManager protected static native void deactivateHandler (int signal); /** - * Consigns a Java thread to the task of dispatching signal handler - * callbacks. + * Consigns a Java thread to the task of dispatching signal handler callbacks. */ protected static native void dispatchSignals (); @@ -266,8 +261,8 @@ public class SignalManager /** Set to true if we successfully load our native library. */ protected static boolean _haveLibrary; - /** Used to collapse bogus multiple delivery of SIGINT when the user - * pressed ctrl-c in the console. */ + /** Used to collapse bogus multiple delivery of SIGINT when the user pressed ctrl-c in the + * console. */ protected static long _lastINTed = 0L; static {