Holy fruity beverage Batman! I'm taking the plunge and switching to Guice for

dependency resolution and management. Things are currently in a state of
transition, but the important patterns have been established and I'll aim to
sweep through all of Narya, Nenya and Vilya in the near future and do
everything properly.

Going through the other million-odd lines of code we have scattered across our
various projects that use these libraries is not something I plan to do, so
we'll be maintaining backward compatibility with the old static member method,
though I hope to strive toward eradication of that usage entirely in MSOY while
we still have a fighting chance.

Given that some of our projects will continue to use the static member method
in perpetuity, I'm not going to @Deprecate those fields because that would fill
their logs with so much spam that they'd have to turn off deprecation warnings
which would make life worse for them. So instead we'll settle for the big scary
comment warning people away from the old bits.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@5153 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2008-06-07 17:44:48 +00:00
parent 443ad8e139
commit 3aefc93838
25 changed files with 1090 additions and 842 deletions
+2
View File
@@ -3,11 +3,13 @@
<project name="library-dependencies"> <project name="library-dependencies">
<fileset dir="${libs.dir}" id="narya.libs"> <fileset dir="${libs.dir}" id="narya.libs">
<include name="ant.jar"/> <include name="ant.jar"/>
<include name="atunit.jar"/>
<include name="asc.jar"/> <include name="asc.jar"/>
<include name="flexTasks.jar"/> <include name="flexTasks.jar"/>
<include name="commons-collections.jar"/> <include name="commons-collections.jar"/>
<include name="commons-io.jar"/> <include name="commons-io.jar"/>
<include name="commons-lang.jar"/> <include name="commons-lang.jar"/>
<include name="guice.jar"/>
<include name="google-collect.jar"/> <include name="google-collect.jar"/>
<include name="javassist.jar"/> <include name="javassist.jar"/>
<include name="junit4.jar"/> <include name="junit4.jar"/>
@@ -21,11 +21,14 @@
package com.threerings.crowd.peer.server; package com.threerings.crowd.peer.server;
import com.google.inject.Inject;
import com.threerings.util.Name; import com.threerings.util.Name;
import com.threerings.presents.data.ClientObject; import com.threerings.presents.data.ClientObject;
import com.threerings.presents.server.InvocationException; import com.threerings.presents.server.InvocationException;
import com.threerings.presents.server.PresentsClient; 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.ClientInfo;
import com.threerings.presents.peer.data.NodeObject; import com.threerings.presents.peer.data.NodeObject;
@@ -49,6 +52,14 @@ import com.threerings.crowd.peer.data.CrowdPeerMarshaller;
public class CrowdPeerManager extends PeerManager public class CrowdPeerManager extends PeerManager
implements CrowdPeerProvider, ChatProvider.ChatForwarder implements CrowdPeerProvider, ChatProvider.ChatForwarder
{ {
/**
* Creates an uninitialized peer manager.
*/
@Inject public CrowdPeerManager (ShutdownManager shutmgr)
{
super(shutmgr);
}
// from interface CrowdPeerProvider // from interface CrowdPeerProvider
public void deliverTell (ClientObject caller, UserMessage message, public void deliverTell (ClientObject caller, UserMessage message,
Name target, ChatService.TellListener listener) Name target, ChatService.TellListener listener)
@@ -23,6 +23,9 @@ package com.threerings.crowd.server;
import java.util.Iterator; import java.util.Iterator;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.threerings.util.Name; import com.threerings.util.Name;
import com.threerings.presents.dobj.RootDObjectManager; import com.threerings.presents.dobj.RootDObjectManager;
@@ -44,6 +47,15 @@ import static com.threerings.crowd.Log.log;
*/ */
public class CrowdServer extends PresentsServer 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. */ /** The place registry. */
public static PlaceRegistry plreg; public static PlaceRegistry plreg;
@@ -53,11 +65,10 @@ public class CrowdServer extends PresentsServer
/** /**
* Initializes all of the server services and prepares for operation. * Initializes all of the server services and prepares for operation.
*/ */
public void init () public void init (Injector injector)
throws Exception throws Exception
{ {
// do the presents server initialization super.init(injector);
super.init();
// configure the client manager to use our bits // configure the client manager to use our bits
clmgr.setClientFactory(new ClientFactory() { clmgr.setClientFactory(new ClientFactory() {
@@ -139,9 +150,10 @@ public class CrowdServer extends PresentsServer
public static void main (String[] args) public static void main (String[] args)
{ {
CrowdServer server = new CrowdServer(); Injector injector = Guice.createInjector(new Module());
CrowdServer server = injector.getInstance(CrowdServer.class);
try { try {
server.init(); server.init(injector);
server.run(); server.run();
} catch (Exception e) { } catch (Exception e) {
log.warning("Unable to initialize server.", e); log.warning("Unable to initialize server.", e);
@@ -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:
*
* <code>@Inject @EventQueue RunQueue _dobjq;</code>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@BindingAnnotation
public @interface EventQueue
{
}
@@ -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:
*
* <code>@Inject @MainInvoker Invoker _invoker;</code>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@BindingAnnotation
public @interface MainInvoker
{
}
@@ -32,6 +32,10 @@ import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; 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.RepositoryUnit;
import com.samskivert.jdbc.WriteOnlyUnit; import com.samskivert.jdbc.WriteOnlyUnit;
import com.samskivert.jdbc.depot.PersistenceContext; import com.samskivert.jdbc.depot.PersistenceContext;
@@ -49,7 +53,6 @@ import com.samskivert.util.Tuple;
import com.threerings.io.Streamable; import com.threerings.io.Streamable;
import com.threerings.util.Name; import com.threerings.util.Name;
import com.threerings.presents.client.Client;
import com.threerings.presents.data.ClientObject; import com.threerings.presents.data.ClientObject;
import com.threerings.presents.dobj.DObject; import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.EntryAddedEvent; 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.ObjectAccessException;
import com.threerings.presents.dobj.SetListener; import com.threerings.presents.dobj.SetListener;
import com.threerings.presents.dobj.Subscriber; import com.threerings.presents.dobj.Subscriber;
import com.threerings.presents.client.Client;
import com.threerings.presents.server.ClientManager; import com.threerings.presents.server.ClientManager;
import com.threerings.presents.server.InvocationException; import com.threerings.presents.server.InvocationException;
import com.threerings.presents.server.InvocationManager;
import com.threerings.presents.server.PresentsClient; 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.ClientInfo;
import com.threerings.presents.peer.data.NodeObject; 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 * 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. * servers and uses those objects to communicate cross-node information.
*/ */
@Singleton
public class PeerManager 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 * 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 (); 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. * Returns the distributed object that represents this node to its peers.
*/ */
@@ -202,14 +219,11 @@ public class PeerManager
_sharedSecret = sharedSecret; _sharedSecret = sharedSecret;
// wire ourselves into the server // wire ourselves into the server
PresentsServer.registerShutdowner(this); _conmgr.setAuthenticator(new PeerAuthenticator(this, _conmgr.getAuthenticator()));
PresentsServer.conmgr.setAuthenticator( _clmgr.setClientFactory(new PeerClientFactory(this, _clmgr.getClientFactory()));
new PeerAuthenticator(this, PresentsServer.conmgr.getAuthenticator()));
PresentsServer.clmgr.setClientFactory(
new PeerClientFactory(this, PresentsServer.clmgr.getClientFactory()));
// create our node object // create our node object
_nodeobj = PresentsServer.omgr.registerObject(createNodeObject()); _nodeobj = _omgr.registerObject(createNodeObject());
_nodeobj.setNodeName(nodeName); _nodeobj.setNodeName(nodeName);
// register ourselves with the node table // register ourselves with the node table
@@ -223,10 +237,10 @@ public class PeerManager
// set the invocation service // set the invocation service
_nodeobj.setPeerService( _nodeobj.setPeerService(
(PeerMarshaller)PresentsServer.invmgr.registerDispatcher(new PeerDispatcher(this))); (PeerMarshaller)_invmgr.registerDispatcher(new PeerDispatcher(this)));
// register ourselves as a client observer // register ourselves as a client observer
PresentsServer.clmgr.addClientObserver(this); _clmgr.addClientObserver(this);
// and start our peer refresh interval // and start our peer refresh interval
_peerRefresher.schedule(5000L, 60*1000L); _peerRefresher.schedule(5000L, 60*1000L);
@@ -317,8 +331,8 @@ public class PeerManager
public void invokeNodeAction (final NodeAction action) public void invokeNodeAction (final NodeAction action)
{ {
// if we're not on the dobjmgr thread, get there // if we're not on the dobjmgr thread, get there
if (!PresentsServer.omgr.isDispatchThread()) { if (!_omgr.isDispatchThread()) {
PresentsServer.omgr.postRunnable(new Runnable() { _omgr.postRunnable(new Runnable() {
public void run () { public void run () {
invokeNodeAction(action); invokeNodeAction(action);
} }
@@ -384,7 +398,7 @@ public class PeerManager
// make a note of this proxy mapping // make a note of this proxy mapping
_proxies.put(key, new Tuple<Subscriber<?>,DObject>(this, object)); _proxies.put(key, new Tuple<Subscriber<?>,DObject>(this, object));
// map the object into our local oid space // 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 // then tell the caller about the (now remapped) oid
listener.requestCompleted(object.getOid()); listener.requestCompleted(object.getOid());
} }
@@ -408,7 +422,7 @@ public class PeerManager
} }
// clear out the local object manager's proxy mapping // 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); final Client peer = getPeerClient(nodeName);
if (peer == null) { if (peer == null) {
@@ -681,19 +695,19 @@ public class PeerManager
_nodeobj.setCacheData(new NodeObject.CacheData(cache, data)); _nodeobj.setCacheData(new NodeObject.CacheData(cache, data));
} }
// from interface PresentsServer.Shutdowner // from interface ShutdownManager.Shutdowner
public void shutdown () public void shutdown ()
{ {
// clear out our invocation service // clear out our invocation service
if (_nodeobj != null) { if (_nodeobj != null) {
PresentsServer.invmgr.clearDispatcher(_nodeobj.peerService); _invmgr.clearDispatcher(_nodeobj.peerService);
} }
// stop our peer refresher interval // stop our peer refresher interval
_peerRefresher.cancel(); _peerRefresher.cancel();
// clear out our client observer registration // clear out our client observer registration
PresentsServer.clmgr.removeClientObserver(this); _clmgr.removeClientObserver(this);
// clear our record from the node table // clear our record from the node table
_invoker.postUnit(new WriteOnlyUnit("deleteNode(" + _nodeName + ")") { _invoker.postUnit(new WriteOnlyUnit("deleteNode(" + _nodeName + ")") {
@@ -1004,7 +1018,7 @@ public class PeerManager
_remoids = (ArrayIntSet)_suboids.clone(); _remoids = (ArrayIntSet)_suboids.clone();
// schedule a timeout to act if something goes wrong // schedule a timeout to act if something goes wrong
(_timeout = new Interval(PresentsServer.omgr) { (_timeout = new Interval(_omgr) {
public void expired () { public void expired () {
log.warning("Lock handler timed out, acting anyway [lock=" + _lock + log.warning("Lock handler timed out, acting anyway [lock=" + _lock +
", acquire=" + _acquire + "]."); ", acquire=" + _acquire + "].");
@@ -1198,19 +1212,23 @@ public class PeerManager
/** Contains a mapping of proxied objects to subscriber instances. */ /** Contains a mapping of proxied objects to subscriber instances. */
protected HashMap<Tuple<String,Integer>,Tuple<Subscriber<?>,DObject>> _proxies = protected HashMap<Tuple<String,Integer>,Tuple<Subscriber<?>,DObject>> _proxies =
new HashMap<Tuple<String,Integer>,Tuple<Subscriber<?>,DObject>>(); Maps.newHashMap();
/** Our stale cache observers. */ /** Our stale cache observers. */
protected HashMap<String, ObserverList<StaleCacheObserver>> _cacheobs = protected HashMap<String, ObserverList<StaleCacheObserver>> _cacheobs = Maps.newHashMap();
new HashMap<String, ObserverList<StaleCacheObserver>>();
/** Listeners for dropped locks. */ /** Listeners for dropped locks. */
protected ObserverList<DroppedLockObserver> _dropobs = protected ObserverList<DroppedLockObserver> _dropobs =
new ObserverList<DroppedLockObserver>(ObserverList.FAST_UNSAFE_NOTIFY); new ObserverList<DroppedLockObserver>(ObserverList.FAST_UNSAFE_NOTIFY);
/** Locks in the process of resolution. */ /** Locks in the process of resolution. */
protected HashMap<NodeObject.Lock, LockHandler> _locks = protected HashMap<NodeObject.Lock, LockHandler> _locks = Maps.newHashMap();
new HashMap<NodeObject.Lock, LockHandler>();
// 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. */ /** We wait this long for peer ratification to complete before acquiring/releasing the lock. */
protected static final long LOCK_TIMEOUT = 5000L; protected static final long LOCK_TIMEOUT = 5000L;
@@ -21,6 +21,8 @@
package com.threerings.presents.server; package com.threerings.presents.server;
import com.google.inject.Inject;
import static com.threerings.presents.Log.log; import static com.threerings.presents.Log.log;
/** /**
@@ -28,9 +30,11 @@ import static com.threerings.presents.Log.log;
*/ */
public abstract class AbstractSignalHandler public abstract class AbstractSignalHandler
{ {
public boolean init (PresentsServer server) /**
* Initializes this signal handler.
*/
public boolean init ()
{ {
_server = server;
return registerHandlers(); return registerHandlers();
} }
@@ -65,8 +69,9 @@ public abstract class AbstractSignalHandler
*/ */
protected void hupReceived () protected void hupReceived ()
{ {
log.info(PresentsServer.generateReport()); log.info(_repmgr.generateReport());
} }
protected PresentsServer _server; @Inject protected PresentsServer _server;
@Inject protected ReportManager _repmgr;
} }
@@ -22,9 +22,14 @@
package com.threerings.presents.server; package com.threerings.presents.server;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator; 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.Interval;
import com.samskivert.util.ObserverList; 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 * 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). * clients are given the boot for application-defined reasons).
*/ */
@Singleton
public class ClientManager public class ClientManager
implements ConnectionObserver, ClientResolutionListener, implements ConnectionObserver, ClientResolutionListener,
PresentsServer.Reporter, PresentsServer.Shutdowner ReportManager.Reporter, ShutdownManager.Shutdowner
{ {
/** /**
* Used by {@link #applyToClient}. * Used by {@link #applyToClient}.
@@ -89,25 +95,26 @@ public class ClientManager
/** /**
* Constructs a client manager that will interact with the supplied connection manager. * 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 // register ourselves as a connection observer
conmgr.addConnectionObserver(this); conmgr.addConnectionObserver(this);
// start up an interval that will check for expired clients and flush them from the bowels // start up an interval that will check for expired clients and flush them from the bowels
// of the server // of the server
new Interval(PresentsServer.omgr) { new Interval(_omgr) {
public void expired () { public void expired () {
flushClients(); flushClients();
} }
}.schedule(CLIENT_FLUSH_INTERVAL, true); }.schedule(CLIENT_FLUSH_INTERVAL, true);
// register as a "state of server" reporter and a shutdowner // register as a "state of server" reporter and a shutdowner
PresentsServer.registerReporter(this); repmgr.registerReporter(this);
PresentsServer.registerShutdowner(this); shutmgr.registerShutdowner(this);
} }
// documentation inherited from interface // from interface ShutdownManager.Shutdowner
public void shutdown () public void shutdown ()
{ {
log.info("Client manager shutting down [ccount=" + _usermap.size() + "]."); 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 // 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 // behavior to the case where we actually have to do the resolution
clobj.reference(); clobj.reference();
PresentsServer.omgr.postRunnable(new Runnable() { _omgr.postRunnable(new Runnable() {
public void run () { public void run () {
listener.clientResolved(username, clobj); 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 // 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 // need to do this on the dobjmgr thread since we're registering an object
final ClientResolver fclr = clr; final ClientResolver fclr = clr;
PresentsServer.omgr.postRunnable(new Runnable() { _omgr.postRunnable(new Runnable() {
public void run () { public void run () {
ClientObject clobj = fclr.createClientObject(); ClientObject clobj = fclr.createClientObject();
clobj.setPermissionPolicy(fclr.createPermissionPolicy()); 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); _objmap.remove(username);
// and destroy the object itself // 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) public void appendReport (StringBuilder report, long now, long sinceLast, boolean reset)
{ {
report.append("* presents.ClientManager:\n"); report.append("* presents.ClientManager:\n");
@@ -477,7 +484,7 @@ public class ClientManager
*/ */
protected void flushClients () protected void flushClients ()
{ {
ArrayList<PresentsClient> victims = new ArrayList<PresentsClient>(); List<PresentsClient> victims = Lists.newArrayList();
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
// first build a list of our victims // first build a list of our victims
@@ -530,23 +537,25 @@ public class ClientManager
} }
/** A mapping from auth username to client instances. */ /** A mapping from auth username to client instances. */
protected HashMap<Name,PresentsClient> _usermap = new HashMap<Name,PresentsClient>(); protected Map<Name,PresentsClient> _usermap = Maps.newHashMap();
/** A mapping from connections to client instances. */ /** A mapping from connections to client instances. */
protected HashMap<Connection,PresentsClient> _conmap = new HashMap<Connection,PresentsClient>(); protected Map<Connection,PresentsClient> _conmap = Maps.newHashMap();
/** A mapping from usernames to client object instances. */ /** A mapping from usernames to client object instances. */
protected HashMap<Name,ClientObject> _objmap = new HashMap<Name,ClientObject>(); protected Map<Name,ClientObject> _objmap = Maps.newHashMap();
/** A mapping of pending client resolvers. */ /** A mapping of pending client resolvers. */
protected HashMap<Name,ClientResolver> _penders = new HashMap<Name,ClientResolver>(); protected Map<Name,ClientResolver> _penders = Maps.newHashMap();
/** The client class in use. */ /** The client class in use. */
protected ClientFactory _factory = ClientFactory.DEFAULT; protected ClientFactory _factory = ClientFactory.DEFAULT;
/** Tracks registered {@link ClientObserver}s. */ /** Tracks registered {@link ClientObserver}s. */
protected ObserverList<ClientObserver> _clobservers = protected ObserverList<ClientObserver> _clobservers = ObserverList.newSafeInOrder();
new ObserverList<ClientObserver>(ObserverList.SAFE_IN_ORDER_NOTIFY);
// our injected dependencies
@Inject protected PresentsDObjectMgr _omgr;
/** The frequency with which we check for expired clients. */ /** The frequency with which we check for expired clients. */
protected static final long CLIENT_FLUSH_INTERVAL = 60 * 1000L; protected static final long CLIENT_FLUSH_INTERVAL = 60 * 1000L;
@@ -21,9 +21,14 @@
package com.threerings.presents.server; 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.LRUHashMap;
import com.samskivert.util.StringUtil; 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.EventListener;
import com.threerings.presents.dobj.InvocationRequestEvent; import com.threerings.presents.dobj.InvocationRequestEvent;
import com.threerings.presents.dobj.ObjectAccessException; import com.threerings.presents.dobj.ObjectAccessException;
import com.threerings.presents.dobj.RootDObjectManager;
import com.threerings.presents.net.Transport; 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 * provides a mechanism by which responses and asynchronous notification invocations can be
* delivered to the client. * delivered to the client.
*/ */
@Singleton
public class InvocationManager public class InvocationManager
implements EventListener implements EventListener
{ {
@@ -70,16 +75,16 @@ public class InvocationManager
* operate its invocation services. Generally only one invocation manager should be operational * operate its invocation services. Generally only one invocation manager should be operational
* in a particular system. * in a particular system.
*/ */
public InvocationManager (RootDObjectManager omgr) @Inject public InvocationManager (PresentsDObjectMgr omgr)
{ {
_omgr = omgr; _omgr = omgr;
// create the object on which we'll listen for invocation requests // 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); invobj.addListener(this);
_invoid = invobj.getOid(); _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()); _recentRegServices.put(Integer.valueOf(invCode), marsh.getClass().getName());
// Log.info("Registered service [marsh=" + marsh + "]."); // log.info("Registered service [marsh=" + marsh + "].");
return marsh; return marsh;
} }
@@ -199,7 +204,7 @@ public class InvocationManager
// documentation inherited from interface // documentation inherited from interface
public void eventReceived (DEvent event) public void eventReceived (DEvent event)
{ {
// Log.info("Event received " + event + "."); // log.info("Event received " + event + ".");
if (event instanceof InvocationRequestEvent) { if (event instanceof InvocationRequestEvent) {
InvocationRequestEvent ire = (InvocationRequestEvent)event; 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 + // ", disp=" + disp + ", methId=" + methodId +
// ", args=" + StringUtil.toString(args) + "]."); // ", args=" + StringUtil.toString(args) + "].");
@@ -301,29 +306,24 @@ public class InvocationManager
return _invCode++; return _invCode++;
} }
// debugging action...
protected static final LRUHashMap<Integer,String> _recentRegServices =
new LRUHashMap<Integer,String>(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. */ /** The object id of the object on which we receive invocation service requests. */
protected int _invoid = -1; protected int _invoid = -1;
/** Used to generate monotonically increasing provider ids. */ /** Used to generate monotonically increasing provider ids. */
protected int _invCode; 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. */ /** A table of invocation dispatchers each mapped by a unique code. */
protected HashIntMap<InvocationDispatcher> _dispatchers = protected IntMap<InvocationDispatcher> _dispatchers = IntMaps.newHashIntMap();
new HashIntMap<InvocationDispatcher>();
/** A mapping from bootstrap group to lists of services that are to be provided to clients at /** Maps bootstrap group to lists of services to be provided to clients at boot time. */
* boot time. */ protected Map<String,StreamableArrayList<InvocationMarshaller>> _bootlists = Maps.newHashMap();
protected HashMap<String,StreamableArrayList<InvocationMarshaller>> _bootlists =
new HashMap<String,StreamableArrayList<InvocationMarshaller>>();
/** The text that is appended to the procedure name when automatically generating a failure // debugging action...
* response. */ protected final Map<Integer,String> _recentRegServices = new LRUHashMap<Integer,String>(10000);
/** The text appended to the procedure name when generating a failure response. */
protected static final String FAILED_SUFFIX = "Failed"; protected static final String FAILED_SUFFIX = "Failed";
} }
@@ -23,6 +23,7 @@ package com.threerings.presents.server;
import java.awt.EventQueue; import java.awt.EventQueue;
import com.google.inject.Inject;
import com.threerings.presents.dobj.DEvent; import com.threerings.presents.dobj.DEvent;
import com.threerings.presents.dobj.DObject; import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.DObjectManager; import com.threerings.presents.dobj.DObjectManager;
@@ -35,6 +36,14 @@ import com.threerings.presents.dobj.Subscriber;
*/ */
public class LocalDObjectMgr extends PresentsDObjectMgr 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 * 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. * sets the source oid of all events to properly identify them with the supplied client oid.
@@ -22,14 +22,17 @@
package com.threerings.presents.server; package com.threerings.presents.server;
import java.lang.reflect.*; import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; 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.AuditLogger;
import com.samskivert.util.HashIntMap;
import com.samskivert.util.Histogram; import com.samskivert.util.Histogram;
import com.samskivert.util.IntMap;
import com.samskivert.util.IntMaps;
import com.samskivert.util.Interval; import com.samskivert.util.Interval;
import com.samskivert.util.Invoker; import com.samskivert.util.Invoker;
import com.samskivert.util.Queue; 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 * 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. * manager has been requested to shut down.
*/ */
@Singleton
public class PresentsDObjectMgr 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 /** 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()}. . */ * period can be requested via {@link #getStats()}. . */
public static class Stats public static class Stats
{ {
@@ -77,17 +81,21 @@ public class PresentsDObjectMgr
/** /**
* Creates the dobjmgr and prepares it for operation. * 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 // create a dummy object to live as oid zero and use that for some internal event trickery
// trickery
DObject dummy = new DObject(); DObject dummy = new DObject();
dummy.setOid(0); dummy.setOid(0);
dummy.setManager(this); dummy.setManager(this);
_objects.put(0, new DObject()); _objects.put(0, new DObject());
// register ourselves as a state of server reporter // register ourselves as a state of server reporter and also tell the report manager that
PresentsServer.registerReporter(this); // 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; _defaultController = controller;
// switch all objects from the old default (null, usually) to the new default. // switch all objects from the old default (null, usually) to the new default.
for (Iterator itr = _objects.elements(); itr.hasNext(); ) { for (DObject obj : _objects.values()) {
DObject obj = (DObject) itr.next();
if (oldDefault == obj.getAccessController()) { if (oldDefault == obj.getAccessController()) {
obj.setAccessController(controller); obj.setAccessController(controller);
} }
@@ -289,6 +296,235 @@ public class PresentsDObjectMgr
log.info("DOMGR exited."); 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<String,UnitProfile> entry : _profiles.entrySet()) {
log.info("P: " + entry.getKey() + " => " + entry.getValue());
}
}
/**
* Called as a helper for <code>ObjectDestroyedEvent</code> 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 <code>ObjectAddedEvent</code> 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 <code>ObjectRemovedEvent</code> 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<String,UnitProfile> 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. * Processes a single unit from the queue.
*/ */
@@ -352,7 +588,7 @@ public class PresentsDObjectMgr
cname = StringUtil.shortClassName(ival); cname = StringUtil.shortClassName(ival);
} else if (unit instanceof InvocationRequestEvent) { } else if (unit instanceof InvocationRequestEvent) {
InvocationRequestEvent ire = (InvocationRequestEvent)unit; 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)" : cname = (c == null) ? "dobj.InvocationRequestEvent:(no longer registered)" :
StringUtil.shortClassName(c) + ":" + ire.getMethodId(); StringUtil.shortClassName(c) + ":" + ire.getMethodId();
} else { } else {
@@ -481,105 +717,6 @@ public class PresentsDObjectMgr
log.warning("Fatal error caused by '" + causer + "': " + error, error); 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<String,UnitProfile> entry : _profiles.entrySet()) {
log.info("P: " + entry.getKey() + " => " + entry.getValue());
}
}
/**
* Called as a helper for <code>ObjectDestroyedEvent</code> 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 <code>objectDestroyed</code>; clears out the tracking info for a reference by the * Called by <code>objectDestroyed</code>; clears out the tracking info for a reference by the
* supplied object to the specified oid via the specified field. * supplied object to the specified oid via the specified field.
@@ -615,111 +752,6 @@ public class PresentsDObjectMgr
} }
} }
/**
* Called as a helper for <code>ObjectAddedEvent</code> 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 <code>ObjectRemovedEvent</code> 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 () protected synchronized boolean isRunning ()
{ {
return _running; return _running;
@@ -735,28 +767,26 @@ public class PresentsDObjectMgr
return _nextOid; 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"); Class[] ptypes = new Class[] { DEvent.class, DObject.class };
int queueSize = _evqueue.size(); Method method;
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) { try {
report.append("- Unit profiles: ").append(_profiles.size()).append("\n"); method = PresentsDObjectMgr.class.getMethod("objectDestroyed", ptypes);
for (Map.Entry<String,UnitProfile> entry : _profiles.entrySet()) { _helpers.put(ObjectDestroyedEvent.class, method);
report.append(" ").append(entry.getKey());
report.append(" ").append(entry.getValue()).append("\n");
}
}
// roll over stats method = PresentsDObjectMgr.class.getMethod("objectAdded", ptypes);
if (reset) { _helpers.put(ObjectAddedEvent.class, method);
_recent = _current;
_current = new Stats(); method = PresentsDObjectMgr.class.getMethod("objectRemoved", ptypes);
_current.maxQueueSize = queueSize; _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; 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. * Used to track references of objects in oid lists.
*/ */
@@ -947,7 +954,7 @@ public class PresentsDObjectMgr
protected Queue<Object> _evqueue = new Queue<Object>(); protected Queue<Object> _evqueue = new Queue<Object>();
/** The managed distributed objects table. */ /** The managed distributed objects table. */
protected HashIntMap<DObject> _objects = new HashIntMap<DObject>(); protected IntMap<DObject> _objects = IntMaps.newHashIntMap();
/** Used to assign a unique oid to each distributed object. */ /** Used to assign a unique oid to each distributed object. */
protected int _nextOid = 0; protected int _nextOid = 0;
@@ -960,28 +967,33 @@ public class PresentsDObjectMgr
protected Throttle _fatalThrottle = new Throttle(30, 60*1000L); protected Throttle _fatalThrottle = new Throttle(30, 60*1000L);
/** Used to track oid list references of distributed objects. */ /** Used to track oid list references of distributed objects. */
protected HashIntMap<Reference[]> _refs = new HashIntMap<Reference[]>(); protected IntMap<Reference[]> _refs = IntMaps.newHashIntMap();
/** The default access controller to use when creating distributed objects. */ /** The default access controller to use when creating distributed objects. */
protected AccessController _defaultController; protected AccessController _defaultController;
/** Maintains proxy information for any proxied distributed objects. */ /** Maintains proxy information for any proxied distributed objects. */
protected HashIntMap<ProxyReference> _proxies = new HashIntMap<ProxyReference>(); protected IntMap<ProxyReference> _proxies = IntMaps.newHashIntMap();
/** We keep track of which thread is executing the event loop so that other services can /** keeps Track of which thread is executing the event loop so that other services can enforce
* enforce restrictions on code that should or should not be called from the event dispatch * restrictions on code that should or should not be called from the event dispatch thread. */
* thread. */
protected Thread _dobjThread; protected Thread _dobjThread;
/** A monotonically increasing counter used to assign an id to all dispatched events. */ /** A monotonically increasing counter used to assign an id to all dispatched events. */
protected long _nextEventId = 1; protected long _nextEventId = 1;
/** Used to profile our events and runnable units. */ /** Used to profile our events and runnable units. */
protected HashMap<String,UnitProfile> _profiles = new HashMap<String,UnitProfile>(); protected Map<String,UnitProfile> _profiles = Maps.newHashMap();
/** Used to track runtime statistics. */ /** Used to track runtime statistics. */
protected Stats _recent = new Stats(), _current = _recent; protected Stats _recent = new Stats(), _current = _recent;
/** Maps event classes to helpers that perform additional processing for particular events. */
protected Map<Class,Method> _helpers = Maps.newHashMap();
// injected dependencies
@Inject protected InvocationManager _invmgr;
/** Whether or not unit profiling is enabled. */ /** Whether or not unit profiling is enabled. */
protected static final boolean UNIT_PROF_ENABLED = false; protected static final boolean UNIT_PROF_ENABLED = false;
@@ -990,9 +1002,4 @@ public class PresentsDObjectMgr
/** The default size of an oid list refs vector. */ /** The default size of an oid list refs vector. */
protected static final int DEFREFVEC_SIZE = 4; 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<Class,Method> _helpers = new HashMap<Class,Method>();
static { registerEventHelpers(); }
} }
@@ -23,43 +23,41 @@ package com.threerings.presents.server;
import java.util.Iterator; import java.util.Iterator;
import com.google.inject.Inject;
import com.samskivert.util.Invoker; import com.samskivert.util.Invoker;
import com.samskivert.util.StringUtil; import com.samskivert.util.StringUtil;
import static com.threerings.presents.Log.log; import static com.threerings.presents.Log.log;
/** /**
* Extends the generic {@link Invoker} and integrates it a bit more into * Extends the generic {@link Invoker} and integrates it a bit more into the Presents system.
* the Presents system.
*/ */
public class PresentsInvoker extends Invoker public class PresentsInvoker extends Invoker
implements PresentsServer.Reporter implements ReportManager.Reporter
{ {
/** /**
* Creates an invoker that will post results to the supplied * Creates an invoker that will post results to the supplied
* distributed object manager. * distributed object manager.
*/ */
public PresentsInvoker (PresentsDObjectMgr omgr) @Inject public PresentsInvoker (PresentsDObjectMgr omgr, ReportManager repmgr)
{ {
super("presents.Invoker", omgr); super("presents.Invoker", omgr);
_omgr = omgr; _omgr = omgr;
if (PERF_TRACK) { if (PERF_TRACK) {
PresentsServer.registerReporter(this); repmgr.registerReporter(this);
} }
} }
/** @Override // from Invoker
* Will do a sophisticated shutdown of both itself and the DObjectManager
* thread.
*/
public void shutdown () public void shutdown ()
{ {
// this will do a sophisticated shutdown of both ourself and the dobjmgr
_queue.append(new ShutdownUnit()); _queue.append(new ShutdownUnit());
} }
// documentation inherited from interface // from interface ReportManager.Reporter
public void appendReport ( public void appendReport (StringBuilder buf, long now, long sinceLast, boolean reset)
StringBuilder buf, long now, long sinceLast, boolean reset)
{ {
buf.append("* presents.util.Invoker:\n"); buf.append("* presents.util.Invoker:\n");
int qsize = _queue.size(); 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) protected void willInvokeUnit (Unit unit, long start)
{ {
super.willInvokeUnit(unit, 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) protected void didInvokeUnit (Unit unit, long start)
{ {
super.didInvokeUnit(unit, 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 * This gets posted back and forth between the invoker and DObjectMgr until both of their
* until both of their queues are empty and they can both be safely * queues are empty and they can both be safely shutdown.
* shutdown.
*/ */
protected class ShutdownUnit extends Unit protected class ShutdownUnit extends Unit
{ {
@@ -21,13 +21,17 @@
package com.threerings.presents.server; 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.Invoker;
import com.samskivert.util.ObserverList; import com.samskivert.util.RunQueue;
import com.samskivert.util.StringUtil;
import com.samskivert.util.SystemInfo; 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.client.Client;
import com.threerings.presents.dobj.AccessController; import com.threerings.presents.dobj.AccessController;
import com.threerings.presents.server.net.ConnectionManager; import com.threerings.presents.server.net.ConnectionManager;
@@ -44,74 +48,40 @@ import static com.threerings.presents.Log.log;
*/ */
public class PresentsServer public class PresentsServer
{ {
/** Used to generate "state of the server" reports. See {@link #registerReporter}. */ /** Configures dependencies needed by the Presents services. */
public static interface Reporter public static class Module extends AbstractModule
{ {
/** @Override protected void configure () {
* Requests that this reporter append its report to the supplied string buffer. bind(Invoker.class).annotatedWith(MainInvoker.class).to(ServerInvoker.class);
* bind(RunQueue.class).annotatedWith(EventQueue.class).to(PresentsDObjectMgr.class);
* @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);
} }
/** Implementers of this interface will be notified when the server is shutting down. */ /** OBSOLETE! Don't use me. */
public static interface Shutdowner
{
/**
* Called when the server is shutting down.
*/
public void shutdown ();
}
/** The manager of network connections. */
public static ConnectionManager conmgr; public static ConnectionManager conmgr;
/** The manager of clients. */ /** OBSOLETE! Don't use me. */
public static ClientManager clmgr; public static ClientManager clmgr;
/** The distributed object manager. */ /** OBSOLETE! Don't use me. */
public static PresentsDObjectMgr omgr; public static PresentsDObjectMgr omgr;
/** The invocation manager. */ /** OBSOLETE! Don't use me. */
public static InvocationManager invmgr; public static InvocationManager invmgr;
/** This is used to invoke background tasks that should not be allowed to tie up the /** OBSOLETE! Don't use me. */
* distributed object manager thread. This is generally used to talk to databases and other public static Invoker invoker;
* (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);
}
/** /**
* The default entry point for the server. * The default entry point for the server.
*/ */
public static void main (String[] args) public static void main (String[] args)
{ {
log.info("Presents server starting..."); Injector injector = Guice.createInjector(new Module());
PresentsServer server = injector.getInstance(PresentsServer.class);
PresentsServer server = new PresentsServer();
try { try {
// initialize the server // initialize the server
server.init(); server.init(injector);
// check to see if we should load and invoke a test module before running the server // check to see if we should load and invoke a test module before running the server
String testmod = System.getProperty("test_module"); String testmod = System.getProperty("test_module");
@@ -139,9 +109,16 @@ public class PresentsServer
/** /**
* Initializes all of the server services and prepares for operation. * Initializes all of the server services and prepares for operation.
*/ */
public void init () public void init (Injector injector)
throws Exception throws Exception
{ {
// populate our legacy statics
conmgr = _conmgr;
clmgr = _clmgr;
omgr = _omgr;
invmgr = _invmgr;
invoker = _invoker;
// output general system information // output general system information
SystemInfo si = new SystemInfo(); SystemInfo si = new SystemInfo();
log.info("Starting up server [os=" + si.osToString() + ", jvm=" + si.jvmToString() + log.info("Starting up server [os=" + si.osToString() + ", jvm=" + si.jvmToString() +
@@ -150,48 +127,26 @@ public class PresentsServer
// register SIGTERM, SIGINT (ctrl-c) and a SIGHUP handlers // register SIGTERM, SIGINT (ctrl-c) and a SIGHUP handlers
boolean registered = false; boolean registered = false;
try { try {
registered = new SunSignalHandler().init(this); registered = injector.getInstance(SunSignalHandler.class).init();
} catch (Throwable t) { } catch (Throwable t) {
log.warning("Unable to register Sun signal handlers [error=" + t + "]."); log.warning("Unable to register Sun signal handlers [error=" + t + "].");
} }
if (!registered) { 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 // configure the dobject manager with our access controller
omgr.setDefaultAccessController(createDefaultObjectAccessController()); _omgr.setDefaultAccessController(createDefaultObjectAccessController());
// create and start up our invoker // start the main invoker thread
invoker = new PresentsInvoker(omgr) { _invoker.start();
protected void didShutdown () {
invokerDidShutdown();
}
};
invoker.start();
// create our connection manager // configure our connection manager
conmgr = new ConnectionManager(getListenPorts(), getDatagramPorts()); _conmgr.init(getListenPorts(), getDatagramPorts());
conmgr.setAuthenticator(createAuthenticator()); _conmgr.setAuthenticator(createAuthenticator());
// create our client manager
clmgr = createClientManager(conmgr);
// create our invocation manager
invmgr = new InvocationManager(omgr);
// initialize the time base services // initialize the time base services
TimeBaseProvider.init(invmgr, omgr); 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(); return new DummyAuthenticator();
} }
/** // /**
* Creates the client manager to be used on this server. // * Creates the client manager to be used on this server.
*/ // */
protected ClientManager createClientManager (ConnectionManager conmgr) // protected ClientManager createClientManager (ConnectionManager conmgr)
{ // {
return new ClientManager(conmgr); // return new ClientManager(conmgr);
} // }
/** // /**
* Creates the distributed object manager to be used on this server. // * Creates the distributed object manager to be used on this server.
*/ // */
protected PresentsDObjectMgr createDObjectManager () // protected PresentsDObjectMgr createDObjectManager ()
{ // {
return new PresentsDObjectMgr(); // return new PresentsDObjectMgr();
} // }
/** /**
* Defines the default object access policy for all {@link DObject} instances. The default * Defines the default object access policy for all {@link DObject} instances. The default
@@ -253,119 +208,26 @@ public class PresentsServer
omgr.postRunnable(new Runnable() { omgr.postRunnable(new Runnable() {
public void run () { public void run () {
// start up the connection manager // start up the connection manager
conmgr.start(); _conmgr.start();
} }
}); });
// invoke the dobjmgr event loop // invoke the dobjmgr event loop
omgr.run(); 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, * Requests that the server shut down. All registered shutdown participants will be shut down,
* following which the server process will be terminated. * following which the server process will be terminated.
*/ */
public void shutdown () public void shutdown ()
{ {
ObserverList<Shutdowner> downers = _downers; // shutdown all registered shutdowners
if (downers == null) { _shutmgr.shutdown();
log.warning("Refusing repeat shutdown request.");
return;
}
_downers = null;
// shut down all shutdown participants
downers.apply(new ObserverList.ObserverOp<Shutdowner>() {
public boolean apply (Shutdowner downer) {
downer.shutdown();
return true;
}
});
// shut down the connection manager (this will cease all network activity but not actually // shut down the connection manager (this will cease all network activity but not actually
// close the connections) // close the connections)
if (conmgr.isRunning()) { if (_conmgr.isRunning()) {
conmgr.shutdown(); _conmgr.shutdown();
} }
// finally shut down the invoker and dobj manager (The invoker does both for us.) // 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. */ /** Integrates the main invoker thread with the distributed object thread so that they can
protected Interval _reportInterval; * 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. */ /** The manager of distributed objects. */
protected static long _serverStartTime = System.currentTimeMillis(); @Inject protected PresentsDObjectMgr _omgr;
/** The last time at which {@link #generateReport} was run. */ /** The manager of network connections. */
protected static long _lastReportStamp = _serverStartTime; @Inject protected ConnectionManager _conmgr;
/** Used to generate "state of server" reports. */ /** The manager of clients. */
protected static ArrayList<Reporter> _reporters = new ArrayList<Reporter>(); @Inject protected ClientManager _clmgr;
/** A list of shutdown participants. */ /** The manager of invocation services. */
protected static ObserverList<Shutdowner> _downers = @Inject protected InvocationManager _invmgr;
new ObserverList<Shutdowner>(ObserverList.SAFE_IN_ORDER_NOTIFY);
/** The frequency with which we generate "state of server" reports. */ /** Handles orderly shutdown of our managers, etc. */
protected static final long REPORT_INTERVAL = 15 * 60 * 1000L; @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;
} }
@@ -21,6 +21,9 @@
package com.threerings.presents.server; package com.threerings.presents.server;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.samskivert.io.PersistenceException; import com.samskivert.io.PersistenceException;
import com.samskivert.util.Invoker; import com.samskivert.util.Invoker;
@@ -39,12 +42,12 @@ import static com.threerings.presents.Log.log;
*/ */
public class Rejector extends PresentsServer public class Rejector extends PresentsServer
{ {
// documentation inherited @Override // from PresentsServer
public void init () public void init (Injector injector)
throws Exception throws Exception
{ {
super.init(); super.init(injector);
conmgr.setAuthenticator(new RejectingAuthenticator()); _conmgr.setAuthenticator(new RejectingAuthenticator());
} }
// documentation inherited // documentation inherited
@@ -68,9 +71,10 @@ public class Rejector extends PresentsServer
_errmsg = MessageBundle.tcompose(_errmsg, eargs); _errmsg = MessageBundle.tcompose(_errmsg, eargs);
} }
Rejector server = new Rejector(); Injector injector = Guice.createInjector(new Module());
Rejector server = injector.getInstance(Rejector.class);
try { try {
server.init(); server.init(injector);
server.run(); server.run();
} catch (Exception e) { } catch (Exception e) {
log.warning("Unable to initialize server.", e); log.warning("Unable to initialize server.", e);
@@ -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<Reporter> _reporters = Lists.newArrayList();
/** The frequency with which we generate "state of server" reports. */
protected static final long REPORT_INTERVAL = 15 * 60 * 1000L;
}
@@ -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<Shutdowner> downers = _downers;
if (downers == null) {
log.warning("Refusing repeat shutdown request.");
return;
}
_downers = null;
// shut down all shutdown participants
downers.apply(new ObserverList.ObserverOp<Shutdowner>() {
public boolean apply (Shutdowner downer) {
downer.shutdown();
return true;
}
});
}
/** A list of shutdown participants. */
protected static ObserverList<Shutdowner> _downers = ObserverList.newSafeInOrder();
}
@@ -32,15 +32,27 @@ import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel; import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel; import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider; import java.nio.channels.spi.SelectorProvider;
import java.util.ArrayList;
import java.util.List;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map;
import java.util.Set; 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.FramingOutputStream;
import com.threerings.io.ObjectOutputStream; 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.util.DatagramSequencer;
import com.threerings.presents.server.Authenticator; 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; 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 * closely with the connection manager because network I/O is done via a poll()-like mechanism
* rather than via threads. * rather than via threads.
*/ */
@Singleton
public class ConnectionManager extends LoopingThread 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 * Creates a connection manager instance. Don't call this, Guice will do it for you.
* for client connections).
*/ */
public ConnectionManager (int port) @Inject public ConnectionManager (ReportManager repmgr)
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
{ {
super("ConnectionManager"); 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; _ports = ports;
_datagramPorts = datagramPorts; _datagramPorts = datagramPorts;
_selector = SelectorProvider.provider().openSelector(); _selector = SelectorProvider.provider().openSelector();
@@ -102,9 +113,6 @@ public class ConnectionManager extends LoopingThread
// create our stats record // create our stats record
_stats = new ConMgrStats(); _stats = new ConMgrStats();
_lastStats = 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); _authq.append(conn);
} }
// documentation inherited from interface PresentsServer.Reporter // documentation inherited from interface ReportManager.Reporter
public void appendReport ( public void appendReport (
StringBuilder report, long now, long sinceLast, boolean reset) 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 // 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 + log.warning("Message posted on non-distributed object thread [conn=" + conn +
", msg=" + msg + ", thread=" + Thread.currentThread() + "]."); ", msg=" + msg + ", thread=" + Thread.currentThread() + "].");
Thread.dumpStack(); Thread.dumpStack();
@@ -1029,6 +1037,15 @@ public class ConnectionManager extends LoopingThread
protected int _msgs, _partials; 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 int[] _ports, _datagramPorts;
protected Authenticator _author; protected Authenticator _author;
protected Selector _selector; protected Selector _selector;
@@ -1040,11 +1057,10 @@ public class ConnectionManager extends LoopingThread
protected int _runtimeExceptionCount; protected int _runtimeExceptionCount;
/** Maps selection keys to network event handlers. */ /** Maps selection keys to network event handlers. */
protected HashMap<SelectionKey,NetEventHandler> _handlers = protected Map<SelectionKey,NetEventHandler> _handlers = Maps.newHashMap();
new HashMap<SelectionKey,NetEventHandler>();
/** Connections mapped by identifier. */ /** Connections mapped by identifier. */
protected HashIntMap<Connection> _connections = new HashIntMap<Connection>(); protected IntMap<Connection> _connections = IntMaps.newHashIntMap();
protected Queue<Connection> _deathq = new Queue<Connection>(); protected Queue<Connection> _deathq = new Queue<Connection>();
protected Queue<AuthingConnection> _authq = new Queue<AuthingConnection>(); protected Queue<AuthingConnection> _authq = new Queue<AuthingConnection>();
@@ -1056,9 +1072,9 @@ public class ConnectionManager extends LoopingThread
protected ByteBuffer _outbuf = ByteBuffer.allocateDirect(64 * 1024); protected ByteBuffer _outbuf = ByteBuffer.allocateDirect(64 * 1024);
protected ByteBuffer _databuf = ByteBuffer.allocateDirect(Client.MAX_DATAGRAM_SIZE); protected ByteBuffer _databuf = ByteBuffer.allocateDirect(Client.MAX_DATAGRAM_SIZE);
protected HashMap<Connection,OverflowQueue> _oflowqs = new HashMap<Connection,OverflowQueue>(); protected Map<Connection,OverflowQueue> _oflowqs = Maps.newHashMap();
protected ArrayList<ConnectionObserver> _observers = new ArrayList<ConnectionObserver>(); protected List<ConnectionObserver> _observers = Lists.newArrayList();
/** Bytes in and out in the last reporting period. */ /** Bytes in and out in the last reporting period. */
protected long _bytesIn, _bytesOut; protected long _bytesIn, _bytesOut;
@@ -1075,14 +1091,8 @@ public class ConnectionManager extends LoopingThread
/** A runnable to execute when the connection manager thread exits. */ /** A runnable to execute when the connection manager thread exits. */
protected volatile Runnable _onExit; protected volatile Runnable _onExit;
/** Used to create an overflow queue on the first partial write. */ // injected dependencies
protected PartialWriteHandler _oflowHandler = new PartialWriteHandler() { @Inject protected PresentsDObjectMgr _omgr;
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));
}
};
/** How long we wait for network events before checking our running flag to see if we should /** 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 * still be running. We don't want to loop too tightly, but we need to make sure we don't sit
+3 -68
View File
@@ -129,73 +129,8 @@
<move overwrite="true" file="../etc/empty.abc" tofile="${deploy.dir}/testslib.abc"/> <move overwrite="true" file="../etc/empty.abc" tofile="${deploy.dir}/testslib.abc"/>
</target> </target>
<!-- test the component metadata bundling process -->
<target name="cbundles" description="Build component bundles.">
<!-- define our tasks -->
<taskdef name="metabundle"
classname="com.threerings.cast.bundle.tools.MetadataBundlerTask"
classpathref="classpath"/>
<taskdef name="cbundle"
classname="com.threerings.cast.bundle.tools.ComponentBundlerTask"
classpathref="classpath"/>
<!-- build the metadata bundles -->
<metabundle actiondef="${cbundle.dir}/actions.xml"
classdef="${cbundle.dir}/classes.xml"
target="${cbundle.dir}/metadata.jar"/>
<!-- blow away the components map file so that we get a consistent -->
<!-- mapping every time-->
<delete file="${cbundle.dir}/components.map"/>
<!-- build the component bundles -->
<cbundle actiondef="${cbundle.dir}/actions.xml"
target="${cbundle.dir}/pirate/components.jar"
mapfile="${cbundle.dir}/components.map"
root="${cbundle.dir}/pirate">
<fileset dir="${cbundle.dir}/pirate" includes="**/*.png"
excludes="components/**"/>
</cbundle>
<cbundle actiondef="${cbundle.dir}/actions.xml"
target="${cbundle.dir}/vessel/components.jar"
mapfile="${cbundle.dir}/components.map"
root="${cbundle.dir}/vessel">
<fileset dir="${cbundle.dir}/vessel" includes="**/*.png"
excludes="components/**"/>
</cbundle>
</target>
<!-- test the tileset bundling process -->
<target name="tsbundles" description="Build tileset bundles.">
<!-- blow away the tilesetid map file so that we get a consistent -->
<!-- mapping every time-->
<delete file="${tbundle.dir}/tilesets.map"/>
<!-- build the tileset bundles -->
<taskdef name="tilebundle"
classname="com.threerings.media.tile.bundle.tools.TileSetBundlerTask"
classpathref="classpath"/>
<tilebundle config="${tbundle.dir}/bundler-config.xml"
mapfile="${tbundle.dir}/tilesets.map">
<fileset dir="${tbundle.dir}/ground" includes="**/*.xml"/>
</tilebundle>
<tilebundle config="${tbundle.dir}/bundler-config.xml"
mapfile="${tbundle.dir}/tilesets.map">
<fileset dir="${tbundle.dir}/objects" includes="**/*.xml"/>
</tilebundle>
<!-- build the fringe configurations -->
<taskdef name="conffringe"
classname="com.threerings.miso.tile.tools.CompileFringeConfigurationTask"
classpathref="classpath"/>
<conffringe
tilesetmap="${tbundle.dir}/tilesets.map"
fringedef="rsrc/config/miso/tile/fringeconf.xml"
target="rsrc/config/miso/tile/fringeconf.dat"/>
</target>
<!-- run the tests --> <!-- run the tests -->
<target name="test" depends="compile,cbundles,tsbundles" <target name="test" depends="compile"
description="Run the tests."> description="Run the tests.">
<junit printsummary="no" haltonfailure="yes" fork="${junit.fork}"> <junit printsummary="no" haltonfailure="yes" fork="${junit.fork}">
<classpath refid="classpath"/> <classpath refid="classpath"/>
@@ -233,7 +168,8 @@
</import> </import>
<filespec dir="src/as" includes="com/threerings/bureau/client/TestClientMain.as"/> <filespec dir="src/as" includes="com/threerings/bureau/client/TestClientMain.as"/>
</asc> </asc>
<move overwrite="true" file="src/as/com/threerings/bureau/client/TestClientMain.abc" todir="dist"/> <move overwrite="true" todir="dist"
file="src/as/com/threerings/bureau/client/TestClientMain.abc"/>
</target> </target>
<target name="bureau-runserver" depends="compile" <target name="bureau-runserver" depends="compile"
@@ -243,7 +179,6 @@
</java> </java>
</target> </target>
<target name="bureau-testregistry" depends="compile" <target name="bureau-testregistry" depends="compile"
description="Run the bureau test server and tests the registry."> description="Run the bureau test server and tests the registry.">
<java fork="true" classname="com.threerings.bureau.server.RegistryTester"> <java fork="true" classname="com.threerings.bureau.server.RegistryTester">
@@ -25,9 +25,14 @@ import java.util.List;
import java.util.Random; import java.util.Random;
import com.google.common.collect.Lists; 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.bureau.data.AgentObject;
import com.threerings.presents.server.ShutdownManager;
import static com.threerings.bureau.Log.log; import static com.threerings.bureau.Log.log;
/** /**
@@ -59,11 +64,12 @@ public class RegistryTester
*/ */
public static void main (String[] args) public static void main (String[] args)
{ {
TestServer server = new TestServer(); Injector injector = Guice.createInjector(new TestServer.Module());
RegistryTester tester = new RegistryTester(server); TestServer server = injector.getInstance(TestServer.class);
RegistryTester tester = injector.getInstance(RegistryTester.class);
try { try {
server.init(); server.init(injector);
tester.start(); tester.start();
server.run(); server.run();
@@ -75,7 +81,7 @@ public class RegistryTester
/** /**
* Creates a new registry tester. * Creates a new registry tester.
*/ */
public RegistryTester (TestServer server) @Inject public RegistryTester (TestServer server, ShutdownManager shutmgr)
{ {
_server = server; _server = server;
@@ -92,7 +98,7 @@ public class RegistryTester
// stop the tests when the server shuts down // stop the tests when the server shuts down
// TODO: this is not called on Ctrl-C, need a way to shut down gracefully // 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 () { public void shutdown () {
log.info("Shutting down tests"); log.info("Shutting down tests");
_stop = true; _stop = true;
@@ -21,6 +21,9 @@
package com.threerings.bureau.server; package com.threerings.bureau.server;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.threerings.presents.server.PresentsServer; import com.threerings.presents.server.PresentsServer;
import static com.threerings.bureau.Log.log; import static com.threerings.bureau.Log.log;
@@ -40,9 +43,10 @@ public class TestServer extends PresentsServer
*/ */
public static void main (String[] args) public static void main (String[] args)
{ {
final TestServer server = new TestServer(); Injector injector = Guice.createInjector(new Module());
TestServer server = injector.getInstance(TestServer.class);
try { try {
server.init(); server.init(injector);
setClientTarget("bureau-runclient"); setClientTarget("bureau-runclient");
server.run(); server.run();
@@ -51,20 +55,20 @@ public class TestServer extends PresentsServer
} }
} }
// inherit documentation - from PresentsServer @Override // from PresentsServer
public void init () public void init (Injector injector)
throws Exception throws Exception
{ {
super.init(); super.init(injector);
breg = new BureauRegistry("localhost:47624", invmgr, omgr, invoker); 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)); breg.setCommandGenerator("test", antCommandGenerator(target));
} }
static public BureauRegistry.CommandGenerator antCommandGenerator ( public static BureauRegistry.CommandGenerator antCommandGenerator (
final String target) final String target)
{ {
return new BureauRegistry.CommandGenerator() { return new BureauRegistry.CommandGenerator() {
@@ -3,6 +3,9 @@
package com.threerings.crowd.server; 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.JabberConfig;
import com.threerings.crowd.data.PlaceObject; import com.threerings.crowd.data.PlaceObject;
@@ -15,10 +18,10 @@ import static com.threerings.crowd.Log.log;
public class JabberServer extends CrowdServer public class JabberServer extends CrowdServer
{ {
// documentation inherited // documentation inherited
public void init () public void init (Injector injector)
throws Exception throws Exception
{ {
super.init(); super.init(injector);
// create a single location // create a single location
_pmgr = plreg.createPlace(new JabberConfig()); _pmgr = plreg.createPlace(new JabberConfig());
@@ -26,9 +29,11 @@ public class JabberServer extends CrowdServer
public static void main (String[] args) public static void main (String[] args)
{ {
JabberServer server = new JabberServer(); Injector injector = Guice.createInjector(new Module());
JabberServer server = injector.getInstance(JabberServer.class);
try { try {
server.init(); server.init(injector);
server.run(); server.run();
} catch (Exception e) { } catch (Exception e) {
log.warning("Unable to initialize server.", e); log.warning("Unable to initialize server.", e);
@@ -21,43 +21,29 @@
package com.threerings.presents.server; package com.threerings.presents.server;
import junit.framework.Test; import org.junit.Test;
import junit.framework.TestCase; 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.data.TestObject;
import com.threerings.presents.dobj.*; import com.threerings.presents.dobj.*;
import static org.junit.Assert.*;
/** /**
* A simple test case for the dobjmgr. * 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 implements AttributeChangeListener, ElementUpdateListener
{ {
public DOMTest () @Test public void runTest ()
{
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 ()
{ {
// request that a new TestObject be registered // request that a new TestObject be registered
_test = _omgr.registerObject(new TestObject()); _test = _omgr.registerObject(new TestObject());
@@ -87,27 +73,35 @@ public class DOMTest extends TestCase
_omgr.run(); _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(); // Log.info("Element updated " + event);
test.runTest(); // Log.info(StringUtil.toString(_test.ints));
// Log.info(StringUtil.toString(_test.strings));
} }
protected int _fcount = 0; protected int _fcount = 0;
protected TestObject _test; protected TestObject _test;
// the fields that will change in attribute changed events // the fields that will change in attribute changed events
protected Object[] fields = { protected Object[] fields = { TestObject.FOO, TestObject.BAR, TestObject.FOO, TestObject.BAR };
TestObject.FOO, TestObject.BAR, TestObject.FOO, TestObject.BAR };
// the values we'll receive via attribute changed events // the values we'll receive via attribute changed events
protected Object[] values = { protected Object[] values = { new Integer(99), "hoopie", new Integer(25), "howdy" };
new Integer(99), "hoopie", new Integer(25), "howdy" };
protected static PresentsDObjectMgr _omgr = new PresentsDObjectMgr(); @Inject @Unit protected PresentsDObjectMgr _omgr;
} }
@@ -21,60 +21,37 @@
package com.threerings.presents.server; package com.threerings.presents.server;
import junit.framework.Test; import org.junit.Test;
import junit.framework.TestCase; 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.data.TestObject;
import com.threerings.presents.dobj.*; import com.threerings.presents.dobj.*;
import static org.junit.Assert.*;
import static com.threerings.presents.Log.log; import static com.threerings.presents.Log.log;
/** /**
* Tests that the dobjmgr will not allow a destroyed object to be added to * Tests that the dobjmgr will not allow a destroyed object to be added to
* an oid list. * an oid list.
*/ */
public class DestroyedRefTest extends TestCase @RunWith(AtUnit.class)
implements EventListener @Container(Container.Option.GUICE)
public class DestroyedRefTest
{ {
public static Test suite () @Test public void runTest ()
{
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 ()
{ {
// create two test objects // create two test objects
_objone = _omgr.registerObject(new TestObject()); _objone = _omgr.registerObject(new TestObject());
_objone.addListener(this); _objone.addListener(_listener);
_objtwo = _omgr.registerObject(new TestObject()); _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 // add object one to object two twice in a row to make sure repeated
// adds don't result in the object being listed twice // adds don't result in the object being listed twice
@@ -95,7 +72,30 @@ public class DestroyedRefTest extends TestCase
_omgr.run(); _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 TestObject _objone, _objtwo;
protected static PresentsDObjectMgr _omgr = new PresentsDObjectMgr(); @Inject @Unit protected PresentsDObjectMgr _omgr;
} }
@@ -21,65 +21,34 @@
package com.threerings.presents.server; package com.threerings.presents.server;
import junit.framework.Test; import org.junit.Test;
import junit.framework.TestCase; 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.data.TestObject;
import com.threerings.presents.dobj.*; import com.threerings.presents.dobj.*;
import static org.junit.Assert.*;
/** /**
* Tests the oid list reference tracking code. * Tests the oid list reference tracking code.
*/ */
public class RefTest extends TestCase @RunWith(AtUnit.class)
implements EventListener @Container(Container.Option.GUICE)
public class RefTest
{ {
public static Test suite () @Test public void runTest ()
{
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 ()
{ {
// create two test objects // create two test objects
_objone = _omgr.registerObject(new TestObject()); _objone = _omgr.registerObject(new TestObject());
_objone.addListener(this); _objone.addListener(_listener);
_objtwo = _omgr.registerObject(new TestObject()); _objtwo = _omgr.registerObject(new TestObject());
_objtwo.addListener(this); _objtwo.addListener(_listener);
// now that we have both objects, set up the references // now that we have both objects, set up the references
_objone.addToList(_objtwo.getOid()); _objone.addToList(_objtwo.getOid());
@@ -89,8 +58,39 @@ public class RefTest extends TestCase
_omgr.run(); _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 _objone;
protected TestObject _objtwo; protected TestObject _objtwo;
protected static PresentsDObjectMgr _omgr = new PresentsDObjectMgr(); @Inject @Unit protected PresentsDObjectMgr _omgr;
} }
@@ -21,6 +21,9 @@
package com.threerings.presents.server; 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.data.TestObject;
import com.threerings.presents.dobj.*; import com.threerings.presents.dobj.*;
@@ -30,13 +33,13 @@ public class TestServer extends PresentsServer
{ {
public static TestObject testobj; public static TestObject testobj;
public void init () public void init (Injector injector)
throws Exception throws Exception
{ {
super.init(); super.init(injector);
// register our test provider // register our test provider
invmgr.registerDispatcher(new TestDispatcher(new TestManager()), "test"); _invmgr.registerDispatcher(new TestDispatcher(new TestManager()), "test");
// create a test object // create a test object
testobj = omgr.registerObject(new TestObject()); testobj = omgr.registerObject(new TestObject());
@@ -48,9 +51,10 @@ public class TestServer extends PresentsServer
public static void main (String[] args) public static void main (String[] args)
{ {
TestServer server = new TestServer(); Injector injector = Guice.createInjector(new Module());
TestServer server = injector.getInstance(TestServer.class);
try { try {
server.init(); server.init(injector);
server.run(); server.run();
} catch (Exception e) { } catch (Exception e) {
log.warning("Unable to initialize server.", e); log.warning("Unable to initialize server.", e);