diff --git a/etc/libs-incl.xml b/etc/libs-incl.xml index 67af4e6eb..8567f9ced 100644 --- a/etc/libs-incl.xml +++ b/etc/libs-incl.xml @@ -3,11 +3,13 @@ + + diff --git a/src/java/com/threerings/crowd/peer/server/CrowdPeerManager.java b/src/java/com/threerings/crowd/peer/server/CrowdPeerManager.java index b2a84b7fb..1e7b960bc 100644 --- a/src/java/com/threerings/crowd/peer/server/CrowdPeerManager.java +++ b/src/java/com/threerings/crowd/peer/server/CrowdPeerManager.java @@ -21,11 +21,14 @@ package com.threerings.crowd.peer.server; +import com.google.inject.Inject; + import com.threerings.util.Name; import com.threerings.presents.data.ClientObject; import com.threerings.presents.server.InvocationException; import com.threerings.presents.server.PresentsClient; +import com.threerings.presents.server.ShutdownManager; import com.threerings.presents.peer.data.ClientInfo; import com.threerings.presents.peer.data.NodeObject; @@ -49,6 +52,14 @@ import com.threerings.crowd.peer.data.CrowdPeerMarshaller; public class CrowdPeerManager extends PeerManager implements CrowdPeerProvider, ChatProvider.ChatForwarder { + /** + * Creates an uninitialized peer manager. + */ + @Inject public CrowdPeerManager (ShutdownManager shutmgr) + { + super(shutmgr); + } + // from interface CrowdPeerProvider public void deliverTell (ClientObject caller, UserMessage message, Name target, ChatService.TellListener listener) diff --git a/src/java/com/threerings/crowd/server/CrowdServer.java b/src/java/com/threerings/crowd/server/CrowdServer.java index bd4f690a8..2593ef5a2 100644 --- a/src/java/com/threerings/crowd/server/CrowdServer.java +++ b/src/java/com/threerings/crowd/server/CrowdServer.java @@ -23,6 +23,9 @@ package com.threerings.crowd.server; import java.util.Iterator; +import com.google.inject.Guice; +import com.google.inject.Injector; + import com.threerings.util.Name; import com.threerings.presents.dobj.RootDObjectManager; @@ -44,6 +47,15 @@ import static com.threerings.crowd.Log.log; */ public class CrowdServer extends PresentsServer { + /** Configures dependencies needed by the Crowd services. */ + public static class Module extends PresentsServer.Module + { + @Override protected void configure () { + super.configure(); + // nada + } + } + /** The place registry. */ public static PlaceRegistry plreg; @@ -53,11 +65,10 @@ public class CrowdServer extends PresentsServer /** * Initializes all of the server services and prepares for operation. */ - public void init () + public void init (Injector injector) throws Exception { - // do the presents server initialization - super.init(); + super.init(injector); // configure the client manager to use our bits clmgr.setClientFactory(new ClientFactory() { @@ -139,9 +150,10 @@ public class CrowdServer extends PresentsServer public static void main (String[] args) { - CrowdServer server = new CrowdServer(); + Injector injector = Guice.createInjector(new Module()); + CrowdServer server = injector.getInstance(CrowdServer.class); try { - server.init(); + server.init(injector); server.run(); } catch (Exception e) { log.warning("Unable to initialize server.", e); diff --git a/src/java/com/threerings/presents/annotation/EventQueue.java b/src/java/com/threerings/presents/annotation/EventQueue.java new file mode 100644 index 000000000..61af69348 --- /dev/null +++ b/src/java/com/threerings/presents/annotation/EventQueue.java @@ -0,0 +1,45 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2008 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.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.google.inject.BindingAnnotation; + +import com.samskivert.util.RunQueue; + +/** + * An annotation that identifies the distributed object event dispatcher's {@link RunQueue}. Code + * that requires the ability to post runnables for execution on the event dispatcher thread can + * inject this queue like so: + * + * @Inject @EventQueue RunQueue _dobjq; + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.FIELD, ElementType.PARAMETER }) +@BindingAnnotation +public @interface EventQueue +{ +} diff --git a/src/java/com/threerings/presents/annotation/MainInvoker.java b/src/java/com/threerings/presents/annotation/MainInvoker.java new file mode 100644 index 000000000..744c7e1ed --- /dev/null +++ b/src/java/com/threerings/presents/annotation/MainInvoker.java @@ -0,0 +1,44 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2008 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.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.google.inject.BindingAnnotation; + +import com.samskivert.util.Invoker; + +/** + * An annotation that identifies the main Presents {@link Invoker}. Code that requires the ability + * to post units for execution on the main invoker thread can inject this queue like so: + * + * @Inject @MainInvoker Invoker _invoker; + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.FIELD, ElementType.PARAMETER }) +@BindingAnnotation +public @interface MainInvoker +{ +} diff --git a/src/java/com/threerings/presents/peer/server/PeerManager.java b/src/java/com/threerings/presents/peer/server/PeerManager.java index 0bc2e602f..b3d1a840d 100644 --- a/src/java/com/threerings/presents/peer/server/PeerManager.java +++ b/src/java/com/threerings/presents/peer/server/PeerManager.java @@ -32,6 +32,10 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; +import com.google.common.collect.Maps; +import com.google.inject.Inject; +import com.google.inject.Singleton; + import com.samskivert.jdbc.RepositoryUnit; import com.samskivert.jdbc.WriteOnlyUnit; import com.samskivert.jdbc.depot.PersistenceContext; @@ -49,7 +53,6 @@ import com.samskivert.util.Tuple; import com.threerings.io.Streamable; import com.threerings.util.Name; -import com.threerings.presents.client.Client; import com.threerings.presents.data.ClientObject; import com.threerings.presents.dobj.DObject; import com.threerings.presents.dobj.EntryAddedEvent; @@ -58,10 +61,15 @@ import com.threerings.presents.dobj.EntryUpdatedEvent; import com.threerings.presents.dobj.ObjectAccessException; import com.threerings.presents.dobj.SetListener; import com.threerings.presents.dobj.Subscriber; + +import com.threerings.presents.client.Client; import com.threerings.presents.server.ClientManager; import com.threerings.presents.server.InvocationException; +import com.threerings.presents.server.InvocationManager; import com.threerings.presents.server.PresentsClient; -import com.threerings.presents.server.PresentsServer; +import com.threerings.presents.server.PresentsDObjectMgr; +import com.threerings.presents.server.ShutdownManager; +import com.threerings.presents.server.net.ConnectionManager; import com.threerings.presents.peer.data.ClientInfo; import com.threerings.presents.peer.data.NodeObject; @@ -77,8 +85,9 @@ import static com.threerings.presents.Log.log; * client connection to the other servers and subscribes to the {@link NodeObject} of all peer * servers and uses those objects to communicate cross-node information. */ +@Singleton public class PeerManager - implements PeerProvider, ClientManager.ClientObserver, PresentsServer.Shutdowner + implements PeerProvider, ClientManager.ClientObserver, ShutdownManager.Shutdowner { /** * Used by entities that wish to know when cached data has become stale due to a change on @@ -168,6 +177,14 @@ public class PeerManager protected abstract void execute (); } + /** + * Creates an uninitialized peer manager. + */ + @Inject public PeerManager (ShutdownManager shutmgr) + { + shutmgr.registerShutdowner(this); + } + /** * Returns the distributed object that represents this node to its peers. */ @@ -202,14 +219,11 @@ public class PeerManager _sharedSecret = sharedSecret; // wire ourselves into the server - PresentsServer.registerShutdowner(this); - PresentsServer.conmgr.setAuthenticator( - new PeerAuthenticator(this, PresentsServer.conmgr.getAuthenticator())); - PresentsServer.clmgr.setClientFactory( - new PeerClientFactory(this, PresentsServer.clmgr.getClientFactory())); + _conmgr.setAuthenticator(new PeerAuthenticator(this, _conmgr.getAuthenticator())); + _clmgr.setClientFactory(new PeerClientFactory(this, _clmgr.getClientFactory())); // create our node object - _nodeobj = PresentsServer.omgr.registerObject(createNodeObject()); + _nodeobj = _omgr.registerObject(createNodeObject()); _nodeobj.setNodeName(nodeName); // register ourselves with the node table @@ -223,10 +237,10 @@ public class PeerManager // set the invocation service _nodeobj.setPeerService( - (PeerMarshaller)PresentsServer.invmgr.registerDispatcher(new PeerDispatcher(this))); + (PeerMarshaller)_invmgr.registerDispatcher(new PeerDispatcher(this))); // register ourselves as a client observer - PresentsServer.clmgr.addClientObserver(this); + _clmgr.addClientObserver(this); // and start our peer refresh interval _peerRefresher.schedule(5000L, 60*1000L); @@ -317,8 +331,8 @@ public class PeerManager public void invokeNodeAction (final NodeAction action) { // if we're not on the dobjmgr thread, get there - if (!PresentsServer.omgr.isDispatchThread()) { - PresentsServer.omgr.postRunnable(new Runnable() { + if (!_omgr.isDispatchThread()) { + _omgr.postRunnable(new Runnable() { public void run () { invokeNodeAction(action); } @@ -384,7 +398,7 @@ public class PeerManager // make a note of this proxy mapping _proxies.put(key, new Tuple,DObject>(this, object)); // map the object into our local oid space - PresentsServer.omgr.registerProxyObject(object, peer.getDObjectManager()); + _omgr.registerProxyObject(object, peer.getDObjectManager()); // then tell the caller about the (now remapped) oid listener.requestCompleted(object.getOid()); } @@ -408,7 +422,7 @@ public class PeerManager } // clear out the local object manager's proxy mapping - PresentsServer.omgr.clearProxyObject(remoteOid, bits.right); + _omgr.clearProxyObject(remoteOid, bits.right); final Client peer = getPeerClient(nodeName); if (peer == null) { @@ -681,19 +695,19 @@ public class PeerManager _nodeobj.setCacheData(new NodeObject.CacheData(cache, data)); } - // from interface PresentsServer.Shutdowner + // from interface ShutdownManager.Shutdowner public void shutdown () { // clear out our invocation service if (_nodeobj != null) { - PresentsServer.invmgr.clearDispatcher(_nodeobj.peerService); + _invmgr.clearDispatcher(_nodeobj.peerService); } // stop our peer refresher interval _peerRefresher.cancel(); // clear out our client observer registration - PresentsServer.clmgr.removeClientObserver(this); + _clmgr.removeClientObserver(this); // clear our record from the node table _invoker.postUnit(new WriteOnlyUnit("deleteNode(" + _nodeName + ")") { @@ -1004,7 +1018,7 @@ public class PeerManager _remoids = (ArrayIntSet)_suboids.clone(); // schedule a timeout to act if something goes wrong - (_timeout = new Interval(PresentsServer.omgr) { + (_timeout = new Interval(_omgr) { public void expired () { log.warning("Lock handler timed out, acting anyway [lock=" + _lock + ", acquire=" + _acquire + "]."); @@ -1198,19 +1212,23 @@ public class PeerManager /** Contains a mapping of proxied objects to subscriber instances. */ protected HashMap,Tuple,DObject>> _proxies = - new HashMap,Tuple,DObject>>(); + Maps.newHashMap(); /** Our stale cache observers. */ - protected HashMap> _cacheobs = - new HashMap>(); + protected HashMap> _cacheobs = Maps.newHashMap(); /** Listeners for dropped locks. */ protected ObserverList _dropobs = new ObserverList(ObserverList.FAST_UNSAFE_NOTIFY); /** Locks in the process of resolution. */ - protected HashMap _locks = - new HashMap(); + protected HashMap _locks = Maps.newHashMap(); + + // our service dependencies + @Inject protected ConnectionManager _conmgr; + @Inject protected ClientManager _clmgr; + @Inject protected PresentsDObjectMgr _omgr; + @Inject protected InvocationManager _invmgr; /** We wait this long for peer ratification to complete before acquiring/releasing the lock. */ protected static final long LOCK_TIMEOUT = 5000L; diff --git a/src/java/com/threerings/presents/server/AbstractSignalHandler.java b/src/java/com/threerings/presents/server/AbstractSignalHandler.java index a8bb227fe..7af3eb62a 100644 --- a/src/java/com/threerings/presents/server/AbstractSignalHandler.java +++ b/src/java/com/threerings/presents/server/AbstractSignalHandler.java @@ -21,6 +21,8 @@ package com.threerings.presents.server; +import com.google.inject.Inject; + import static com.threerings.presents.Log.log; /** @@ -28,9 +30,11 @@ import static com.threerings.presents.Log.log; */ public abstract class AbstractSignalHandler { - public boolean init (PresentsServer server) + /** + * Initializes this signal handler. + */ + public boolean init () { - _server = server; return registerHandlers(); } @@ -65,8 +69,9 @@ public abstract class AbstractSignalHandler */ protected void hupReceived () { - log.info(PresentsServer.generateReport()); + log.info(_repmgr.generateReport()); } - protected PresentsServer _server; + @Inject protected PresentsServer _server; + @Inject protected ReportManager _repmgr; } diff --git a/src/java/com/threerings/presents/server/ClientManager.java b/src/java/com/threerings/presents/server/ClientManager.java index eb6649a11..87bd55bc6 100644 --- a/src/java/com/threerings/presents/server/ClientManager.java +++ b/src/java/com/threerings/presents/server/ClientManager.java @@ -22,9 +22,14 @@ package com.threerings.presents.server; import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.inject.Inject; +import com.google.inject.Singleton; import com.samskivert.util.Interval; import com.samskivert.util.ObserverList; @@ -49,9 +54,10 @@ import static com.threerings.presents.Log.log; * thread (to notify of connections showing up or going away) and from the dobjmgr thread (when * clients are given the boot for application-defined reasons). */ +@Singleton public class ClientManager implements ConnectionObserver, ClientResolutionListener, - PresentsServer.Reporter, PresentsServer.Shutdowner + ReportManager.Reporter, ShutdownManager.Shutdowner { /** * Used by {@link #applyToClient}. @@ -89,25 +95,26 @@ public class ClientManager /** * Constructs a client manager that will interact with the supplied connection manager. */ - public ClientManager (ConnectionManager conmgr) + @Inject public ClientManager (ConnectionManager conmgr, ReportManager repmgr, + ShutdownManager shutmgr) { // register ourselves as a connection observer conmgr.addConnectionObserver(this); // start up an interval that will check for expired clients and flush them from the bowels // of the server - new Interval(PresentsServer.omgr) { + new Interval(_omgr) { public void expired () { flushClients(); } }.schedule(CLIENT_FLUSH_INTERVAL, true); // register as a "state of server" reporter and a shutdowner - PresentsServer.registerReporter(this); - PresentsServer.registerShutdowner(this); + repmgr.registerReporter(this); + shutmgr.registerShutdowner(this); } - // documentation inherited from interface + // from interface ShutdownManager.Shutdowner public void shutdown () { log.info("Client manager shutting down [ccount=" + _usermap.size() + "]."); @@ -237,7 +244,7 @@ public class ClientManager // report that the client is resolved on the dobjmgr thread to provide equivalent // behavior to the case where we actually have to do the resolution clobj.reference(); - PresentsServer.omgr.postRunnable(new Runnable() { + _omgr.postRunnable(new Runnable() { public void run () { listener.clientResolved(username, clobj); } @@ -265,11 +272,11 @@ public class ClientManager // create and register our client object and give it back to the client resolver; we // need to do this on the dobjmgr thread since we're registering an object final ClientResolver fclr = clr; - PresentsServer.omgr.postRunnable(new Runnable() { + _omgr.postRunnable(new Runnable() { public void run () { ClientObject clobj = fclr.createClientObject(); clobj.setPermissionPolicy(fclr.createPermissionPolicy()); - fclr.objectAvailable(PresentsServer.omgr.registerObject(clobj)); + fclr.objectAvailable(_omgr.registerObject(clobj)); } }); @@ -303,7 +310,7 @@ public class ClientManager _objmap.remove(username); // and destroy the object itself - PresentsServer.omgr.destroyObject(clobj.getOid()); + _omgr.destroyObject(clobj.getOid()); } /** @@ -411,7 +418,7 @@ public class ClientManager } } - // documentation inherited from interface PresentsServer.Reporter + // documentation inherited from interface ReportManager.Reporter public void appendReport (StringBuilder report, long now, long sinceLast, boolean reset) { report.append("* presents.ClientManager:\n"); @@ -477,7 +484,7 @@ public class ClientManager */ protected void flushClients () { - ArrayList victims = new ArrayList(); + List victims = Lists.newArrayList(); long now = System.currentTimeMillis(); // first build a list of our victims @@ -530,23 +537,25 @@ public class ClientManager } /** A mapping from auth username to client instances. */ - protected HashMap _usermap = new HashMap(); + protected Map _usermap = Maps.newHashMap(); /** A mapping from connections to client instances. */ - protected HashMap _conmap = new HashMap(); + protected Map _conmap = Maps.newHashMap(); /** A mapping from usernames to client object instances. */ - protected HashMap _objmap = new HashMap(); + protected Map _objmap = Maps.newHashMap(); /** A mapping of pending client resolvers. */ - protected HashMap _penders = new HashMap(); + protected Map _penders = Maps.newHashMap(); /** The client class in use. */ protected ClientFactory _factory = ClientFactory.DEFAULT; /** Tracks registered {@link ClientObserver}s. */ - protected ObserverList _clobservers = - new ObserverList(ObserverList.SAFE_IN_ORDER_NOTIFY); + protected ObserverList _clobservers = ObserverList.newSafeInOrder(); + + // our injected dependencies + @Inject protected PresentsDObjectMgr _omgr; /** The frequency with which we check for expired clients. */ protected static final long CLIENT_FLUSH_INTERVAL = 60 * 1000L; diff --git a/src/java/com/threerings/presents/server/InvocationManager.java b/src/java/com/threerings/presents/server/InvocationManager.java index 8aa35f686..343166d7a 100644 --- a/src/java/com/threerings/presents/server/InvocationManager.java +++ b/src/java/com/threerings/presents/server/InvocationManager.java @@ -21,9 +21,14 @@ package com.threerings.presents.server; -import java.util.HashMap; +import java.util.Map; -import com.samskivert.util.HashIntMap; +import com.google.common.collect.Maps; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +import com.samskivert.util.IntMaps; +import com.samskivert.util.IntMap; import com.samskivert.util.LRUHashMap; import com.samskivert.util.StringUtil; @@ -39,7 +44,6 @@ import com.threerings.presents.dobj.DObject; import com.threerings.presents.dobj.EventListener; import com.threerings.presents.dobj.InvocationRequestEvent; import com.threerings.presents.dobj.ObjectAccessException; -import com.threerings.presents.dobj.RootDObjectManager; import com.threerings.presents.net.Transport; @@ -62,6 +66,7 @@ import static com.threerings.presents.Log.log; * provides a mechanism by which responses and asynchronous notification invocations can be * delivered to the client. */ +@Singleton public class InvocationManager implements EventListener { @@ -70,16 +75,16 @@ public class InvocationManager * operate its invocation services. Generally only one invocation manager should be operational * in a particular system. */ - public InvocationManager (RootDObjectManager omgr) + @Inject public InvocationManager (PresentsDObjectMgr omgr) { _omgr = omgr; // create the object on which we'll listen for invocation requests - DObject invobj = omgr.registerObject(new DObject()); + DObject invobj = _omgr.registerObject(new DObject()); invobj.addListener(this); _invoid = invobj.getOid(); -// Log.info("Created invocation service object [oid=" + _invoid + "]."); +// log.info("Created invocation service object [oid=" + _invoid + "]."); } /** @@ -143,7 +148,7 @@ public class InvocationManager _recentRegServices.put(Integer.valueOf(invCode), marsh.getClass().getName()); -// Log.info("Registered service [marsh=" + marsh + "]."); +// log.info("Registered service [marsh=" + marsh + "]."); return marsh; } @@ -199,7 +204,7 @@ public class InvocationManager // documentation inherited from interface public void eventReceived (DEvent event) { -// Log.info("Event received " + event + "."); +// log.info("Event received " + event + "."); if (event instanceof InvocationRequestEvent) { InvocationRequestEvent ire = (InvocationRequestEvent)event; @@ -255,7 +260,7 @@ public class InvocationManager } } -// Log.debug("Dispatching invreq [caller=" + source.who() + +// log.debug("Dispatching invreq [caller=" + source.who() + // ", disp=" + disp + ", methId=" + methodId + // ", args=" + StringUtil.toString(args) + "]."); @@ -301,29 +306,24 @@ public class InvocationManager return _invCode++; } - // debugging action... - protected static final LRUHashMap _recentRegServices = - new LRUHashMap(10000); - - /** The distributed object manager with which we're working. */ - protected RootDObjectManager _omgr; - /** The object id of the object on which we receive invocation service requests. */ protected int _invoid = -1; /** Used to generate monotonically increasing provider ids. */ protected int _invCode; + /** The distribted object manager we're working with. */ + protected PresentsDObjectMgr _omgr; + /** A table of invocation dispatchers each mapped by a unique code. */ - protected HashIntMap _dispatchers = - new HashIntMap(); + protected IntMap _dispatchers = IntMaps.newHashIntMap(); - /** A mapping from bootstrap group to lists of services that are to be provided to clients at - * boot time. */ - protected HashMap> _bootlists = - new HashMap>(); + /** Maps bootstrap group to lists of services to be provided to clients at boot time. */ + protected Map> _bootlists = Maps.newHashMap(); - /** The text that is appended to the procedure name when automatically generating a failure - * response. */ + // debugging action... + protected final Map _recentRegServices = new LRUHashMap(10000); + + /** The text appended to the procedure name when generating a failure response. */ protected static final String FAILED_SUFFIX = "Failed"; } diff --git a/src/java/com/threerings/presents/server/LocalDObjectMgr.java b/src/java/com/threerings/presents/server/LocalDObjectMgr.java index d25614682..006dca4e6 100644 --- a/src/java/com/threerings/presents/server/LocalDObjectMgr.java +++ b/src/java/com/threerings/presents/server/LocalDObjectMgr.java @@ -23,6 +23,7 @@ package com.threerings.presents.server; import java.awt.EventQueue; +import com.google.inject.Inject; import com.threerings.presents.dobj.DEvent; import com.threerings.presents.dobj.DObject; import com.threerings.presents.dobj.DObjectManager; @@ -35,6 +36,14 @@ import com.threerings.presents.dobj.Subscriber; */ public class LocalDObjectMgr extends PresentsDObjectMgr { + /** + * Creates the dobjmgr and prepares it for operation. + */ + @Inject public LocalDObjectMgr (ReportManager repmgr) + { + super(repmgr); + } + /** * Creates a {@link DObjectManager} that posts directly to this local object manager, but first * sets the source oid of all events to properly identify them with the supplied client oid. diff --git a/src/java/com/threerings/presents/server/PresentsDObjectMgr.java b/src/java/com/threerings/presents/server/PresentsDObjectMgr.java index 4d8824102..3764a95e6 100644 --- a/src/java/com/threerings/presents/server/PresentsDObjectMgr.java +++ b/src/java/com/threerings/presents/server/PresentsDObjectMgr.java @@ -22,14 +22,17 @@ package com.threerings.presents.server; import java.lang.reflect.*; -import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; +import com.google.common.collect.Maps; +import com.google.inject.Inject; +import com.google.inject.Singleton; + import com.samskivert.util.AuditLogger; -import com.samskivert.util.HashIntMap; import com.samskivert.util.Histogram; +import com.samskivert.util.IntMap; +import com.samskivert.util.IntMaps; import com.samskivert.util.Interval; import com.samskivert.util.Invoker; import com.samskivert.util.Queue; @@ -51,11 +54,12 @@ import static com.threerings.presents.Log.log; * thus provides a method to be invoked by the application main thread which won't return until the * manager has been requested to shut down. */ +@Singleton public class PresentsDObjectMgr - implements RootDObjectManager, RunQueue, PresentsServer.Reporter + implements RootDObjectManager, RunQueue, ReportManager.Reporter { /** Contains operational statistics that are tracked by the distributed object manager between - * {@link PresentsServer#Reporter} intervals. The snapshot for the most recently completed + * {@link ReportManager#Reporter} intervals. The snapshot for the most recently completed * period can be requested via {@link #getStats()}. . */ public static class Stats { @@ -77,17 +81,21 @@ public class PresentsDObjectMgr /** * Creates the dobjmgr and prepares it for operation. */ - public PresentsDObjectMgr () + @Inject public PresentsDObjectMgr (ReportManager repmgr) { - // we create a dummy object to live as oid zero and we'll use that for some internal event - // trickery + // create a dummy object to live as oid zero and use that for some internal event trickery DObject dummy = new DObject(); dummy.setOid(0); dummy.setManager(this); _objects.put(0, new DObject()); - // register ourselves as a state of server reporter - PresentsServer.registerReporter(this); + // register ourselves as a state of server reporter and also tell the report manager that + // we're providing the runqueue for it to do its business + repmgr.registerReporter(this); + repmgr.init(this); + + // register our event helpers + registerEventHelpers(); } /** @@ -112,8 +120,7 @@ public class PresentsDObjectMgr _defaultController = controller; // switch all objects from the old default (null, usually) to the new default. - for (Iterator itr = _objects.elements(); itr.hasNext(); ) { - DObject obj = (DObject) itr.next(); + for (DObject obj : _objects.values()) { if (oldDefault == obj.getAccessController()) { obj.setAccessController(controller); } @@ -289,6 +296,235 @@ public class PresentsDObjectMgr log.info("DOMGR exited."); } + /** + * Requests that the dobjmgr shut itself down soon- you may want to try using {@link + * Invoker#shutdown} which will make sure that both the Invoker and DObjectMgr are empty and + * then shut them both down. + */ + public void harshShutdown () + { + postRunnable(new Runnable() { + public void run () { + _running = false; + } + }); + } + + /** + * Dumps collected profiling information to the system log. + */ + public void dumpUnitProfiles () + { + for (Map.Entry entry : _profiles.entrySet()) { + log.info("P: " + entry.getKey() + " => " + entry.getValue()); + } + } + + /** + * Called as a helper for ObjectDestroyedEvent events. It removes the object from + * the object table. + * + * @return true if the event should be dispatched, false if it should be aborted. + */ + public boolean objectDestroyed (DEvent event, DObject target) + { + int oid = target.getOid(); + +// log.info("Removing destroyed object from table [oid=" + oid + "]."); + + // remove the object from the table + _objects.remove(oid); + + // inactivate the object + target.setManager(null); + + // deal with any remaining oid lists that reference this object + Reference[] refs = _refs.remove(oid); + if (refs != null) { + for (int i = 0; i < refs.length; i++) { + // skip empty spots + if (refs[i] == null) { + continue; + } + + Reference ref = refs[i]; + DObject reffer = _objects.get(ref.reffingOid); + + // ensure that the referencing object is still around + if (reffer != null) { + // post an object removed event to clear the reference + postEvent(new ObjectRemovedEvent(ref.reffingOid, ref.field, oid)); +// log.info("Forcing removal " + ref + "."); + + } else { + log.info("Dangling reference from inactive object " + ref + "."); + } + } + } + + // if this object has any oid list fields that are still referencing other objects, we need + // to clear out those references + Class oclass = target.getClass(); + Field[] fields = oclass.getFields(); + for (int f = 0; f < fields.length; f++) { + Field field = fields[f]; + + // ignore static and non-public fields + int mods = field.getModifiers(); + if ((mods & Modifier.STATIC) != 0 || (mods & Modifier.PUBLIC) == 0) { + continue; + } + + // ignore non-oidlist fields + if (!OidList.class.isAssignableFrom(field.getType())) { + continue; + } + + try { + OidList list = (OidList)field.get(target); + for (int i = 0; i < list.size(); i++) { + clearReference(target, field.getName(), list.get(i)); + } + + } catch (Exception e) { + log.warning("Unable to clean up after oid list field [target=" + target + + ", field=" + field + "]."); + } + } + + return true; + } + + /** + * Called as a helper for ObjectAddedEvent events. It updates the object/oid list + * tracking structures. + * + * @return true if the event should be dispatched, false if it should be aborted. + */ + public boolean objectAdded (DEvent event, DObject target) + { + ObjectAddedEvent oae = (ObjectAddedEvent)event; + int oid = oae.getOid(); + + // ensure that the target object exists + if (!_objects.containsKey(oid)) { + log.info("Rejecting object added event of non-existent object " + + "[refferOid=" + target.getOid() + ", reffedOid=" + oid + "]."); + return false; + } + + // get the reference vector for the referenced object. we use bare arrays rather than + // something like an array list to conserve memory. there will be many objects and + // references + Reference[] refs = _refs.get(oid); + if (refs == null) { + refs = new Reference[DEFREFVEC_SIZE]; + _refs.put(oid, refs); + } + + // determine where to add the reference + Reference ref = new Reference(target.getOid(), oae.getName(), oid); + int rpos = -1; + for (int i = 0; i < refs.length; i++) { + if (ref.equals(refs[i])) { + log.warning("Ignoring request to track existing reference " + ref + "."); + return true; + } else if (refs[i] == null && rpos == -1) { + rpos = i; + } + } + + // expand the refvec if necessary + if (rpos == -1) { + Reference[] nrefs = new Reference[refs.length*2]; + System.arraycopy(refs, 0, nrefs, 0, refs.length); + rpos = refs.length; + _refs.put(oid, refs = nrefs); + } + + // finally add the reference + refs[rpos] = ref; + +// log.info("Tracked reference " + ref + "."); + return true; + } + + /** + * Called as a helper for ObjectRemovedEvent events. It updates the object/oid + * list tracking structures. + * + * @return true if the event should be dispatched, false if it should be aborted. + */ + public boolean objectRemoved (DEvent event, DObject target) + { + ObjectRemovedEvent ore = (ObjectRemovedEvent)event; + String field = ore.getName(); + int toid = target.getOid(); + int oid = ore.getOid(); + +// log.info("Processing object removed [from=" + toid + ", roid=" + toid + "]."); + + // get the reference vector for the referenced object + Reference[] refs = _refs.get(oid); + if (refs == null) { + // this can happen normally when an object is destroyed. it will remove itself from the + // reference system and then generate object removed events for all of its referencees. + // so we opt not to log anything in this case + +// log.info("Object removed without reference to track it [toid=" + toid + +// ", field=" + field + ", oid=" + oid + "]."); + return true; + } + + // look for the matching reference + for (int i = 0; i < refs.length; i++) { + Reference ref = refs[i]; + if (ref != null && ref.equals(toid, field)) { +// log.info("Removed reference " + refs[i] + "."); + refs[i] = null; + return true; + } + } + + log.warning("Unable to locate reference for removal [reffingOid=" + toid + + ", field=" + field + ", reffedOid=" + oid + "]."); + return true; + } + + /** + * Should not need to be called except by the invoker during shutdown to ensure that things are + * proceeding smoothly. + */ + public boolean queueIsEmpty () + { + return !_evqueue.hasElements(); + } + + // from interface ReportManager.Reporter + public void appendReport (StringBuilder report, long now, long sinceLast, boolean reset) + { + report.append("* presents.PresentsDObjectMgr:\n"); + int queueSize = _evqueue.size(); + report.append("- Queue size: ").append(queueSize).append("\n"); + report.append("- Max queue size: ").append(_current.maxQueueSize).append("\n"); + report.append("- Units executed: ").append(_current.eventCount).append("\n"); + + if (UNIT_PROF_ENABLED) { + report.append("- Unit profiles: ").append(_profiles.size()).append("\n"); + for (Map.Entry entry : _profiles.entrySet()) { + report.append(" ").append(entry.getKey()); + report.append(" ").append(entry.getValue()).append("\n"); + } + } + + // roll over stats + if (reset) { + _recent = _current; + _current = new Stats(); + _current.maxQueueSize = queueSize; + } + } + /** * Processes a single unit from the queue. */ @@ -352,7 +588,7 @@ public class PresentsDObjectMgr cname = StringUtil.shortClassName(ival); } else if (unit instanceof InvocationRequestEvent) { InvocationRequestEvent ire = (InvocationRequestEvent)unit; - Class c = PresentsServer.invmgr.getDispatcherClass(ire.getInvCode()); + Class c = _invmgr.getDispatcherClass(ire.getInvCode()); cname = (c == null) ? "dobj.InvocationRequestEvent:(no longer registered)" : StringUtil.shortClassName(c) + ":" + ire.getMethodId(); } else { @@ -481,105 +717,6 @@ public class PresentsDObjectMgr log.warning("Fatal error caused by '" + causer + "': " + error, error); } - /** - * Requests that the dobjmgr shut itself down soon- you may want to try using {@link - * Invoker#shutdown} which will make sure that both the Invoker and DObjectMgr are empty and - * then shut them both down. - */ - public void harshShutdown () - { - postRunnable(new Runnable() { - public void run () { - _running = false; - } - }); - } - - /** - * Dumps collected profiling information to the system log. - */ - public void dumpUnitProfiles () - { - for (Map.Entry entry : _profiles.entrySet()) { - log.info("P: " + entry.getKey() + " => " + entry.getValue()); - } - } - - /** - * Called as a helper for ObjectDestroyedEvent events. It removes the object from - * the object table. - * - * @return true if the event should be dispatched, false if it should be aborted. - */ - public boolean objectDestroyed (DEvent event, DObject target) - { - int oid = target.getOid(); - -// log.info("Removing destroyed object from table [oid=" + oid + "]."); - - // remove the object from the table - _objects.remove(oid); - - // inactivate the object - target.setManager(null); - - // deal with any remaining oid lists that reference this object - Reference[] refs = _refs.remove(oid); - if (refs != null) { - for (int i = 0; i < refs.length; i++) { - // skip empty spots - if (refs[i] == null) { - continue; - } - - Reference ref = refs[i]; - DObject reffer = _objects.get(ref.reffingOid); - - // ensure that the referencing object is still around - if (reffer != null) { - // post an object removed event to clear the reference - postEvent(new ObjectRemovedEvent(ref.reffingOid, ref.field, oid)); -// log.info("Forcing removal " + ref + "."); - - } else { - log.info("Dangling reference from inactive object " + ref + "."); - } - } - } - - // if this object has any oid list fields that are still referencing other objects, we need - // to clear out those references - Class oclass = target.getClass(); - Field[] fields = oclass.getFields(); - for (int f = 0; f < fields.length; f++) { - Field field = fields[f]; - - // ignore static and non-public fields - int mods = field.getModifiers(); - if ((mods & Modifier.STATIC) != 0 || (mods & Modifier.PUBLIC) == 0) { - continue; - } - - // ignore non-oidlist fields - if (!OidList.class.isAssignableFrom(field.getType())) { - continue; - } - - try { - OidList list = (OidList)field.get(target); - for (int i = 0; i < list.size(); i++) { - clearReference(target, field.getName(), list.get(i)); - } - - } catch (Exception e) { - log.warning("Unable to clean up after oid list field [target=" + target + - ", field=" + field + "]."); - } - } - - return true; - } - /** * Called by objectDestroyed; clears out the tracking info for a reference by the * supplied object to the specified oid via the specified field. @@ -615,111 +752,6 @@ public class PresentsDObjectMgr } } - /** - * Called as a helper for ObjectAddedEvent events. It updates the object/oid list - * tracking structures. - * - * @return true if the event should be dispatched, false if it should be aborted. - */ - public boolean objectAdded (DEvent event, DObject target) - { - ObjectAddedEvent oae = (ObjectAddedEvent)event; - int oid = oae.getOid(); - - // ensure that the target object exists - if (!_objects.containsKey(oid)) { - log.info("Rejecting object added event of non-existent object " + - "[refferOid=" + target.getOid() + ", reffedOid=" + oid + "]."); - return false; - } - - // get the reference vector for the referenced object. we use bare arrays rather than - // something like an array list to conserve memory. there will be many objects and - // references - Reference[] refs = _refs.get(oid); - if (refs == null) { - refs = new Reference[DEFREFVEC_SIZE]; - _refs.put(oid, refs); - } - - // determine where to add the reference - Reference ref = new Reference(target.getOid(), oae.getName(), oid); - int rpos = -1; - for (int i = 0; i < refs.length; i++) { - if (ref.equals(refs[i])) { - log.warning("Ignoring request to track existing reference " + ref + "."); - return true; - } else if (refs[i] == null && rpos == -1) { - rpos = i; - } - } - - // expand the refvec if necessary - if (rpos == -1) { - Reference[] nrefs = new Reference[refs.length*2]; - System.arraycopy(refs, 0, nrefs, 0, refs.length); - rpos = refs.length; - _refs.put(oid, refs = nrefs); - } - - // finally add the reference - refs[rpos] = ref; - -// log.info("Tracked reference " + ref + "."); - return true; - } - - /** - * Called as a helper for ObjectRemovedEvent events. It updates the object/oid - * list tracking structures. - * - * @return true if the event should be dispatched, false if it should be aborted. - */ - public boolean objectRemoved (DEvent event, DObject target) - { - ObjectRemovedEvent ore = (ObjectRemovedEvent)event; - String field = ore.getName(); - int toid = target.getOid(); - int oid = ore.getOid(); - -// log.info("Processing object removed [from=" + toid + ", roid=" + toid + "]."); - - // get the reference vector for the referenced object - Reference[] refs = _refs.get(oid); - if (refs == null) { - // this can happen normally when an object is destroyed. it will remove itself from the - // reference system and then generate object removed events for all of its referencees. - // so we opt not to log anything in this case - -// log.info("Object removed without reference to track it [toid=" + toid + -// ", field=" + field + ", oid=" + oid + "]."); - return true; - } - - // look for the matching reference - for (int i = 0; i < refs.length; i++) { - Reference ref = refs[i]; - if (ref != null && ref.equals(toid, field)) { -// log.info("Removed reference " + refs[i] + "."); - refs[i] = null; - return true; - } - } - - log.warning("Unable to locate reference for removal [reffingOid=" + toid + - ", field=" + field + ", reffedOid=" + oid + "]."); - return true; - } - - /** - * Should not need to be called except by the invoker during shutdown to ensure that things are - * proceeding smoothly. - */ - public boolean queueIsEmpty () - { - return !_evqueue.hasElements(); - } - protected synchronized boolean isRunning () { return _running; @@ -735,28 +767,26 @@ public class PresentsDObjectMgr return _nextOid; } - // from interface PresentsServer.Reporter - public void appendReport (StringBuilder report, long now, long sinceLast, boolean reset) + /** + * Registers our event helper methods. + */ + protected void registerEventHelpers () { - report.append("* presents.PresentsDObjectMgr:\n"); - int queueSize = _evqueue.size(); - report.append("- Queue size: ").append(queueSize).append("\n"); - report.append("- Max queue size: ").append(_current.maxQueueSize).append("\n"); - report.append("- Units executed: ").append(_current.eventCount).append("\n"); + Class[] ptypes = new Class[] { DEvent.class, DObject.class }; + Method method; - if (UNIT_PROF_ENABLED) { - report.append("- Unit profiles: ").append(_profiles.size()).append("\n"); - for (Map.Entry entry : _profiles.entrySet()) { - report.append(" ").append(entry.getKey()); - report.append(" ").append(entry.getValue()).append("\n"); - } - } + try { + method = PresentsDObjectMgr.class.getMethod("objectDestroyed", ptypes); + _helpers.put(ObjectDestroyedEvent.class, method); - // roll over stats - if (reset) { - _recent = _current; - _current = new Stats(); - _current.maxQueueSize = queueSize; + method = PresentsDObjectMgr.class.getMethod("objectAdded", ptypes); + _helpers.put(ObjectAddedEvent.class, method); + + method = PresentsDObjectMgr.class.getMethod("objectRemoved", ptypes); + _helpers.put(ObjectRemovedEvent.class, method); + + } catch (Exception e) { + log.warning("Unable to register event helpers [error=" + e + "]."); } } @@ -846,29 +876,6 @@ public class PresentsDObjectMgr protected int _action; } - /** - * Registers our event helper methods. - */ - protected static void registerEventHelpers () - { - Class[] ptypes = new Class[] { DEvent.class, DObject.class }; - Method method; - - try { - method = PresentsDObjectMgr.class.getMethod("objectDestroyed", ptypes); - _helpers.put(ObjectDestroyedEvent.class, method); - - method = PresentsDObjectMgr.class.getMethod("objectAdded", ptypes); - _helpers.put(ObjectAddedEvent.class, method); - - method = PresentsDObjectMgr.class.getMethod("objectRemoved", ptypes); - _helpers.put(ObjectRemovedEvent.class, method); - - } catch (Exception e) { - log.warning("Unable to register event helpers [error=" + e + "]."); - } - } - /** * Used to track references of objects in oid lists. */ @@ -947,7 +954,7 @@ public class PresentsDObjectMgr protected Queue _evqueue = new Queue(); /** The managed distributed objects table. */ - protected HashIntMap _objects = new HashIntMap(); + protected IntMap _objects = IntMaps.newHashIntMap(); /** Used to assign a unique oid to each distributed object. */ protected int _nextOid = 0; @@ -960,28 +967,33 @@ public class PresentsDObjectMgr protected Throttle _fatalThrottle = new Throttle(30, 60*1000L); /** Used to track oid list references of distributed objects. */ - protected HashIntMap _refs = new HashIntMap(); + protected IntMap _refs = IntMaps.newHashIntMap(); /** The default access controller to use when creating distributed objects. */ protected AccessController _defaultController; /** Maintains proxy information for any proxied distributed objects. */ - protected HashIntMap _proxies = new HashIntMap(); + protected IntMap _proxies = IntMaps.newHashIntMap(); - /** We keep track of which thread is executing the event loop so that other services can - * enforce restrictions on code that should or should not be called from the event dispatch - * thread. */ + /** keeps Track of which thread is executing the event loop so that other services can enforce + * restrictions on code that should or should not be called from the event dispatch thread. */ protected Thread _dobjThread; /** A monotonically increasing counter used to assign an id to all dispatched events. */ protected long _nextEventId = 1; /** Used to profile our events and runnable units. */ - protected HashMap _profiles = new HashMap(); + protected Map _profiles = Maps.newHashMap(); /** Used to track runtime statistics. */ protected Stats _recent = new Stats(), _current = _recent; + /** Maps event classes to helpers that perform additional processing for particular events. */ + protected Map _helpers = Maps.newHashMap(); + + // injected dependencies + @Inject protected InvocationManager _invmgr; + /** Whether or not unit profiling is enabled. */ protected static final boolean UNIT_PROF_ENABLED = false; @@ -990,9 +1002,4 @@ public class PresentsDObjectMgr /** The default size of an oid list refs vector. */ protected static final int DEFREFVEC_SIZE = 4; - - /** This table maps event classes to helper methods that perform some additional processing for - * particular events. */ - protected static HashMap _helpers = new HashMap(); - static { registerEventHelpers(); } } diff --git a/src/java/com/threerings/presents/server/PresentsInvoker.java b/src/java/com/threerings/presents/server/PresentsInvoker.java index 4187079c7..f3d615e67 100644 --- a/src/java/com/threerings/presents/server/PresentsInvoker.java +++ b/src/java/com/threerings/presents/server/PresentsInvoker.java @@ -23,43 +23,41 @@ package com.threerings.presents.server; import java.util.Iterator; +import com.google.inject.Inject; + import com.samskivert.util.Invoker; import com.samskivert.util.StringUtil; import static com.threerings.presents.Log.log; /** - * Extends the generic {@link Invoker} and integrates it a bit more into - * the Presents system. + * Extends the generic {@link Invoker} and integrates it a bit more into the Presents system. */ public class PresentsInvoker extends Invoker - implements PresentsServer.Reporter + implements ReportManager.Reporter { /** * Creates an invoker that will post results to the supplied * distributed object manager. */ - public PresentsInvoker (PresentsDObjectMgr omgr) + @Inject public PresentsInvoker (PresentsDObjectMgr omgr, ReportManager repmgr) { super("presents.Invoker", omgr); _omgr = omgr; if (PERF_TRACK) { - PresentsServer.registerReporter(this); + repmgr.registerReporter(this); } } - /** - * Will do a sophisticated shutdown of both itself and the DObjectManager - * thread. - */ + @Override // from Invoker public void shutdown () { + // this will do a sophisticated shutdown of both ourself and the dobjmgr _queue.append(new ShutdownUnit()); } - // documentation inherited from interface - public void appendReport ( - StringBuilder buf, long now, long sinceLast, boolean reset) + // from interface ReportManager.Reporter + public void appendReport (StringBuilder buf, long now, long sinceLast, boolean reset) { buf.append("* presents.util.Invoker:\n"); int qsize = _queue.size(); @@ -93,7 +91,7 @@ public class PresentsInvoker extends Invoker } } - // documentation inherited + @Override // from Invoker protected void willInvokeUnit (Unit unit, long start) { super.willInvokeUnit(unit, start); @@ -111,7 +109,7 @@ public class PresentsInvoker extends Invoker } } - // documentation inherited + @Override // from Invoker protected void didInvokeUnit (Unit unit, long start) { super.didInvokeUnit(unit, start); @@ -124,9 +122,8 @@ public class PresentsInvoker extends Invoker } /** - * This unit gets posted back and forth between the invoker and DObjectMgr - * until both of their queues are empty and they can both be safely - * shutdown. + * This gets posted back and forth between the invoker and DObjectMgr until both of their + * queues are empty and they can both be safely shutdown. */ protected class ShutdownUnit extends Unit { diff --git a/src/java/com/threerings/presents/server/PresentsServer.java b/src/java/com/threerings/presents/server/PresentsServer.java index c85dc8153..addcbf75e 100644 --- a/src/java/com/threerings/presents/server/PresentsServer.java +++ b/src/java/com/threerings/presents/server/PresentsServer.java @@ -21,13 +21,17 @@ package com.threerings.presents.server; -import java.util.ArrayList; +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.Injector; -import com.samskivert.util.Interval; -import com.samskivert.util.ObserverList; -import com.samskivert.util.StringUtil; +import com.samskivert.util.Invoker; +import com.samskivert.util.RunQueue; import com.samskivert.util.SystemInfo; +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.server.net.ConnectionManager; @@ -44,74 +48,40 @@ import static com.threerings.presents.Log.log; */ public class PresentsServer { - /** Used to generate "state of the server" reports. See {@link #registerReporter}. */ - public static interface Reporter + /** Configures dependencies needed by the Presents services. */ + public static class Module extends AbstractModule { - /** - * 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); + @Override protected void configure () { + bind(Invoker.class).annotatedWith(MainInvoker.class).to(ServerInvoker.class); + bind(RunQueue.class).annotatedWith(EventQueue.class).to(PresentsDObjectMgr.class); + } } - /** 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. */ + /** OBSOLETE! Don't use me. */ public static ConnectionManager conmgr; - /** The manager of clients. */ + /** OBSOLETE! Don't use me. */ public static ClientManager clmgr; - /** The distributed object manager. */ + /** OBSOLETE! Don't use me. */ public static PresentsDObjectMgr omgr; - /** The invocation manager. */ + /** OBSOLETE! Don't use me. */ 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); - } + /** OBSOLETE! Don't use me. */ + public static Invoker invoker; /** * The default entry point for the server. */ public static void main (String[] args) { - log.info("Presents server starting..."); - - PresentsServer server = new PresentsServer(); + Injector injector = Guice.createInjector(new Module()); + PresentsServer server = injector.getInstance(PresentsServer.class); try { // initialize the server - server.init(); + 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"); @@ -139,9 +109,16 @@ public class PresentsServer /** * Initializes all of the server services and prepares for operation. */ - public void init () + public void init (Injector injector) throws Exception { + // populate our legacy statics + conmgr = _conmgr; + clmgr = _clmgr; + omgr = _omgr; + invmgr = _invmgr; + invoker = _invoker; + // output general system information SystemInfo si = new SystemInfo(); log.info("Starting up server [os=" + si.osToString() + ", jvm=" + si.jvmToString() + @@ -150,48 +127,26 @@ public class PresentsServer // register SIGTERM, SIGINT (ctrl-c) and a SIGHUP handlers boolean registered = false; try { - registered = new SunSignalHandler().init(this); + registered = injector.getInstance(SunSignalHandler.class).init(); } catch (Throwable t) { log.warning("Unable to register Sun signal handlers [error=" + t + "]."); } if (!registered) { - new NativeSignalHandler().init(this); + injector.getInstance(NativeSignalHandler.class).init(); } - // create our distributed object manager - omgr = createDObjectManager(); - // configure the dobject manager with our access controller - omgr.setDefaultAccessController(createDefaultObjectAccessController()); + _omgr.setDefaultAccessController(createDefaultObjectAccessController()); - // create and start up our invoker - invoker = new PresentsInvoker(omgr) { - protected void didShutdown () { - invokerDidShutdown(); - } - }; - invoker.start(); + // start the main invoker thread + _invoker.start(); - // create our connection manager - conmgr = new ConnectionManager(getListenPorts(), getDatagramPorts()); - conmgr.setAuthenticator(createAuthenticator()); - - // create our client manager - clmgr = createClientManager(conmgr); - - // create our invocation manager - invmgr = new InvocationManager(omgr); + // configure our connection manager + _conmgr.init(getListenPorts(), getDatagramPorts()); + _conmgr.setAuthenticator(createAuthenticator()); // initialize the time base services TimeBaseProvider.init(invmgr, omgr); - - // queue up an interval which will generate reports - _reportInterval = new Interval(omgr) { - public void expired () { - logReport(generateReport(System.currentTimeMillis(), true)); - } - }; - _reportInterval.schedule(REPORT_INTERVAL, true); } /** @@ -202,21 +157,21 @@ public class PresentsServer return new DummyAuthenticator(); } - /** - * Creates the client manager to be used on this server. - */ - protected ClientManager createClientManager (ConnectionManager conmgr) - { - return new ClientManager(conmgr); - } +// /** +// * 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(); - } +// /** +// * 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 @@ -253,119 +208,26 @@ public class PresentsServer omgr.postRunnable(new Runnable() { public void run () { // start up the connection manager - conmgr.start(); + _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.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 downers = _downers; - if (downers == null) { - log.warning("Refusing repeat shutdown request."); - return; - } - _downers = null; - - // shut down all shutdown participants - downers.apply(new ObserverList.ObserverOp() { - public boolean apply (Shutdowner downer) { - downer.shutdown(); - return true; - } - }); + // shutdown all registered shutdowners + _shutmgr.shutdown(); // shut down the connection manager (this will cease all network activity but not actually // close the connections) - if (conmgr.isRunning()) { - conmgr.shutdown(); + if (_conmgr.isRunning()) { + _conmgr.shutdown(); } // finally shut down the invoker and dobj manager (The invoker does both for us.) @@ -395,22 +257,39 @@ public class PresentsServer { } - /** Our interval that generates "state of server" reports. */ - protected Interval _reportInterval; + /** Integrates the main invoker thread with the distributed object thread so that they can + * coordinate the shutdown process to ensure that all (barring infinite loops) invoker units + * and distributed object events are processed before the server shuts down. */ + protected static class ServerInvoker extends PresentsInvoker + { + @Inject public ServerInvoker (PresentsDObjectMgr omgr, ReportManager repmgr) { + super(omgr, repmgr); + } + @Override protected void didShutdown () { + _server.invokerDidShutdown(); + } + @Inject protected PresentsServer _server; + } - /** The time at which the server was started. */ - protected static long _serverStartTime = System.currentTimeMillis(); + /** The manager of distributed objects. */ + @Inject protected PresentsDObjectMgr _omgr; - /** The last time at which {@link #generateReport} was run. */ - protected static long _lastReportStamp = _serverStartTime; + /** The manager of network connections. */ + @Inject protected ConnectionManager _conmgr; - /** Used to generate "state of server" reports. */ - protected static ArrayList _reporters = new ArrayList(); + /** The manager of clients. */ + @Inject protected ClientManager _clmgr; - /** A list of shutdown participants. */ - protected static ObserverList _downers = - new ObserverList(ObserverList.SAFE_IN_ORDER_NOTIFY); + /** The manager of invocation services. */ + @Inject protected InvocationManager _invmgr; - /** The frequency with which we generate "state of server" reports. */ - protected static final long REPORT_INTERVAL = 15 * 60 * 1000L; + /** Handles orderly shutdown of our managers, etc. */ + @Inject protected ShutdownManager _shutmgr; + + /** Handles generation of state of the server reports. */ + @Inject protected ReportManager _repmgr; + + /** 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; } diff --git a/src/java/com/threerings/presents/server/Rejector.java b/src/java/com/threerings/presents/server/Rejector.java index 991d90316..e2d406f48 100644 --- a/src/java/com/threerings/presents/server/Rejector.java +++ b/src/java/com/threerings/presents/server/Rejector.java @@ -21,6 +21,9 @@ package com.threerings.presents.server; +import com.google.inject.Guice; +import com.google.inject.Injector; + import com.samskivert.io.PersistenceException; import com.samskivert.util.Invoker; @@ -39,12 +42,12 @@ import static com.threerings.presents.Log.log; */ public class Rejector extends PresentsServer { - // documentation inherited - public void init () + @Override // from PresentsServer + public void init (Injector injector) throws Exception { - super.init(); - conmgr.setAuthenticator(new RejectingAuthenticator()); + super.init(injector); + _conmgr.setAuthenticator(new RejectingAuthenticator()); } // documentation inherited @@ -68,9 +71,10 @@ public class Rejector extends PresentsServer _errmsg = MessageBundle.tcompose(_errmsg, eargs); } - Rejector server = new Rejector(); + Injector injector = Guice.createInjector(new Module()); + Rejector server = injector.getInstance(Rejector.class); try { - server.init(); + server.init(injector); server.run(); } catch (Exception e) { log.warning("Unable to initialize server.", e); diff --git a/src/java/com/threerings/presents/server/ReportManager.java b/src/java/com/threerings/presents/server/ReportManager.java new file mode 100644 index 000000000..a939c8785 --- /dev/null +++ b/src/java/com/threerings/presents/server/ReportManager.java @@ -0,0 +1,167 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2008 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.List; + +import com.google.common.collect.Lists; +import com.google.inject.Singleton; + +import com.samskivert.util.Interval; +import com.samskivert.util.RunQueue; +import com.samskivert.util.StringUtil; + +import com.threerings.presents.annotation.EventQueue; + +import static com.threerings.presents.Log.log; + +/** + * Handles the generation of server status reports. + */ +@Singleton +public class ReportManager +{ + /** 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); + } + + /** + * Starts up our periodic report generation task on the supplied run queue. + */ + public void init (RunQueue rqueue) + { + // queue up an interval which will generate reports + _reportInterval = new Interval(rqueue) { + public void expired () { + logReport(generateReport(System.currentTimeMillis(), true)); + } + }; + _reportInterval.schedule(REPORT_INTERVAL, true); + } + + /** + * A report is generated by the presents server periodically in which server entities can + * participate by registering a {@link Reporter} with this method. + */ + public void registerReporter (Reporter reporter) + { + _reporters.add(reporter); + } + + /** + * Generates a report for all system services registered as a {@link Reporter}. + */ + public String generateReport () + { + return generateReport(System.currentTimeMillis(), false); + } + + /** + * Generates and logs a "state of server" report. + */ + protected 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.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); + } + + /** Our interval that generates "state of server" reports. */ + protected Interval _reportInterval; + + /** The time at which the server was started. */ + protected long _serverStartTime = System.currentTimeMillis(); + + /** The last time at which {@link #generateReport} was run. */ + protected long _lastReportStamp = _serverStartTime; + + /** Used to generate "state of server" reports. */ + protected List _reporters = Lists.newArrayList(); + + /** The frequency with which we generate "state of server" reports. */ + protected static final long REPORT_INTERVAL = 15 * 60 * 1000L; +} diff --git a/src/java/com/threerings/presents/server/ShutdownManager.java b/src/java/com/threerings/presents/server/ShutdownManager.java new file mode 100644 index 000000000..95e8334f6 --- /dev/null +++ b/src/java/com/threerings/presents/server/ShutdownManager.java @@ -0,0 +1,81 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2008 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.google.inject.Singleton; + +import com.samskivert.util.ObserverList; + +import static com.threerings.presents.Log.log; + +/** + * Handles the orderly shutdown of all server services. + */ +@Singleton +public class ShutdownManager +{ + /** 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 (); + } + + /** + * Registers an entity that will be notified when the server is shutting down. + */ + public void registerShutdowner (Shutdowner downer) + { + _downers.add(downer); + } + + /** + * Unregisters the shutdowner from hearing when the server is shutdown. + */ + public void unregisterShutdowner (Shutdowner downer) + { + _downers.remove(downer); + } + + public void shutdown () + { + ObserverList downers = _downers; + if (downers == null) { + log.warning("Refusing repeat shutdown request."); + return; + } + _downers = null; + + // shut down all shutdown participants + downers.apply(new ObserverList.ObserverOp() { + public boolean apply (Shutdowner downer) { + downer.shutdown(); + return true; + } + }); + } + + /** A list of shutdown participants. */ + protected static ObserverList _downers = ObserverList.newSafeInOrder(); +} diff --git a/src/java/com/threerings/presents/server/net/ConnectionManager.java b/src/java/com/threerings/presents/server/net/ConnectionManager.java index 18ae1a383..55d194719 100644 --- a/src/java/com/threerings/presents/server/net/ConnectionManager.java +++ b/src/java/com/threerings/presents/server/net/ConnectionManager.java @@ -32,15 +32,27 @@ import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.nio.channels.spi.SelectorProvider; +import java.util.ArrayList; +import java.util.List; import java.net.InetSocketAddress; -import java.util.ArrayList; -import java.util.HashMap; import java.util.Iterator; +import java.util.Map; import java.util.Set; -import com.samskivert.util.*; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +import com.samskivert.util.IntMap; +import com.samskivert.util.IntMaps; +import com.samskivert.util.LoopingThread; +import com.samskivert.util.Queue; +import com.samskivert.util.ResultListener; +import com.samskivert.util.StringUtil; +import com.samskivert.util.Tuple; import com.threerings.io.FramingOutputStream; import com.threerings.io.ObjectOutputStream; @@ -53,7 +65,8 @@ import com.threerings.presents.net.DownstreamMessage; import com.threerings.presents.util.DatagramSequencer; import com.threerings.presents.server.Authenticator; -import com.threerings.presents.server.PresentsServer; +import com.threerings.presents.server.PresentsDObjectMgr; +import com.threerings.presents.server.ReportManager; import static com.threerings.presents.Log.log; @@ -63,38 +76,36 @@ import static com.threerings.presents.Log.log; * closely with the connection manager because network I/O is done via a poll()-like mechanism * rather than via threads. */ +@Singleton public class ConnectionManager extends LoopingThread - implements PresentsServer.Reporter + implements ReportManager.Reporter { /** - * Constructs and initialized a connection manager (binding the socket on which it will listen - * for client connections). + * Creates a connection manager instance. Don't call this, Guice will do it for you. */ - public ConnectionManager (int port) - throws IOException - { - this(new int[] { port }); - } - - /** - * Constructs and initialized a connection manager (binding socket on which it will listen for - * client connections to each of the specified ports). - */ - public ConnectionManager (int[] ports) - throws IOException - { - this(ports, new int[0]); - } - - /** - * Constructs and initialized a connection manager (binding socket on which it will listen for - * client connections to each of the specified ports). - */ - public ConnectionManager (int[] ports, int[] datagramPorts) - throws IOException + @Inject public ConnectionManager (ReportManager repmgr) { super("ConnectionManager"); + repmgr.registerReporter(this); + } + /** + * Constructs and initialized a connection manager (binding socket on which it will listen for + * client connections to each of the specified ports). + */ + public void init (int[] ports) + throws IOException + { + init(ports, new int[0]); + } + + /** + * Constructs and initialized a connection manager (binding socket on which it will listen for + * client connections to each of the specified ports). + */ + public void init (int[] ports, int[] datagramPorts) + throws IOException + { _ports = ports; _datagramPorts = datagramPorts; _selector = SelectorProvider.provider().openSelector(); @@ -102,9 +113,6 @@ public class ConnectionManager extends LoopingThread // create our stats record _stats = new ConMgrStats(); _lastStats = new ConMgrStats(); - - // register as a "state of server" reporter - PresentsServer.registerReporter(this); } /** @@ -207,7 +215,7 @@ public class ConnectionManager extends LoopingThread _authq.append(conn); } - // documentation inherited from interface PresentsServer.Reporter + // documentation inherited from interface ReportManager.Reporter public void appendReport ( StringBuilder report, long now, long sinceLast, boolean reset) { @@ -842,7 +850,7 @@ public class ConnectionManager extends LoopingThread } // more sanity check; messages must only be posted from the dobjmgr thread - if (!PresentsServer.omgr.isDispatchThread()) { + if (!_omgr.isDispatchThread()) { log.warning("Message posted on non-distributed object thread [conn=" + conn + ", msg=" + msg + ", thread=" + Thread.currentThread() + "]."); Thread.dumpStack(); @@ -1029,6 +1037,15 @@ public class ConnectionManager extends LoopingThread protected int _msgs, _partials; } + /** Used to create an overflow queue on the first partial write. */ + protected PartialWriteHandler _oflowHandler = new PartialWriteHandler() { + public void handlePartialWrite (Connection conn, ByteBuffer msgbuf) { + // if we couldn't write all the data for this message, we'll need to establish an + // overflow queue + _oflowqs.put(conn, new OverflowQueue(conn, msgbuf)); + } + }; + protected int[] _ports, _datagramPorts; protected Authenticator _author; protected Selector _selector; @@ -1040,11 +1057,10 @@ public class ConnectionManager extends LoopingThread protected int _runtimeExceptionCount; /** Maps selection keys to network event handlers. */ - protected HashMap _handlers = - new HashMap(); + protected Map _handlers = Maps.newHashMap(); /** Connections mapped by identifier. */ - protected HashIntMap _connections = new HashIntMap(); + protected IntMap _connections = IntMaps.newHashIntMap(); protected Queue _deathq = new Queue(); protected Queue _authq = new Queue(); @@ -1056,9 +1072,9 @@ public class ConnectionManager extends LoopingThread protected ByteBuffer _outbuf = ByteBuffer.allocateDirect(64 * 1024); protected ByteBuffer _databuf = ByteBuffer.allocateDirect(Client.MAX_DATAGRAM_SIZE); - protected HashMap _oflowqs = new HashMap(); + protected Map _oflowqs = Maps.newHashMap(); - protected ArrayList _observers = new ArrayList(); + protected List _observers = Lists.newArrayList(); /** Bytes in and out in the last reporting period. */ protected long _bytesIn, _bytesOut; @@ -1075,14 +1091,8 @@ public class ConnectionManager extends LoopingThread /** A runnable to execute when the connection manager thread exits. */ protected volatile Runnable _onExit; - /** Used to create an overflow queue on the first partial write. */ - protected PartialWriteHandler _oflowHandler = new PartialWriteHandler() { - public void handlePartialWrite (Connection conn, ByteBuffer msgbuf) { - // if we couldn't write all the data for this message, we'll need to establish an - // overflow queue - _oflowqs.put(conn, new OverflowQueue(conn, msgbuf)); - } - }; + // injected dependencies + @Inject protected PresentsDObjectMgr _omgr; /** How long we wait for network events before checking our running flag to see if we should * still be running. We don't want to loop too tightly, but we need to make sure we don't sit diff --git a/tests/build.xml b/tests/build.xml index b05ea9182..dee197c2d 100644 --- a/tests/build.xml +++ b/tests/build.xml @@ -129,73 +129,8 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -233,7 +168,8 @@ - + - diff --git a/tests/src/java/com/threerings/bureau/server/RegistryTester.java b/tests/src/java/com/threerings/bureau/server/RegistryTester.java index 2583a5c63..b7586a7ed 100644 --- a/tests/src/java/com/threerings/bureau/server/RegistryTester.java +++ b/tests/src/java/com/threerings/bureau/server/RegistryTester.java @@ -25,9 +25,14 @@ import java.util.List; import java.util.Random; import com.google.common.collect.Lists; +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.Injector; import com.threerings.bureau.data.AgentObject; +import com.threerings.presents.server.ShutdownManager; + import static com.threerings.bureau.Log.log; /** @@ -59,11 +64,12 @@ public class RegistryTester */ public static void main (String[] args) { - TestServer server = new TestServer(); - RegistryTester tester = new RegistryTester(server); + Injector injector = Guice.createInjector(new TestServer.Module()); + TestServer server = injector.getInstance(TestServer.class); + RegistryTester tester = injector.getInstance(RegistryTester.class); try { - server.init(); + server.init(injector); tester.start(); server.run(); @@ -75,7 +81,7 @@ public class RegistryTester /** * Creates a new registry tester. */ - public RegistryTester (TestServer server) + @Inject public RegistryTester (TestServer server, ShutdownManager shutmgr) { _server = server; @@ -92,7 +98,7 @@ public class RegistryTester // stop the tests when the server shuts down // TODO: this is not called on Ctrl-C, need a way to shut down gracefully - TestServer.registerShutdowner(new TestServer.Shutdowner() { + shutmgr.registerShutdowner(new ShutdownManager.Shutdowner() { public void shutdown () { log.info("Shutting down tests"); _stop = true; diff --git a/tests/src/java/com/threerings/bureau/server/TestServer.java b/tests/src/java/com/threerings/bureau/server/TestServer.java index 029f3af2e..0ba3c438a 100644 --- a/tests/src/java/com/threerings/bureau/server/TestServer.java +++ b/tests/src/java/com/threerings/bureau/server/TestServer.java @@ -21,6 +21,9 @@ package com.threerings.bureau.server; +import com.google.inject.Guice; +import com.google.inject.Injector; + import com.threerings.presents.server.PresentsServer; import static com.threerings.bureau.Log.log; @@ -40,9 +43,10 @@ public class TestServer extends PresentsServer */ public static void main (String[] args) { - final TestServer server = new TestServer(); + Injector injector = Guice.createInjector(new Module()); + TestServer server = injector.getInstance(TestServer.class); try { - server.init(); + server.init(injector); setClientTarget("bureau-runclient"); server.run(); @@ -51,20 +55,20 @@ public class TestServer extends PresentsServer } } - // inherit documentation - from PresentsServer - public void init () + @Override // from PresentsServer + public void init (Injector injector) throws Exception { - super.init(); + super.init(injector); breg = new BureauRegistry("localhost:47624", invmgr, omgr, invoker); } - static public void setClientTarget (String target) + public static void setClientTarget (String target) { breg.setCommandGenerator("test", antCommandGenerator(target)); } - static public BureauRegistry.CommandGenerator antCommandGenerator ( + public static BureauRegistry.CommandGenerator antCommandGenerator ( final String target) { return new BureauRegistry.CommandGenerator() { diff --git a/tests/src/java/com/threerings/crowd/server/JabberServer.java b/tests/src/java/com/threerings/crowd/server/JabberServer.java index c3ca6756f..7e01e7b62 100644 --- a/tests/src/java/com/threerings/crowd/server/JabberServer.java +++ b/tests/src/java/com/threerings/crowd/server/JabberServer.java @@ -3,6 +3,9 @@ package com.threerings.crowd.server; +import com.google.inject.Guice; +import com.google.inject.Injector; + import com.threerings.crowd.data.JabberConfig; import com.threerings.crowd.data.PlaceObject; @@ -15,10 +18,10 @@ import static com.threerings.crowd.Log.log; public class JabberServer extends CrowdServer { // documentation inherited - public void init () + public void init (Injector injector) throws Exception { - super.init(); + super.init(injector); // create a single location _pmgr = plreg.createPlace(new JabberConfig()); @@ -26,9 +29,11 @@ public class JabberServer extends CrowdServer public static void main (String[] args) { - JabberServer server = new JabberServer(); + Injector injector = Guice.createInjector(new Module()); + JabberServer server = injector.getInstance(JabberServer.class); + try { - server.init(); + server.init(injector); server.run(); } catch (Exception e) { log.warning("Unable to initialize server.", e); diff --git a/tests/src/java/com/threerings/presents/server/DOMTest.java b/tests/src/java/com/threerings/presents/server/DOMTest.java index b2c44f336..5c2013ab1 100644 --- a/tests/src/java/com/threerings/presents/server/DOMTest.java +++ b/tests/src/java/com/threerings/presents/server/DOMTest.java @@ -21,43 +21,29 @@ package com.threerings.presents.server; -import junit.framework.Test; -import junit.framework.TestCase; +import org.junit.Test; +import org.junit.runner.RunWith; + +import atunit.AtUnit; +import atunit.Container; +import atunit.Unit; + +import com.google.inject.Inject; import com.threerings.presents.data.TestObject; import com.threerings.presents.dobj.*; +import static org.junit.Assert.*; + /** * A simple test case for the dobjmgr. */ -public class DOMTest extends TestCase +@RunWith(AtUnit.class) +@Container(Container.Option.GUICE) +public class DOMTest implements AttributeChangeListener, ElementUpdateListener { - public DOMTest () - { - super(DOMTest.class.getName()); - } - - public void attributeChanged (AttributeChangedEvent event) - { - assertTrue(fields[_fcount] + " == " + values[_fcount], - event.getName().equals(fields[_fcount]) && - event.getValue().equals(values[_fcount])); - - // shutdown once we receive our last update - if (++_fcount == fields.length) { - _omgr.harshShutdown(); - } - } - - public void elementUpdated (ElementUpdatedEvent event) - { -// Log.info("Element updated " + event); -// Log.info(StringUtil.toString(_test.ints)); -// Log.info(StringUtil.toString(_test.strings)); - } - - public void runTest () + @Test public void runTest () { // request that a new TestObject be registered _test = _omgr.registerObject(new TestObject()); @@ -87,27 +73,35 @@ public class DOMTest extends TestCase _omgr.run(); } - public static Test suite () + // from interface AttributeChangeListener + public void attributeChanged (AttributeChangedEvent event) { - return new DOMTest(); + assertTrue(fields[_fcount] + " == " + values[_fcount], + event.getName().equals(fields[_fcount]) && + event.getValue().equals(values[_fcount])); + + // shutdown once we receive our last update + if (++_fcount == fields.length) { + _omgr.harshShutdown(); + } } - public static void main (String[] args) + // from interface ElementUpdateListener + public void elementUpdated (ElementUpdatedEvent event) { - DOMTest test = new DOMTest(); - test.runTest(); +// Log.info("Element updated " + event); +// Log.info(StringUtil.toString(_test.ints)); +// Log.info(StringUtil.toString(_test.strings)); } protected int _fcount = 0; protected TestObject _test; // the fields that will change in attribute changed events - protected Object[] fields = { - TestObject.FOO, TestObject.BAR, TestObject.FOO, TestObject.BAR }; + protected Object[] fields = { TestObject.FOO, TestObject.BAR, TestObject.FOO, TestObject.BAR }; // the values we'll receive via attribute changed events - protected Object[] values = { - new Integer(99), "hoopie", new Integer(25), "howdy" }; + protected Object[] values = { new Integer(99), "hoopie", new Integer(25), "howdy" }; - protected static PresentsDObjectMgr _omgr = new PresentsDObjectMgr(); + @Inject @Unit protected PresentsDObjectMgr _omgr; } diff --git a/tests/src/java/com/threerings/presents/server/DestroyedRefTest.java b/tests/src/java/com/threerings/presents/server/DestroyedRefTest.java index b49d6115b..eb297dbad 100644 --- a/tests/src/java/com/threerings/presents/server/DestroyedRefTest.java +++ b/tests/src/java/com/threerings/presents/server/DestroyedRefTest.java @@ -21,60 +21,37 @@ package com.threerings.presents.server; -import junit.framework.Test; -import junit.framework.TestCase; +import org.junit.Test; +import org.junit.runner.RunWith; + +import atunit.AtUnit; +import atunit.Container; +import atunit.Unit; + +import com.google.inject.Inject; import com.threerings.presents.data.TestObject; import com.threerings.presents.dobj.*; +import static org.junit.Assert.*; + import static com.threerings.presents.Log.log; /** * Tests that the dobjmgr will not allow a destroyed object to be added to * an oid list. */ -public class DestroyedRefTest extends TestCase - implements EventListener +@RunWith(AtUnit.class) +@Container(Container.Option.GUICE) +public class DestroyedRefTest { - public static Test suite () - { - return new DestroyedRefTest(); - } - - public DestroyedRefTest () - { - super(DestroyedRefTest.class.getName()); - } - - public void eventReceived (DEvent event) - { - int toid = event.getTargetOid(); - - // when we get the attribute change, we can exit - if (event instanceof ObjectDestroyedEvent) { - log.info("The upcoming object added event should be rejected."); - - } else if (event instanceof ObjectAddedEvent && - toid == _objtwo.getOid()) { - assertTrue("list should contain only one oid", - _objtwo.list.size() == 1); - - } else if (event instanceof AttributeChangedEvent) { - // go bye bye - _omgr.harshShutdown(); - - } else { - fail("Got unexpected event: " + event); - } - } - - public void runTest () + @Test public void runTest () { // create two test objects _objone = _omgr.registerObject(new TestObject()); - _objone.addListener(this); + _objone.addListener(_listener); _objtwo = _omgr.registerObject(new TestObject()); - _objtwo.addListener(this); + _objtwo.addListener(_listener); // add object one to object two twice in a row to make sure repeated // adds don't result in the object being listed twice @@ -95,7 +72,30 @@ public class DestroyedRefTest extends TestCase _omgr.run(); } + protected EventListener _listener = new EventListener() { + public void eventReceived (DEvent event) { + int toid = event.getTargetOid(); + + // when we get the attribute change, we can exit + if (event instanceof ObjectDestroyedEvent) { + log.info("The upcoming object added event should be rejected."); + + } else if (event instanceof ObjectAddedEvent && + toid == _objtwo.getOid()) { + assertTrue("list should contain only one oid", + _objtwo.list.size() == 1); + + } else if (event instanceof AttributeChangedEvent) { + // go bye bye + _omgr.harshShutdown(); + + } else { + fail("Got unexpected event: " + event); + } + } + }; + protected TestObject _objone, _objtwo; - protected static PresentsDObjectMgr _omgr = new PresentsDObjectMgr(); + @Inject @Unit protected PresentsDObjectMgr _omgr; } diff --git a/tests/src/java/com/threerings/presents/server/RefTest.java b/tests/src/java/com/threerings/presents/server/RefTest.java index 250c07e8c..f5706ddfa 100644 --- a/tests/src/java/com/threerings/presents/server/RefTest.java +++ b/tests/src/java/com/threerings/presents/server/RefTest.java @@ -21,65 +21,34 @@ package com.threerings.presents.server; -import junit.framework.Test; -import junit.framework.TestCase; +import org.junit.Test; +import org.junit.runner.RunWith; + +import atunit.AtUnit; +import atunit.Container; +import atunit.Unit; + +import com.google.inject.Inject; import com.threerings.presents.data.TestObject; import com.threerings.presents.dobj.*; +import static org.junit.Assert.*; + /** * Tests the oid list reference tracking code. */ -public class RefTest extends TestCase - implements EventListener +@RunWith(AtUnit.class) +@Container(Container.Option.GUICE) +public class RefTest { - public static Test suite () - { - return new RefTest(); - } - - public RefTest () - { - super(RefTest.class.getName()); - } - - public void eventReceived (DEvent event) - { - // Log.info("Got event: " + event); - int toid = event.getTargetOid(); - - // once we receive the second object added we can destroy the - // target object to see if the reference is cleaned up - if (event instanceof ObjectAddedEvent && - toid == _objtwo.getOid()) { - // Log.info("Destroying object two " + _objtwo + "."); - _objtwo.destroy(); - - } else if (event instanceof ObjectDestroyedEvent) { - if (toid == _objtwo.getOid()) { - // Log.info("List won't yet be empty: " + _objone.list); - assertTrue("List not empty", _objone.list.size() > 0); - } else { - // Log.info("Other object destroyed."); - // go bye bye - _omgr.harshShutdown(); - } - - } else if (event instanceof ObjectRemovedEvent) { - // Log.info("List should be empty: " + _objone.list); - assertTrue("List empty", _objone.list.size() == 0); - // finally destroy the other object to complete the circle - _objone.destroy(); - } - } - - public void runTest () + @Test public void runTest () { // create two test objects _objone = _omgr.registerObject(new TestObject()); - _objone.addListener(this); + _objone.addListener(_listener); _objtwo = _omgr.registerObject(new TestObject()); - _objtwo.addListener(this); + _objtwo.addListener(_listener); // now that we have both objects, set up the references _objone.addToList(_objtwo.getOid()); @@ -89,8 +58,39 @@ public class RefTest extends TestCase _omgr.run(); } + protected EventListener _listener = new EventListener() { + public void eventReceived (DEvent event) { + // Log.info("Got event: " + event); + int toid = event.getTargetOid(); + + // once we receive the second object added we can destroy the + // target object to see if the reference is cleaned up + if (event instanceof ObjectAddedEvent && + toid == _objtwo.getOid()) { + // Log.info("Destroying object two " + _objtwo + "."); + _objtwo.destroy(); + + } else if (event instanceof ObjectDestroyedEvent) { + if (toid == _objtwo.getOid()) { + // Log.info("List won't yet be empty: " + _objone.list); + assertTrue("List not empty", _objone.list.size() > 0); + } else { + // Log.info("Other object destroyed."); + // go bye bye + _omgr.harshShutdown(); + } + + } else if (event instanceof ObjectRemovedEvent) { + // Log.info("List should be empty: " + _objone.list); + assertTrue("List empty", _objone.list.size() == 0); + // finally destroy the other object to complete the circle + _objone.destroy(); + } + } + }; + protected TestObject _objone; protected TestObject _objtwo; - protected static PresentsDObjectMgr _omgr = new PresentsDObjectMgr(); + @Inject @Unit protected PresentsDObjectMgr _omgr; } diff --git a/tests/src/java/com/threerings/presents/server/TestServer.java b/tests/src/java/com/threerings/presents/server/TestServer.java index b8e673581..332b0cc6f 100644 --- a/tests/src/java/com/threerings/presents/server/TestServer.java +++ b/tests/src/java/com/threerings/presents/server/TestServer.java @@ -21,6 +21,9 @@ package com.threerings.presents.server; +import com.google.inject.Guice; +import com.google.inject.Injector; + import com.threerings.presents.data.TestObject; import com.threerings.presents.dobj.*; @@ -30,13 +33,13 @@ public class TestServer extends PresentsServer { public static TestObject testobj; - public void init () + public void init (Injector injector) throws Exception { - super.init(); + super.init(injector); // register our test provider - invmgr.registerDispatcher(new TestDispatcher(new TestManager()), "test"); + _invmgr.registerDispatcher(new TestDispatcher(new TestManager()), "test"); // create a test object testobj = omgr.registerObject(new TestObject()); @@ -48,9 +51,10 @@ public class TestServer extends PresentsServer public static void main (String[] args) { - TestServer server = new TestServer(); + Injector injector = Guice.createInjector(new Module()); + TestServer server = injector.getInstance(TestServer.class); try { - server.init(); + server.init(injector); server.run(); } catch (Exception e) { log.warning("Unable to initialize server.", e);