Turns out Sun has secret support for handling signals, so we'll use that

instead of our native library. I've left the native library support in in case
the Sun stuff is not available, but that's so extremely unlikely in a server
environment (IBM recreates Sun's signal handling in their VM because it's so
dang useful) that I should probably just nix it altogether and simplify things.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@4710 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2007-05-14 21:36:58 +00:00
parent 9ea934cd82
commit 91f8ac6fa6
5 changed files with 246 additions and 110 deletions
@@ -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;
}
@@ -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;
}
}
@@ -28,49 +28,37 @@ import com.samskivert.util.ObserverList;
import com.samskivert.util.StringUtil; import com.samskivert.util.StringUtil;
import com.samskivert.util.SystemInfo; import com.samskivert.util.SystemInfo;
import com.threerings.util.signal.SignalManager;
import com.threerings.presents.Log; import com.threerings.presents.Log;
import com.threerings.presents.client.Client; import com.threerings.presents.client.Client;
import com.threerings.presents.dobj.AccessController; import com.threerings.presents.dobj.AccessController;
import com.threerings.presents.server.net.ConnectionManager; import com.threerings.presents.server.net.ConnectionManager;
/** /**
* The presents server provides a central point of access to the various * The presents server provides a central point of access to the various facilities that make up
* facilities that make up the presents framework. To facilitate extension * the presents framework. To facilitate extension and customization, a single instance of the
* and customization, a single instance of the presents server should be * presents server should be created and initialized in a process. To facilitate easy access to the
* 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
* services provided by the presents server, static references to the * available in the <code>PresentsServer</code> class. These will be configured when the singleton
* various managers are made available in the <code>PresentsServer</code> * instance is initialized.
* class. These will be configured when the singleton instance is
* initialized.
*/ */
public class PresentsServer public class PresentsServer
implements SignalManager.SignalHandler
{ {
/** Used to generate "state of the server" reports. See {@link /** Used to generate "state of the server" reports. See {@link #registerReporter}. */
* #registerReporter}. */
public static interface Reporter public static interface Reporter
{ {
/** /**
* Requests that this reporter append its report to the supplied * Requests that this reporter append its report to the supplied string buffer.
* string buffer.
* *
* @param buffer the string buffer to which the report text should * @param buffer the string buffer to which the report text should be appended.
* be appended. * @param now the time at which the report generation began, in epoch millis.
* @param now the time at which the report generation began, in * @param sinceLast number of milliseconds since the last time we generated a report.
* epoch millis. * @param reset if true, all accumulating stats should be reset, if false they should be
* @param sinceLast number of milliseconds since the last time we * allowed to continue to accumulate.
* 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 ( public void appendReport (StringBuilder buffer, long now, long sinceLast, boolean reset);
StringBuilder buffer, long now, long sinceLast, boolean reset);
} }
/** Implementers of this interface will be notified when the server is /** Implementers of this interface will be notified when the server is shutting down. */
* shutting down. */
public static interface Shutdowner public static interface Shutdowner
{ {
/** /**
@@ -91,9 +79,9 @@ public class PresentsServer
/** The invocation manager. */ /** The invocation manager. */
public static InvocationManager invmgr; public static InvocationManager invmgr;
/** This is used to invoke background tasks that should not be allowed /** This is used to invoke background tasks that should not be allowed to tie up the
* to tie up the distributed object manager thread. This is generally * distributed object manager thread. This is generally used to talk to databases and other
* used to talk to databases and other (relatively) slow entities. */ * (relatively) slow entities. */
public static PresentsInvoker invoker; public static PresentsInvoker invoker;
/** /**
@@ -104,17 +92,22 @@ public class PresentsServer
{ {
// output general system information // output general system information
SystemInfo si = new SystemInfo(); SystemInfo si = new SystemInfo();
Log.info("Starting up server [os=" + si.osToString() + Log.info("Starting up server [os=" + si.osToString() + ", jvm=" + si.jvmToString() +
", jvm=" + si.jvmToString() +
", mem=" + si.memoryToString() + "]."); ", mem=" + si.memoryToString() + "].");
// register SIGINT (ctrl-c) and a SIGHUP handlers // register SIGINT (ctrl-c) and a SIGHUP handlers
SignalManager.registerSignalHandler(SignalManager.SIGINT, this); boolean registered = false;
SignalManager.registerSignalHandler(SignalManager.SIGHUP, this); try {
registered = new SunSignalHandler().init(this);
} catch (Throwable t) {
t.printStackTrace(System.err);
}
if (!registered) {
new NativeSignalHandler().init(this);
}
// create our list of shutdowners // create our list of shutdowners
_downers = new ObserverList<Shutdowner>( _downers = new ObserverList<Shutdowner>(ObserverList.SAFE_IN_ORDER_NOTIFY);
ObserverList.SAFE_IN_ORDER_NOTIFY);
// create our distributed object manager // create our distributed object manager
omgr = createDObjectManager(); omgr = createDObjectManager();
@@ -181,8 +174,7 @@ public class PresentsServer
} }
/** /**
* Returns the port on which the connection manager will listen for * Returns the port on which the connection manager will listen for client connections.
* client connections.
*/ */
protected int[] getListenPorts () protected int[] getListenPorts ()
{ {
@@ -190,13 +182,12 @@ public class PresentsServer
} }
/** /**
* Starts up all of the server services and enters the main server * Starts up all of the server services and enters the main server event loop.
* event loop.
*/ */
public void run () public void run ()
{ {
// post a unit that will start up the connection manager when // post a unit that will start up the connection manager when everything else in the
// everything else in the dobjmgr queue is processed // dobjmgr queue is processed
omgr.postRunnable(new Runnable() { omgr.postRunnable(new Runnable() {
public void run () { public void run () {
// start up the connection manager // start up the connection manager
@@ -207,33 +198,9 @@ public class PresentsServer
omgr.run(); 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 * A report is generated by the presents server periodically in which server entities can
* server entities can participate by registering a {@link Reporter} * participate by registering a {@link Reporter} with this method.
* with this method.
*/ */
public static void registerReporter (Reporter reporter) public static void registerReporter (Reporter reporter)
{ {
@@ -241,8 +208,7 @@ public class PresentsServer
} }
/** /**
* Generates a report for all system services registered as a {@link * Generates a report for all system services registered as a {@link Reporter}.
* Reporter}.
*/ */
public static String generateReport () public static String generateReport ()
{ {
@@ -306,9 +272,8 @@ public class PresentsServer
} }
/** /**
* Logs the state of the server report via the default logging mechanism. * Logs the state of the server report via the default logging mechanism. Derived classes may
* Derived classes may wish to log the state of the server report via a * wish to log the state of the server report via a different means.
* different means.
*/ */
protected void logReport (String report) protected void logReport (String report)
{ {
@@ -316,9 +281,8 @@ public class PresentsServer
} }
/** /**
* Requests that the server shut down. All registered shutdown * Requests that the server shut down. All registered shutdown participants will be shut down,
* participants will be shut down, following which the server process * following which the server process will be terminated.
* will be terminated.
*/ */
public void shutdown () public void shutdown ()
{ {
@@ -329,8 +293,8 @@ public class PresentsServer
} }
_downers = null; _downers = null;
// shut down the connection manager (this will cease all network // shut down the connection manager (this will cease all network activity but not actually
// activity but not actually close the connections) // close the connections)
if (conmgr.isRunning()) { if (conmgr.isRunning()) {
conmgr.shutdown(); conmgr.shutdown();
} }
@@ -343,14 +307,14 @@ public class PresentsServer
} }
}); });
// finally shut down the invoker and distributed object manager // finally shut down the invoker and distributed object manager (The invoker does both for
// (The invoker does both for us.) // us.)
invoker.shutdown(); invoker.shutdown();
} }
/** /**
* Queues up a request to shutdown on the dobjmgr thread. This method * Queues up a request to shutdown on the dobjmgr thread. This method may be safely called from
* may be safely called from any thread. * any thread.
*/ */
public void queueShutdown () public void queueShutdown ()
{ {
@@ -362,8 +326,7 @@ public class PresentsServer
} }
/** /**
* Registers an entity that will be notified when the server is * Registers an entity that will be notified when the server is shutting down.
* shutting down.
*/ */
public static void registerShutdowner (Shutdowner downer) public static void registerShutdowner (Shutdowner downer)
{ {
@@ -387,8 +350,7 @@ public class PresentsServer
// initialize the server // initialize the server
server.init(); server.init();
// check to see if we should load and invoke a test module // check to see if we should load and invoke a test module before running the server
// before running the server
String testmod = System.getProperty("test_module"); String testmod = System.getProperty("test_module");
if (testmod != null) { if (testmod != null) {
try { try {
@@ -403,8 +365,8 @@ public class PresentsServer
} }
} }
// start the server to running (this method call won't return // start the server to running (this method call won't return until the server is shut
// until the server is shut down) // down)
server.run(); server.run();
} catch (Exception e) { } catch (Exception e) {
@@ -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;
}
}
@@ -27,10 +27,9 @@ import com.samskivert.util.ObserverList;
import static com.threerings.NaryaLog.log; import static com.threerings.NaryaLog.log;
/** /**
* Uses native code to catch Unix signals and invoke callbacks on a * Uses native code to catch Unix signals and invoke callbacks on a separate signal handler thread.
* separate signal handler thread. If the native library cannot be loaded, * If the native library cannot be loaded, signal handlers will be allowed to be registered but
* signal handlers will be allowed to be registered but will never be * will never be called.
* called.
*/ */
public class SignalManager public class SignalManager
{ {
@@ -139,17 +138,17 @@ public class SignalManager
/** /**
* Called when the specified signal is received. * Called when the specified signal is received.
* *
* @return true if the signal handler should remain registered, * @return true if the signal handler should remain registered, false if it should be
* false if it should be removed. * removed.
*/ */
public boolean signalReceived (int signal); public boolean signalReceived (int signal);
} }
/** /**
* Returns true if signal dispatching services are available, false if * Returns true if signal dispatching services are available, false if we could not load our
* we could not load our native library. * native library.
*/ */
public boolean servicesAvailable () public static boolean servicesAvailable ()
{ {
return _haveLibrary; return _haveLibrary;
} }
@@ -157,8 +156,7 @@ public class SignalManager
/** /**
* Registers a signal handler for the specified signal. * Registers a signal handler for the specified signal.
*/ */
public synchronized static void registerSignalHandler ( public synchronized static void registerSignalHandler (int signal, SignalHandler handler)
int signal, SignalHandler handler)
{ {
ObserverList<SignalHandler> list = _handlers.get(signal); ObserverList<SignalHandler> list = _handlers.get(signal);
if (list == null) { if (list == null) {
@@ -186,13 +184,12 @@ public class SignalManager
/** /**
* Removes the registration for the specified signal handler. * Removes the registration for the specified signal handler.
*/ */
public synchronized static void removeSignalHandler ( public synchronized static void removeSignalHandler (int signal, SignalHandler handler)
int signal, SignalHandler handler)
{ {
ObserverList<SignalHandler> list = _handlers.get(signal); ObserverList<SignalHandler> list = _handlers.get(signal);
if (list == null || !list.contains(handler)) { if (list == null || !list.contains(handler)) {
log.warning("Requested to remove non-registered handler " + log.warning("Requested to remove non-registered handler [signal=" + signal +
"[signal=" + signal + ", handler=" + handler + "]."); ", handler=" + handler + "].");
return; return;
} }
list.remove(handler); list.remove(handler);
@@ -204,9 +201,8 @@ public class SignalManager
*/ */
protected synchronized static void signalReceived (final int signal) protected synchronized static void signalReceived (final int signal)
{ {
// this is hack, but we seem to get a call to our signal handler // this is hack, but we seem to get a call to our signal handler for each thread if the
// for each thread if the user presses ctrl-c in the terminal, so // user presses ctrl-c in the terminal, so we "collapse" those into one call back
// we "collapse" those into one call back
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
if (signal == SIGINT) { if (signal == SIGINT) {
if (now - _lastINTed < 10) { if (now - _lastINTed < 10) {
@@ -251,8 +247,7 @@ public class SignalManager
protected static native void deactivateHandler (int signal); protected static native void deactivateHandler (int signal);
/** /**
* Consigns a Java thread to the task of dispatching signal handler * Consigns a Java thread to the task of dispatching signal handler callbacks.
* callbacks.
*/ */
protected static native void dispatchSignals (); protected static native void dispatchSignals ();
@@ -266,8 +261,8 @@ public class SignalManager
/** Set to true if we successfully load our native library. */ /** Set to true if we successfully load our native library. */
protected static boolean _haveLibrary; protected static boolean _haveLibrary;
/** Used to collapse bogus multiple delivery of SIGINT when the user /** Used to collapse bogus multiple delivery of SIGINT when the user pressed ctrl-c in the
* pressed ctrl-c in the console. */ * console. */
protected static long _lastINTed = 0L; protected static long _lastINTed = 0L;
static { static {