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.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 <code>PresentsServer</code>
* 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 <code>PresentsServer</code> 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<Shutdowner>(
ObserverList.SAFE_IN_ORDER_NOTIFY);
_downers = new ObserverList<Shutdowner>(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) {
@@ -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;
/**
* 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<SignalHandler> 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<SignalHandler> 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 {