// // $Id$ // // Narya library - tools for developing networked games // Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved // http://code.google.com/p/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.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.Inject; import com.google.inject.Injector; import com.google.inject.Module; import com.google.inject.Singleton; import com.samskivert.util.Invoker; import com.samskivert.util.Lifecycle; import com.samskivert.util.RunQueue; import com.samskivert.util.SystemInfo; import com.threerings.presents.annotation.AuthInvoker; import com.threerings.presents.annotation.EventQueue; import com.threerings.presents.annotation.MainInvoker; import com.threerings.presents.client.Client; import com.threerings.presents.dobj.AccessController; import com.threerings.presents.dobj.DObject; import com.threerings.presents.dobj.DObjectManager; import com.threerings.presents.dobj.RootDObjectManager; import com.threerings.presents.server.PresentsDObjectMgr.LongRunnable; import com.threerings.presents.server.net.DatagramChannelReader; import com.threerings.presents.server.net.PresentsConnectionManager; import com.threerings.crowd.server.PlaceManager; import com.threerings.nio.conman.ConnectionManager; import com.threerings.nio.conman.ServerSocketChannelAcceptor; 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 PresentsServer class. These will be configured when the singleton * instance is initialized. */ @Singleton public class PresentsServer { /** Configures dependencies needed by the Presents services. */ public static class PresentsModule extends AbstractModule { @Override protected void configure () { bindInvokers(); bind(ConnectionManager.class).to(PresentsConnectionManager.class); bind(RunQueue.class).annotatedWith(EventQueue.class).to(PresentsDObjectMgr.class); bind(DObjectManager.class).to(PresentsDObjectMgr.class); bind(RootDObjectManager.class).to(PresentsDObjectMgr.class); bind(Lifecycle.class).toInstance(new Lifecycle()); } /** * Binds just the invokers so this can be overridden if desired. */ protected void bindInvokers () { bind(Invoker.class).annotatedWith(MainInvoker.class).to(PresentsInvoker.class); bind(Invoker.class).annotatedWith(AuthInvoker.class).to(PresentsAuthInvoker.class); } } /** * Binds PresentsServer to a particular class. */ public static class PresentsServerModule extends AbstractModule { public PresentsServerModule (Class serverType) { _serverType = serverType; } @Override protected void configure () { bind(PresentsServer.class).to(_serverType); } protected final Class _serverType; } /** * Inits and runs the PresentsServer bound in the given modules. This blocks until the server * is shut down. */ public static void runServer (Module... modules) { Injector injector = Guice.createInjector(modules); PresentsServer server = injector.getInstance(PresentsServer.class); try { // initialize the server server.init(injector); // 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.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 (Throwable t) { log.warning("Unable to initialize server.", t); System.exit(-1); } } /** * The default entry point for the server. */ public static void main (String[] args) { runServer(new PresentsModule()); } /** Legacy static reference to the main distributed object manager. Don't use this. If you're * writing a game, use {@link PlaceManager#_omgr}. */ @Deprecated public static PresentsDObjectMgr omgr; /** Legacy static reference to the invocation manager. Don't use this. If you're * writing a game, use {@link PlaceManager#_invmgr}. */ @Deprecated public static InvocationManager invmgr; /** * Initializes all of the server services and prepares for operation. */ public void init (Injector injector) throws Exception { // output general system information SystemInfo si = new SystemInfo(); log.info("Starting up", "server", getClass().getSimpleName(), "os", si.osToString(), "jvm", si.jvmToString()); registerSignalHandlers(injector); // initialize our deprecated legacy static references omgr = _omgr; invmgr = _invmgr; // configure the dobject manager with our access controller _omgr.setDefaultAccessController(createDefaultObjectAccessController()); // start the main and auth invoker threads _invoker.start(); _authInvoker.start(); ((PresentsInvoker)_invoker).addInterdependentInvoker(_authInvoker); // provide our client manager with the injector it needs _clmgr.setInjector(injector); // Create our socket opening objects now that we know what ports we're using. _socketAcceptor = new ServerSocketChannelAcceptor(getBindHostname(), getListenPorts(), _conmgr); _lifecycle.addComponent(_socketAcceptor); _datagramReader = new DatagramChannelReader(getDatagramHostname(), getDatagramPorts(), _conmgr); _lifecycle.addComponent(_datagramReader); // initialize the time base services TimeBaseProvider.init(_invmgr, _omgr); // make the client manager shut down before the invoker and dobj threads; this helps // application code to avoid long chains of shutdown constraints _lifecycle.addShutdownConstraint( _clmgr, Lifecycle.Constraint.RUNS_BEFORE, (PresentsInvoker)_invoker); // initialize all of our registered components _lifecycle.init(); } protected void registerSignalHandlers (Injector injector) { // register SIGTERM, SIGINT (ctrl-c) and a SIGHUP handlers try { injector.getInstance(SunSignalHandler.class).init(); } catch (Throwable t) { log.warning("Unable to register Sun signal handlers", "error", t); } } /** * Starts up all of the server services and enters the main server event loop. */ public void run () { // wait till everything in the dobjmgr queue and invokers are processed and then start the // connection manager ((PresentsInvoker)_invoker).postRunnableWhenEmpty(new Runnable() { public void run () { // Take as long as you like opening to the public _omgr.postRunnable(new LongRunnable() { public void run () { openToThePublic(); } }); } }); // invoke the dobjmgr event loop _omgr.run(); } /** * Opens the server for connections after the event thread is running and all the invokers are * clear after starting up. */ protected void openToThePublic () { if (getListenPorts().length != 0 && !_socketAcceptor.bind()) { queueShutdown(); } else { _datagramReader.bind(); _conmgr.start(); } } /** * Queues up a request to shutdown on the event thread. This method may be safely called from * any thread. */ public void queueShutdown () { _omgr.postRunnable(new PresentsDObjectMgr.LongRunnable() { public void run () { _lifecycle.shutdown(); } }); } /** * 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 hostname on which the connection manager will listen for TCP traffic, or * null to bind to the wildcard address. */ protected String getBindHostname () { return null; } /** * Returns the hostname on which the connection will listen for datagram traffic, or * null to bind to the wildcard address. */ protected String getDatagramHostname () { return null; } /** * Returns the port on which the connection manager will listen for client connections or an * empty array to skip binding and have an external entity transfer accepted sockets into * the connection manager. */ protected int[] getListenPorts () { return Client.DEFAULT_SERVER_PORTS; } /** * Returns the ports on which the connection manager will listen for datagrams or an empty * array if no datagrams are desired. */ protected int[] getDatagramPorts () { return Client.DEFAULT_DATAGRAM_PORTS; } /** * Called once the invoker and distributed object manager have both completed processing all * remaining events and are fully shutdown. Note: this is called as the last act of * the invoker on the invoker thread. In theory no other (important) threads are * running, so thread safety should not be an issue, but be careful! */ protected void invokerDidShutdown () { } protected ServerSocketChannelAcceptor _socketAcceptor; protected DatagramChannelReader _datagramReader; /** The manager of distributed objects. */ @Inject protected PresentsDObjectMgr _omgr; /** The manager of network connections. */ @Inject protected PresentsConnectionManager _conmgr; /** The manager of clients. */ @Inject protected ClientManager _clmgr; /** The manager of invocation services. */ @Inject protected InvocationManager _invmgr; /** Handles orderly initialization and shutdown of our managers, etc. */ @Inject protected Lifecycle _lifecycle; /** Used to invoke background tasks that should not be allowed to tie up the distributed object * manager thread (generally talking to databases and other relatively slow entities). */ @Inject @MainInvoker protected Invoker _invoker; /** Used to invoke authentication tasks. */ @Inject @AuthInvoker protected Invoker _authInvoker; }