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">
<fileset dir="${libs.dir}" id="narya.libs">
<include name="ant.jar"/>
<include name="atunit.jar"/>
<include name="asc.jar"/>
<include name="flexTasks.jar"/>
<include name="commons-collections.jar"/>
<include name="commons-io.jar"/>
<include name="commons-lang.jar"/>
<include name="guice.jar"/>
<include name="google-collect.jar"/>
<include name="javassist.jar"/>
<include name="junit4.jar"/>
@@ -21,11 +21,14 @@
package com.threerings.crowd.peer.server;
import com.google.inject.Inject;
import com.threerings.util.Name;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.server.InvocationException;
import com.threerings.presents.server.PresentsClient;
import com.threerings.presents.server.ShutdownManager;
import com.threerings.presents.peer.data.ClientInfo;
import com.threerings.presents.peer.data.NodeObject;
@@ -49,6 +52,14 @@ import com.threerings.crowd.peer.data.CrowdPeerMarshaller;
public class CrowdPeerManager extends PeerManager
implements CrowdPeerProvider, ChatProvider.ChatForwarder
{
/**
* Creates an uninitialized peer manager.
*/
@Inject public CrowdPeerManager (ShutdownManager shutmgr)
{
super(shutmgr);
}
// from interface CrowdPeerProvider
public void deliverTell (ClientObject caller, UserMessage message,
Name target, ChatService.TellListener listener)
@@ -23,6 +23,9 @@ package com.threerings.crowd.server;
import java.util.Iterator;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.threerings.util.Name;
import com.threerings.presents.dobj.RootDObjectManager;
@@ -44,6 +47,15 @@ import static com.threerings.crowd.Log.log;
*/
public class CrowdServer extends PresentsServer
{
/** Configures dependencies needed by the Crowd services. */
public static class Module extends PresentsServer.Module
{
@Override protected void configure () {
super.configure();
// nada
}
}
/** The place registry. */
public static PlaceRegistry plreg;
@@ -53,11 +65,10 @@ public class CrowdServer extends PresentsServer
/**
* Initializes all of the server services and prepares for operation.
*/
public void init ()
public void init (Injector injector)
throws Exception
{
// do the presents server initialization
super.init();
super.init(injector);
// configure the client manager to use our bits
clmgr.setClientFactory(new ClientFactory() {
@@ -139,9 +150,10 @@ public class CrowdServer extends PresentsServer
public static void main (String[] args)
{
CrowdServer server = new CrowdServer();
Injector injector = Guice.createInjector(new Module());
CrowdServer server = injector.getInstance(CrowdServer.class);
try {
server.init();
server.init(injector);
server.run();
} catch (Exception e) {
log.warning("Unable to initialize server.", e);
@@ -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.List;
import com.google.common.collect.Maps;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.samskivert.jdbc.RepositoryUnit;
import com.samskivert.jdbc.WriteOnlyUnit;
import com.samskivert.jdbc.depot.PersistenceContext;
@@ -49,7 +53,6 @@ import com.samskivert.util.Tuple;
import com.threerings.io.Streamable;
import com.threerings.util.Name;
import com.threerings.presents.client.Client;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.EntryAddedEvent;
@@ -58,10 +61,15 @@ import com.threerings.presents.dobj.EntryUpdatedEvent;
import com.threerings.presents.dobj.ObjectAccessException;
import com.threerings.presents.dobj.SetListener;
import com.threerings.presents.dobj.Subscriber;
import com.threerings.presents.client.Client;
import com.threerings.presents.server.ClientManager;
import com.threerings.presents.server.InvocationException;
import com.threerings.presents.server.InvocationManager;
import com.threerings.presents.server.PresentsClient;
import com.threerings.presents.server.PresentsServer;
import com.threerings.presents.server.PresentsDObjectMgr;
import com.threerings.presents.server.ShutdownManager;
import com.threerings.presents.server.net.ConnectionManager;
import com.threerings.presents.peer.data.ClientInfo;
import com.threerings.presents.peer.data.NodeObject;
@@ -77,8 +85,9 @@ import static com.threerings.presents.Log.log;
* client connection to the other servers and subscribes to the {@link NodeObject} of all peer
* servers and uses those objects to communicate cross-node information.
*/
@Singleton
public class PeerManager
implements PeerProvider, ClientManager.ClientObserver, PresentsServer.Shutdowner
implements PeerProvider, ClientManager.ClientObserver, ShutdownManager.Shutdowner
{
/**
* Used by entities that wish to know when cached data has become stale due to a change on
@@ -168,6 +177,14 @@ public class PeerManager
protected abstract void execute ();
}
/**
* Creates an uninitialized peer manager.
*/
@Inject public PeerManager (ShutdownManager shutmgr)
{
shutmgr.registerShutdowner(this);
}
/**
* Returns the distributed object that represents this node to its peers.
*/
@@ -202,14 +219,11 @@ public class PeerManager
_sharedSecret = sharedSecret;
// wire ourselves into the server
PresentsServer.registerShutdowner(this);
PresentsServer.conmgr.setAuthenticator(
new PeerAuthenticator(this, PresentsServer.conmgr.getAuthenticator()));
PresentsServer.clmgr.setClientFactory(
new PeerClientFactory(this, PresentsServer.clmgr.getClientFactory()));
_conmgr.setAuthenticator(new PeerAuthenticator(this, _conmgr.getAuthenticator()));
_clmgr.setClientFactory(new PeerClientFactory(this, _clmgr.getClientFactory()));
// create our node object
_nodeobj = PresentsServer.omgr.registerObject(createNodeObject());
_nodeobj = _omgr.registerObject(createNodeObject());
_nodeobj.setNodeName(nodeName);
// register ourselves with the node table
@@ -223,10 +237,10 @@ public class PeerManager
// set the invocation service
_nodeobj.setPeerService(
(PeerMarshaller)PresentsServer.invmgr.registerDispatcher(new PeerDispatcher(this)));
(PeerMarshaller)_invmgr.registerDispatcher(new PeerDispatcher(this)));
// register ourselves as a client observer
PresentsServer.clmgr.addClientObserver(this);
_clmgr.addClientObserver(this);
// and start our peer refresh interval
_peerRefresher.schedule(5000L, 60*1000L);
@@ -317,8 +331,8 @@ public class PeerManager
public void invokeNodeAction (final NodeAction action)
{
// if we're not on the dobjmgr thread, get there
if (!PresentsServer.omgr.isDispatchThread()) {
PresentsServer.omgr.postRunnable(new Runnable() {
if (!_omgr.isDispatchThread()) {
_omgr.postRunnable(new Runnable() {
public void run () {
invokeNodeAction(action);
}
@@ -384,7 +398,7 @@ public class PeerManager
// make a note of this proxy mapping
_proxies.put(key, new Tuple<Subscriber<?>,DObject>(this, object));
// map the object into our local oid space
PresentsServer.omgr.registerProxyObject(object, peer.getDObjectManager());
_omgr.registerProxyObject(object, peer.getDObjectManager());
// then tell the caller about the (now remapped) oid
listener.requestCompleted(object.getOid());
}
@@ -408,7 +422,7 @@ public class PeerManager
}
// clear out the local object manager's proxy mapping
PresentsServer.omgr.clearProxyObject(remoteOid, bits.right);
_omgr.clearProxyObject(remoteOid, bits.right);
final Client peer = getPeerClient(nodeName);
if (peer == null) {
@@ -681,19 +695,19 @@ public class PeerManager
_nodeobj.setCacheData(new NodeObject.CacheData(cache, data));
}
// from interface PresentsServer.Shutdowner
// from interface ShutdownManager.Shutdowner
public void shutdown ()
{
// clear out our invocation service
if (_nodeobj != null) {
PresentsServer.invmgr.clearDispatcher(_nodeobj.peerService);
_invmgr.clearDispatcher(_nodeobj.peerService);
}
// stop our peer refresher interval
_peerRefresher.cancel();
// clear out our client observer registration
PresentsServer.clmgr.removeClientObserver(this);
_clmgr.removeClientObserver(this);
// clear our record from the node table
_invoker.postUnit(new WriteOnlyUnit("deleteNode(" + _nodeName + ")") {
@@ -1004,7 +1018,7 @@ public class PeerManager
_remoids = (ArrayIntSet)_suboids.clone();
// schedule a timeout to act if something goes wrong
(_timeout = new Interval(PresentsServer.omgr) {
(_timeout = new Interval(_omgr) {
public void expired () {
log.warning("Lock handler timed out, acting anyway [lock=" + _lock +
", acquire=" + _acquire + "].");
@@ -1198,19 +1212,23 @@ public class PeerManager
/** Contains a mapping of proxied objects to subscriber instances. */
protected HashMap<Tuple<String,Integer>,Tuple<Subscriber<?>,DObject>> _proxies =
new HashMap<Tuple<String,Integer>,Tuple<Subscriber<?>,DObject>>();
Maps.newHashMap();
/** Our stale cache observers. */
protected HashMap<String, ObserverList<StaleCacheObserver>> _cacheobs =
new HashMap<String, ObserverList<StaleCacheObserver>>();
protected HashMap<String, ObserverList<StaleCacheObserver>> _cacheobs = Maps.newHashMap();
/** Listeners for dropped locks. */
protected ObserverList<DroppedLockObserver> _dropobs =
new ObserverList<DroppedLockObserver>(ObserverList.FAST_UNSAFE_NOTIFY);
/** Locks in the process of resolution. */
protected HashMap<NodeObject.Lock, LockHandler> _locks =
new HashMap<NodeObject.Lock, LockHandler>();
protected HashMap<NodeObject.Lock, LockHandler> _locks = Maps.newHashMap();
// our service dependencies
@Inject protected ConnectionManager _conmgr;
@Inject protected ClientManager _clmgr;
@Inject protected PresentsDObjectMgr _omgr;
@Inject protected InvocationManager _invmgr;
/** We wait this long for peer ratification to complete before acquiring/releasing the lock. */
protected static final long LOCK_TIMEOUT = 5000L;
@@ -21,6 +21,8 @@
package com.threerings.presents.server;
import com.google.inject.Inject;
import static com.threerings.presents.Log.log;
/**
@@ -28,9 +30,11 @@ import static com.threerings.presents.Log.log;
*/
public abstract class AbstractSignalHandler
{
public boolean init (PresentsServer server)
/**
* Initializes this signal handler.
*/
public boolean init ()
{
_server = server;
return registerHandlers();
}
@@ -65,8 +69,9 @@ public abstract class AbstractSignalHandler
*/
protected void hupReceived ()
{
log.info(PresentsServer.generateReport());
log.info(_repmgr.generateReport());
}
protected PresentsServer _server;
@Inject protected PresentsServer _server;
@Inject protected ReportManager _repmgr;
}
@@ -22,9 +22,14 @@
package com.threerings.presents.server;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.samskivert.util.Interval;
import com.samskivert.util.ObserverList;
@@ -49,9 +54,10 @@ import static com.threerings.presents.Log.log;
* thread (to notify of connections showing up or going away) and from the dobjmgr thread (when
* clients are given the boot for application-defined reasons).
*/
@Singleton
public class ClientManager
implements ConnectionObserver, ClientResolutionListener,
PresentsServer.Reporter, PresentsServer.Shutdowner
ReportManager.Reporter, ShutdownManager.Shutdowner
{
/**
* Used by {@link #applyToClient}.
@@ -89,25 +95,26 @@ public class ClientManager
/**
* Constructs a client manager that will interact with the supplied connection manager.
*/
public ClientManager (ConnectionManager conmgr)
@Inject public ClientManager (ConnectionManager conmgr, ReportManager repmgr,
ShutdownManager shutmgr)
{
// register ourselves as a connection observer
conmgr.addConnectionObserver(this);
// start up an interval that will check for expired clients and flush them from the bowels
// of the server
new Interval(PresentsServer.omgr) {
new Interval(_omgr) {
public void expired () {
flushClients();
}
}.schedule(CLIENT_FLUSH_INTERVAL, true);
// register as a "state of server" reporter and a shutdowner
PresentsServer.registerReporter(this);
PresentsServer.registerShutdowner(this);
repmgr.registerReporter(this);
shutmgr.registerShutdowner(this);
}
// documentation inherited from interface
// from interface ShutdownManager.Shutdowner
public void shutdown ()
{
log.info("Client manager shutting down [ccount=" + _usermap.size() + "].");
@@ -237,7 +244,7 @@ public class ClientManager
// report that the client is resolved on the dobjmgr thread to provide equivalent
// behavior to the case where we actually have to do the resolution
clobj.reference();
PresentsServer.omgr.postRunnable(new Runnable() {
_omgr.postRunnable(new Runnable() {
public void run () {
listener.clientResolved(username, clobj);
}
@@ -265,11 +272,11 @@ public class ClientManager
// create and register our client object and give it back to the client resolver; we
// need to do this on the dobjmgr thread since we're registering an object
final ClientResolver fclr = clr;
PresentsServer.omgr.postRunnable(new Runnable() {
_omgr.postRunnable(new Runnable() {
public void run () {
ClientObject clobj = fclr.createClientObject();
clobj.setPermissionPolicy(fclr.createPermissionPolicy());
fclr.objectAvailable(PresentsServer.omgr.registerObject(clobj));
fclr.objectAvailable(_omgr.registerObject(clobj));
}
});
@@ -303,7 +310,7 @@ public class ClientManager
_objmap.remove(username);
// and destroy the object itself
PresentsServer.omgr.destroyObject(clobj.getOid());
_omgr.destroyObject(clobj.getOid());
}
/**
@@ -411,7 +418,7 @@ public class ClientManager
}
}
// documentation inherited from interface PresentsServer.Reporter
// documentation inherited from interface ReportManager.Reporter
public void appendReport (StringBuilder report, long now, long sinceLast, boolean reset)
{
report.append("* presents.ClientManager:\n");
@@ -477,7 +484,7 @@ public class ClientManager
*/
protected void flushClients ()
{
ArrayList<PresentsClient> victims = new ArrayList<PresentsClient>();
List<PresentsClient> victims = Lists.newArrayList();
long now = System.currentTimeMillis();
// first build a list of our victims
@@ -530,23 +537,25 @@ public class ClientManager
}
/** A mapping from auth username to client instances. */
protected HashMap<Name,PresentsClient> _usermap = new HashMap<Name,PresentsClient>();
protected Map<Name,PresentsClient> _usermap = Maps.newHashMap();
/** 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. */
protected HashMap<Name,ClientObject> _objmap = new HashMap<Name,ClientObject>();
protected Map<Name,ClientObject> _objmap = Maps.newHashMap();
/** 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. */
protected ClientFactory _factory = ClientFactory.DEFAULT;
/** Tracks registered {@link ClientObserver}s. */
protected ObserverList<ClientObserver> _clobservers =
new ObserverList<ClientObserver>(ObserverList.SAFE_IN_ORDER_NOTIFY);
protected ObserverList<ClientObserver> _clobservers = ObserverList.newSafeInOrder();
// our injected dependencies
@Inject protected PresentsDObjectMgr _omgr;
/** The frequency with which we check for expired clients. */
protected static final long CLIENT_FLUSH_INTERVAL = 60 * 1000L;
@@ -21,9 +21,14 @@
package com.threerings.presents.server;
import java.util.HashMap;
import java.util.Map;
import com.samskivert.util.HashIntMap;
import com.google.common.collect.Maps;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.samskivert.util.IntMaps;
import com.samskivert.util.IntMap;
import com.samskivert.util.LRUHashMap;
import com.samskivert.util.StringUtil;
@@ -39,7 +44,6 @@ import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.EventListener;
import com.threerings.presents.dobj.InvocationRequestEvent;
import com.threerings.presents.dobj.ObjectAccessException;
import com.threerings.presents.dobj.RootDObjectManager;
import com.threerings.presents.net.Transport;
@@ -62,6 +66,7 @@ import static com.threerings.presents.Log.log;
* provides a mechanism by which responses and asynchronous notification invocations can be
* delivered to the client.
*/
@Singleton
public class InvocationManager
implements EventListener
{
@@ -70,16 +75,16 @@ public class InvocationManager
* operate its invocation services. Generally only one invocation manager should be operational
* in a particular system.
*/
public InvocationManager (RootDObjectManager omgr)
@Inject public InvocationManager (PresentsDObjectMgr omgr)
{
_omgr = omgr;
// create the object on which we'll listen for invocation requests
DObject invobj = omgr.registerObject(new DObject());
DObject invobj = _omgr.registerObject(new DObject());
invobj.addListener(this);
_invoid = invobj.getOid();
// Log.info("Created invocation service object [oid=" + _invoid + "].");
// log.info("Created invocation service object [oid=" + _invoid + "].");
}
/**
@@ -143,7 +148,7 @@ public class InvocationManager
_recentRegServices.put(Integer.valueOf(invCode), marsh.getClass().getName());
// Log.info("Registered service [marsh=" + marsh + "].");
// log.info("Registered service [marsh=" + marsh + "].");
return marsh;
}
@@ -199,7 +204,7 @@ public class InvocationManager
// documentation inherited from interface
public void eventReceived (DEvent event)
{
// Log.info("Event received " + event + ".");
// log.info("Event received " + event + ".");
if (event instanceof InvocationRequestEvent) {
InvocationRequestEvent ire = (InvocationRequestEvent)event;
@@ -255,7 +260,7 @@ public class InvocationManager
}
}
// Log.debug("Dispatching invreq [caller=" + source.who() +
// log.debug("Dispatching invreq [caller=" + source.who() +
// ", disp=" + disp + ", methId=" + methodId +
// ", args=" + StringUtil.toString(args) + "].");
@@ -301,29 +306,24 @@ public class InvocationManager
return _invCode++;
}
// debugging action...
protected static final LRUHashMap<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. */
protected int _invoid = -1;
/** Used to generate monotonically increasing provider ids. */
protected int _invCode;
/** The distribted object manager we're working with. */
protected PresentsDObjectMgr _omgr;
/** A table of invocation dispatchers each mapped by a unique code. */
protected HashIntMap<InvocationDispatcher> _dispatchers =
new HashIntMap<InvocationDispatcher>();
protected IntMap<InvocationDispatcher> _dispatchers = IntMaps.newHashIntMap();
/** A mapping from bootstrap group to lists of services that are to be provided to clients at
* boot time. */
protected HashMap<String,StreamableArrayList<InvocationMarshaller>> _bootlists =
new HashMap<String,StreamableArrayList<InvocationMarshaller>>();
/** Maps bootstrap group to lists of services to be provided to clients at boot time. */
protected Map<String,StreamableArrayList<InvocationMarshaller>> _bootlists = Maps.newHashMap();
/** The text that is appended to the procedure name when automatically generating a failure
* response. */
// debugging action...
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";
}
@@ -23,6 +23,7 @@ package com.threerings.presents.server;
import java.awt.EventQueue;
import com.google.inject.Inject;
import com.threerings.presents.dobj.DEvent;
import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.DObjectManager;
@@ -35,6 +36,14 @@ import com.threerings.presents.dobj.Subscriber;
*/
public class LocalDObjectMgr extends PresentsDObjectMgr
{
/**
* Creates the dobjmgr and prepares it for operation.
*/
@Inject public LocalDObjectMgr (ReportManager repmgr)
{
super(repmgr);
}
/**
* Creates a {@link DObjectManager} that posts directly to this local object manager, but first
* sets the source oid of all events to properly identify them with the supplied client oid.
@@ -22,14 +22,17 @@
package com.threerings.presents.server;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Maps;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.samskivert.util.AuditLogger;
import com.samskivert.util.HashIntMap;
import com.samskivert.util.Histogram;
import com.samskivert.util.IntMap;
import com.samskivert.util.IntMaps;
import com.samskivert.util.Interval;
import com.samskivert.util.Invoker;
import com.samskivert.util.Queue;
@@ -51,11 +54,12 @@ import static com.threerings.presents.Log.log;
* thus provides a method to be invoked by the application main thread which won't return until the
* manager has been requested to shut down.
*/
@Singleton
public class PresentsDObjectMgr
implements RootDObjectManager, RunQueue, PresentsServer.Reporter
implements RootDObjectManager, RunQueue, ReportManager.Reporter
{
/** Contains operational statistics that are tracked by the distributed object manager between
* {@link PresentsServer#Reporter} intervals. The snapshot for the most recently completed
* {@link ReportManager#Reporter} intervals. The snapshot for the most recently completed
* period can be requested via {@link #getStats()}. . */
public static class Stats
{
@@ -77,17 +81,21 @@ public class PresentsDObjectMgr
/**
* Creates the dobjmgr and prepares it for operation.
*/
public PresentsDObjectMgr ()
@Inject public PresentsDObjectMgr (ReportManager repmgr)
{
// we create a dummy object to live as oid zero and we'll use that for some internal event
// trickery
// create a dummy object to live as oid zero and use that for some internal event trickery
DObject dummy = new DObject();
dummy.setOid(0);
dummy.setManager(this);
_objects.put(0, new DObject());
// register ourselves as a state of server reporter
PresentsServer.registerReporter(this);
// register ourselves as a state of server reporter and also tell the report manager that
// we're providing the runqueue for it to do its business
repmgr.registerReporter(this);
repmgr.init(this);
// register our event helpers
registerEventHelpers();
}
/**
@@ -112,8 +120,7 @@ public class PresentsDObjectMgr
_defaultController = controller;
// switch all objects from the old default (null, usually) to the new default.
for (Iterator itr = _objects.elements(); itr.hasNext(); ) {
DObject obj = (DObject) itr.next();
for (DObject obj : _objects.values()) {
if (oldDefault == obj.getAccessController()) {
obj.setAccessController(controller);
}
@@ -289,6 +296,235 @@ public class PresentsDObjectMgr
log.info("DOMGR exited.");
}
/**
* Requests that the dobjmgr shut itself down soon- you may want to try using {@link
* Invoker#shutdown} which will make sure that both the Invoker and DObjectMgr are empty and
* then shut them both down.
*/
public void harshShutdown ()
{
postRunnable(new Runnable() {
public void run () {
_running = false;
}
});
}
/**
* Dumps collected profiling information to the system log.
*/
public void dumpUnitProfiles ()
{
for (Map.Entry<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.
*/
@@ -352,7 +588,7 @@ public class PresentsDObjectMgr
cname = StringUtil.shortClassName(ival);
} else if (unit instanceof InvocationRequestEvent) {
InvocationRequestEvent ire = (InvocationRequestEvent)unit;
Class c = PresentsServer.invmgr.getDispatcherClass(ire.getInvCode());
Class c = _invmgr.getDispatcherClass(ire.getInvCode());
cname = (c == null) ? "dobj.InvocationRequestEvent:(no longer registered)" :
StringUtil.shortClassName(c) + ":" + ire.getMethodId();
} else {
@@ -481,105 +717,6 @@ public class PresentsDObjectMgr
log.warning("Fatal error caused by '" + causer + "': " + error, error);
}
/**
* Requests that the dobjmgr shut itself down soon- you may want to try using {@link
* Invoker#shutdown} which will make sure that both the Invoker and DObjectMgr are empty and
* then shut them both down.
*/
public void harshShutdown ()
{
postRunnable(new Runnable() {
public void run () {
_running = false;
}
});
}
/**
* Dumps collected profiling information to the system log.
*/
public void dumpUnitProfiles ()
{
for (Map.Entry<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
* 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 ()
{
return _running;
@@ -735,28 +767,26 @@ public class PresentsDObjectMgr
return _nextOid;
}
// from interface PresentsServer.Reporter
public void appendReport (StringBuilder report, long now, long sinceLast, boolean reset)
/**
* Registers our event helper methods.
*/
protected void registerEventHelpers ()
{
report.append("* presents.PresentsDObjectMgr:\n");
int queueSize = _evqueue.size();
report.append("- Queue size: ").append(queueSize).append("\n");
report.append("- Max queue size: ").append(_current.maxQueueSize).append("\n");
report.append("- Units executed: ").append(_current.eventCount).append("\n");
Class[] ptypes = new Class[] { DEvent.class, DObject.class };
Method method;
if (UNIT_PROF_ENABLED) {
report.append("- Unit profiles: ").append(_profiles.size()).append("\n");
for (Map.Entry<String,UnitProfile> entry : _profiles.entrySet()) {
report.append(" ").append(entry.getKey());
report.append(" ").append(entry.getValue()).append("\n");
}
}
try {
method = PresentsDObjectMgr.class.getMethod("objectDestroyed", ptypes);
_helpers.put(ObjectDestroyedEvent.class, method);
// roll over stats
if (reset) {
_recent = _current;
_current = new Stats();
_current.maxQueueSize = queueSize;
method = PresentsDObjectMgr.class.getMethod("objectAdded", ptypes);
_helpers.put(ObjectAddedEvent.class, method);
method = PresentsDObjectMgr.class.getMethod("objectRemoved", ptypes);
_helpers.put(ObjectRemovedEvent.class, method);
} catch (Exception e) {
log.warning("Unable to register event helpers [error=" + e + "].");
}
}
@@ -846,29 +876,6 @@ public class PresentsDObjectMgr
protected int _action;
}
/**
* Registers our event helper methods.
*/
protected static void registerEventHelpers ()
{
Class[] ptypes = new Class[] { DEvent.class, DObject.class };
Method method;
try {
method = PresentsDObjectMgr.class.getMethod("objectDestroyed", ptypes);
_helpers.put(ObjectDestroyedEvent.class, method);
method = PresentsDObjectMgr.class.getMethod("objectAdded", ptypes);
_helpers.put(ObjectAddedEvent.class, method);
method = PresentsDObjectMgr.class.getMethod("objectRemoved", ptypes);
_helpers.put(ObjectRemovedEvent.class, method);
} catch (Exception e) {
log.warning("Unable to register event helpers [error=" + e + "].");
}
}
/**
* Used to track references of objects in oid lists.
*/
@@ -947,7 +954,7 @@ public class PresentsDObjectMgr
protected Queue<Object> _evqueue = new Queue<Object>();
/** 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. */
protected int _nextOid = 0;
@@ -960,28 +967,33 @@ public class PresentsDObjectMgr
protected Throttle _fatalThrottle = new Throttle(30, 60*1000L);
/** Used to track oid list references of distributed objects. */
protected HashIntMap<Reference[]> _refs = new HashIntMap<Reference[]>();
protected IntMap<Reference[]> _refs = IntMaps.newHashIntMap();
/** The default access controller to use when creating distributed objects. */
protected AccessController _defaultController;
/** Maintains proxy information for any proxied distributed objects. */
protected HashIntMap<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
* enforce restrictions on code that should or should not be called from the event dispatch
* thread. */
/** keeps Track of which thread is executing the event loop so that other services can enforce
* restrictions on code that should or should not be called from the event dispatch thread. */
protected Thread _dobjThread;
/** A monotonically increasing counter used to assign an id to all dispatched events. */
protected long _nextEventId = 1;
/** Used to profile our events and runnable units. */
protected HashMap<String,UnitProfile> _profiles = new HashMap<String,UnitProfile>();
protected Map<String,UnitProfile> _profiles = Maps.newHashMap();
/** Used to track runtime statistics. */
protected Stats _recent = new Stats(), _current = _recent;
/** Maps event classes to helpers that perform additional processing for particular events. */
protected Map<Class,Method> _helpers = Maps.newHashMap();
// injected dependencies
@Inject protected InvocationManager _invmgr;
/** Whether or not unit profiling is enabled. */
protected static final boolean UNIT_PROF_ENABLED = false;
@@ -990,9 +1002,4 @@ public class PresentsDObjectMgr
/** The default size of an oid list refs vector. */
protected static final int DEFREFVEC_SIZE = 4;
/** This table maps event classes to helper methods that perform some additional processing for
* particular events. */
protected static HashMap<Class,Method> _helpers = new HashMap<Class,Method>();
static { registerEventHelpers(); }
}
@@ -23,43 +23,41 @@ package com.threerings.presents.server;
import java.util.Iterator;
import com.google.inject.Inject;
import com.samskivert.util.Invoker;
import com.samskivert.util.StringUtil;
import static com.threerings.presents.Log.log;
/**
* Extends the generic {@link Invoker} and integrates it a bit more into
* the Presents system.
* Extends the generic {@link Invoker} and integrates it a bit more into the Presents system.
*/
public class PresentsInvoker extends Invoker
implements PresentsServer.Reporter
implements ReportManager.Reporter
{
/**
* Creates an invoker that will post results to the supplied
* distributed object manager.
*/
public PresentsInvoker (PresentsDObjectMgr omgr)
@Inject public PresentsInvoker (PresentsDObjectMgr omgr, ReportManager repmgr)
{
super("presents.Invoker", omgr);
_omgr = omgr;
if (PERF_TRACK) {
PresentsServer.registerReporter(this);
repmgr.registerReporter(this);
}
}
/**
* Will do a sophisticated shutdown of both itself and the DObjectManager
* thread.
*/
@Override // from Invoker
public void shutdown ()
{
// this will do a sophisticated shutdown of both ourself and the dobjmgr
_queue.append(new ShutdownUnit());
}
// documentation inherited from interface
public void appendReport (
StringBuilder buf, long now, long sinceLast, boolean reset)
// from interface ReportManager.Reporter
public void appendReport (StringBuilder buf, long now, long sinceLast, boolean reset)
{
buf.append("* presents.util.Invoker:\n");
int qsize = _queue.size();
@@ -93,7 +91,7 @@ public class PresentsInvoker extends Invoker
}
}
// documentation inherited
@Override // from Invoker
protected void willInvokeUnit (Unit unit, long start)
{
super.willInvokeUnit(unit, start);
@@ -111,7 +109,7 @@ public class PresentsInvoker extends Invoker
}
}
// documentation inherited
@Override // from Invoker
protected void didInvokeUnit (Unit unit, long start)
{
super.didInvokeUnit(unit, start);
@@ -124,9 +122,8 @@ public class PresentsInvoker extends Invoker
}
/**
* This unit gets posted back and forth between the invoker and DObjectMgr
* until both of their queues are empty and they can both be safely
* shutdown.
* This gets posted back and forth between the invoker and DObjectMgr until both of their
* queues are empty and they can both be safely shutdown.
*/
protected class ShutdownUnit extends Unit
{
@@ -21,13 +21,17 @@
package com.threerings.presents.server;
import java.util.ArrayList;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.samskivert.util.Interval;
import com.samskivert.util.ObserverList;
import com.samskivert.util.StringUtil;
import com.samskivert.util.Invoker;
import com.samskivert.util.RunQueue;
import com.samskivert.util.SystemInfo;
import com.threerings.presents.annotation.EventQueue;
import com.threerings.presents.annotation.MainInvoker;
import com.threerings.presents.client.Client;
import com.threerings.presents.dobj.AccessController;
import com.threerings.presents.server.net.ConnectionManager;
@@ -44,74 +48,40 @@ import static com.threerings.presents.Log.log;
*/
public class PresentsServer
{
/** Used to generate "state of the server" reports. See {@link #registerReporter}. */
public static interface Reporter
/** Configures dependencies needed by the Presents services. */
public static class Module extends AbstractModule
{
/**
* Requests that this reporter append its report to the supplied string buffer.
*
* @param buffer the string buffer to which the report text should be appended.
* @param now the time at which the report generation began, in epoch millis.
* @param sinceLast number of milliseconds since the last time we generated a report.
* @param reset if true, all accumulating stats should be reset, if false they should be
* allowed to continue to accumulate.
*/
public void appendReport (StringBuilder buffer, long now, long sinceLast, boolean reset);
@Override protected void configure () {
bind(Invoker.class).annotatedWith(MainInvoker.class).to(ServerInvoker.class);
bind(RunQueue.class).annotatedWith(EventQueue.class).to(PresentsDObjectMgr.class);
}
}
/** Implementers of this interface will be notified when the server is shutting down. */
public static interface Shutdowner
{
/**
* Called when the server is shutting down.
*/
public void shutdown ();
}
/** The manager of network connections. */
/** OBSOLETE! Don't use me. */
public static ConnectionManager conmgr;
/** The manager of clients. */
/** OBSOLETE! Don't use me. */
public static ClientManager clmgr;
/** The distributed object manager. */
/** OBSOLETE! Don't use me. */
public static PresentsDObjectMgr omgr;
/** The invocation manager. */
/** OBSOLETE! Don't use me. */
public static InvocationManager invmgr;
/** This is used to invoke background tasks that should not be allowed to tie up the
* distributed object manager thread. This is generally used to talk to databases and other
* (relatively) slow entities. */
public static PresentsInvoker invoker;
/**
* Registers an entity that will be notified when the server is shutting down.
*/
public static void registerShutdowner (Shutdowner downer)
{
_downers.add(downer);
}
/**
* Unregisters the shutdowner from hearing when the server is shutdown.
*/
public static void unregisterShutdowner (Shutdowner downer)
{
_downers.remove(downer);
}
/** OBSOLETE! Don't use me. */
public static Invoker invoker;
/**
* The default entry point for the server.
*/
public static void main (String[] args)
{
log.info("Presents server starting...");
PresentsServer server = new PresentsServer();
Injector injector = Guice.createInjector(new Module());
PresentsServer server = injector.getInstance(PresentsServer.class);
try {
// initialize the server
server.init();
server.init(injector);
// check to see if we should load and invoke a test module before running the server
String testmod = System.getProperty("test_module");
@@ -139,9 +109,16 @@ public class PresentsServer
/**
* Initializes all of the server services and prepares for operation.
*/
public void init ()
public void init (Injector injector)
throws Exception
{
// populate our legacy statics
conmgr = _conmgr;
clmgr = _clmgr;
omgr = _omgr;
invmgr = _invmgr;
invoker = _invoker;
// output general system information
SystemInfo si = new SystemInfo();
log.info("Starting up server [os=" + si.osToString() + ", jvm=" + si.jvmToString() +
@@ -150,48 +127,26 @@ public class PresentsServer
// register SIGTERM, SIGINT (ctrl-c) and a SIGHUP handlers
boolean registered = false;
try {
registered = new SunSignalHandler().init(this);
registered = injector.getInstance(SunSignalHandler.class).init();
} catch (Throwable t) {
log.warning("Unable to register Sun signal handlers [error=" + t + "].");
}
if (!registered) {
new NativeSignalHandler().init(this);
injector.getInstance(NativeSignalHandler.class).init();
}
// create our distributed object manager
omgr = createDObjectManager();
// configure the dobject manager with our access controller
omgr.setDefaultAccessController(createDefaultObjectAccessController());
_omgr.setDefaultAccessController(createDefaultObjectAccessController());
// create and start up our invoker
invoker = new PresentsInvoker(omgr) {
protected void didShutdown () {
invokerDidShutdown();
}
};
invoker.start();
// start the main invoker thread
_invoker.start();
// create our connection manager
conmgr = new ConnectionManager(getListenPorts(), getDatagramPorts());
conmgr.setAuthenticator(createAuthenticator());
// create our client manager
clmgr = createClientManager(conmgr);
// create our invocation manager
invmgr = new InvocationManager(omgr);
// configure our connection manager
_conmgr.init(getListenPorts(), getDatagramPorts());
_conmgr.setAuthenticator(createAuthenticator());
// initialize the time base services
TimeBaseProvider.init(invmgr, omgr);
// queue up an interval which will generate reports
_reportInterval = new Interval(omgr) {
public void expired () {
logReport(generateReport(System.currentTimeMillis(), true));
}
};
_reportInterval.schedule(REPORT_INTERVAL, true);
}
/**
@@ -202,21 +157,21 @@ public class PresentsServer
return new DummyAuthenticator();
}
/**
* Creates the client manager to be used on this server.
*/
protected ClientManager createClientManager (ConnectionManager conmgr)
{
return new ClientManager(conmgr);
}
// /**
// * Creates the client manager to be used on this server.
// */
// protected ClientManager createClientManager (ConnectionManager conmgr)
// {
// return new ClientManager(conmgr);
// }
/**
* Creates the distributed object manager to be used on this server.
*/
protected PresentsDObjectMgr createDObjectManager ()
{
return new PresentsDObjectMgr();
}
// /**
// * Creates the distributed object manager to be used on this server.
// */
// protected PresentsDObjectMgr createDObjectManager ()
// {
// return new PresentsDObjectMgr();
// }
/**
* Defines the default object access policy for all {@link DObject} instances. The default
@@ -253,119 +208,26 @@ public class PresentsServer
omgr.postRunnable(new Runnable() {
public void run () {
// start up the connection manager
conmgr.start();
_conmgr.start();
}
});
// invoke the dobjmgr event loop
omgr.run();
}
/**
* A report is generated by the presents server periodically in which server entities can
* participate by registering a {@link Reporter} with this method.
*/
public static void registerReporter (Reporter reporter)
{
_reporters.add(reporter);
}
/**
* Generates a report for all system services registered as a {@link Reporter}.
*/
public static String generateReport ()
{
return generateReport(System.currentTimeMillis(), false);
}
/**
* Generates and logs a "state of server" report.
*/
protected static String generateReport (long now, boolean reset)
{
long sinceLast = now - _lastReportStamp;
long uptime = now - _serverStartTime;
StringBuilder report = new StringBuilder("State of server report:\n");
report.append("- Uptime: ");
report.append(StringUtil.intervalToString(uptime)).append("\n");
report.append("- Report period: ");
report.append(StringUtil.intervalToString(sinceLast)).append("\n");
// report on the state of memory
Runtime rt = Runtime.getRuntime();
long total = rt.totalMemory(), max = rt.maxMemory();
long used = (total - rt.freeMemory());
report.append("- Memory: ").append(used/1024).append("k used, ");
report.append(total/1024).append("k total, ");
report.append(max/1024).append("k max\n");
for (int ii = 0; ii < _reporters.size(); ii++) {
Reporter rptr = _reporters.get(ii);
try {
rptr.appendReport(report, now, sinceLast, reset);
} catch (Throwable t) {
log.warning("Reporter choked [rptr=" + rptr + "].", t);
}
}
/* The following Interval debug methods are no longer supported,
* but they could be added back easily if needed.
report.append("* samskivert.Interval:\n");
report.append("- Registered intervals: ");
report.append(Interval.registeredIntervalCount());
report.append("\n- Fired since last report: ");
report.append(Interval.getAndClearFiredIntervals());
report.append("\n");
*/
// strip off the final newline
int blen = report.length();
if (report.charAt(blen-1) == '\n') {
report.delete(blen-1, blen);
}
// only reset the last report time if this is a periodic report
if (reset) {
_lastReportStamp = now;
}
return report.toString();
}
/**
* Logs the state of the server report via the default logging mechanism. Derived classes may
* wish to log the state of the server report via a different means.
*/
protected void logReport (String report)
{
log.info(report);
}
/**
* Requests that the server shut down. All registered shutdown participants will be shut down,
* following which the server process will be terminated.
*/
public void shutdown ()
{
ObserverList<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;
}
});
// shutdown all registered shutdowners
_shutmgr.shutdown();
// shut down the connection manager (this will cease all network activity but not actually
// close the connections)
if (conmgr.isRunning()) {
conmgr.shutdown();
if (_conmgr.isRunning()) {
_conmgr.shutdown();
}
// finally shut down the invoker and dobj manager (The invoker does both for us.)
@@ -395,22 +257,39 @@ public class PresentsServer
{
}
/** Our interval that generates "state of server" reports. */
protected Interval _reportInterval;
/** Integrates the main invoker thread with the distributed object thread so that they can
* coordinate the shutdown process to ensure that all (barring infinite loops) invoker units
* and distributed object events are processed before the server shuts down. */
protected static class ServerInvoker extends PresentsInvoker
{
@Inject public ServerInvoker (PresentsDObjectMgr omgr, ReportManager repmgr) {
super(omgr, repmgr);
}
@Override protected void didShutdown () {
_server.invokerDidShutdown();
}
@Inject protected PresentsServer _server;
}
/** The time at which the server was started. */
protected static long _serverStartTime = System.currentTimeMillis();
/** The manager of distributed objects. */
@Inject protected PresentsDObjectMgr _omgr;
/** The last time at which {@link #generateReport} was run. */
protected static long _lastReportStamp = _serverStartTime;
/** The manager of network connections. */
@Inject protected ConnectionManager _conmgr;
/** Used to generate "state of server" reports. */
protected static ArrayList<Reporter> _reporters = new ArrayList<Reporter>();
/** The manager of clients. */
@Inject protected ClientManager _clmgr;
/** A list of shutdown participants. */
protected static ObserverList<Shutdowner> _downers =
new ObserverList<Shutdowner>(ObserverList.SAFE_IN_ORDER_NOTIFY);
/** The manager of invocation services. */
@Inject protected InvocationManager _invmgr;
/** The frequency with which we generate "state of server" reports. */
protected static final long REPORT_INTERVAL = 15 * 60 * 1000L;
/** Handles orderly shutdown of our managers, etc. */
@Inject protected ShutdownManager _shutmgr;
/** Handles generation of state of the server reports. */
@Inject protected ReportManager _repmgr;
/** Used to invoke background tasks that should not be allowed to tie up the distributed object
* manager thread (generally talking to databases and other relatively slow entities). */
@Inject @MainInvoker protected Invoker _invoker;
}
@@ -21,6 +21,9 @@
package com.threerings.presents.server;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.samskivert.io.PersistenceException;
import com.samskivert.util.Invoker;
@@ -39,12 +42,12 @@ import static com.threerings.presents.Log.log;
*/
public class Rejector extends PresentsServer
{
// documentation inherited
public void init ()
@Override // from PresentsServer
public void init (Injector injector)
throws Exception
{
super.init();
conmgr.setAuthenticator(new RejectingAuthenticator());
super.init(injector);
_conmgr.setAuthenticator(new RejectingAuthenticator());
}
// documentation inherited
@@ -68,9 +71,10 @@ public class Rejector extends PresentsServer
_errmsg = MessageBundle.tcompose(_errmsg, eargs);
}
Rejector server = new Rejector();
Injector injector = Guice.createInjector(new Module());
Rejector server = injector.getInstance(Rejector.class);
try {
server.init();
server.init(injector);
server.run();
} catch (Exception e) {
log.warning("Unable to initialize server.", e);
@@ -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.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.util.ArrayList;
import java.util.List;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import com.samskivert.util.*;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.samskivert.util.IntMap;
import com.samskivert.util.IntMaps;
import com.samskivert.util.LoopingThread;
import com.samskivert.util.Queue;
import com.samskivert.util.ResultListener;
import com.samskivert.util.StringUtil;
import com.samskivert.util.Tuple;
import com.threerings.io.FramingOutputStream;
import com.threerings.io.ObjectOutputStream;
@@ -53,7 +65,8 @@ import com.threerings.presents.net.DownstreamMessage;
import com.threerings.presents.util.DatagramSequencer;
import com.threerings.presents.server.Authenticator;
import com.threerings.presents.server.PresentsServer;
import com.threerings.presents.server.PresentsDObjectMgr;
import com.threerings.presents.server.ReportManager;
import static com.threerings.presents.Log.log;
@@ -63,38 +76,36 @@ import static com.threerings.presents.Log.log;
* closely with the connection manager because network I/O is done via a poll()-like mechanism
* rather than via threads.
*/
@Singleton
public class ConnectionManager extends LoopingThread
implements PresentsServer.Reporter
implements ReportManager.Reporter
{
/**
* Constructs and initialized a connection manager (binding the socket on which it will listen
* for client connections).
* Creates a connection manager instance. Don't call this, Guice will do it for you.
*/
public ConnectionManager (int port)
throws IOException
{
this(new int[] { port });
}
/**
* Constructs and initialized a connection manager (binding socket on which it will listen for
* client connections to each of the specified ports).
*/
public ConnectionManager (int[] ports)
throws IOException
{
this(ports, new int[0]);
}
/**
* Constructs and initialized a connection manager (binding socket on which it will listen for
* client connections to each of the specified ports).
*/
public ConnectionManager (int[] ports, int[] datagramPorts)
throws IOException
@Inject public ConnectionManager (ReportManager repmgr)
{
super("ConnectionManager");
repmgr.registerReporter(this);
}
/**
* Constructs and initialized a connection manager (binding socket on which it will listen for
* client connections to each of the specified ports).
*/
public void init (int[] ports)
throws IOException
{
init(ports, new int[0]);
}
/**
* Constructs and initialized a connection manager (binding socket on which it will listen for
* client connections to each of the specified ports).
*/
public void init (int[] ports, int[] datagramPorts)
throws IOException
{
_ports = ports;
_datagramPorts = datagramPorts;
_selector = SelectorProvider.provider().openSelector();
@@ -102,9 +113,6 @@ public class ConnectionManager extends LoopingThread
// create our stats record
_stats = new ConMgrStats();
_lastStats = new ConMgrStats();
// register as a "state of server" reporter
PresentsServer.registerReporter(this);
}
/**
@@ -207,7 +215,7 @@ public class ConnectionManager extends LoopingThread
_authq.append(conn);
}
// documentation inherited from interface PresentsServer.Reporter
// documentation inherited from interface ReportManager.Reporter
public void appendReport (
StringBuilder report, long now, long sinceLast, boolean reset)
{
@@ -842,7 +850,7 @@ public class ConnectionManager extends LoopingThread
}
// more sanity check; messages must only be posted from the dobjmgr thread
if (!PresentsServer.omgr.isDispatchThread()) {
if (!_omgr.isDispatchThread()) {
log.warning("Message posted on non-distributed object thread [conn=" + conn +
", msg=" + msg + ", thread=" + Thread.currentThread() + "].");
Thread.dumpStack();
@@ -1029,6 +1037,15 @@ public class ConnectionManager extends LoopingThread
protected int _msgs, _partials;
}
/** Used to create an overflow queue on the first partial write. */
protected PartialWriteHandler _oflowHandler = new PartialWriteHandler() {
public void handlePartialWrite (Connection conn, ByteBuffer msgbuf) {
// if we couldn't write all the data for this message, we'll need to establish an
// overflow queue
_oflowqs.put(conn, new OverflowQueue(conn, msgbuf));
}
};
protected int[] _ports, _datagramPorts;
protected Authenticator _author;
protected Selector _selector;
@@ -1040,11 +1057,10 @@ public class ConnectionManager extends LoopingThread
protected int _runtimeExceptionCount;
/** Maps selection keys to network event handlers. */
protected HashMap<SelectionKey,NetEventHandler> _handlers =
new HashMap<SelectionKey,NetEventHandler>();
protected Map<SelectionKey,NetEventHandler> _handlers = Maps.newHashMap();
/** 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<AuthingConnection> _authq = new Queue<AuthingConnection>();
@@ -1056,9 +1072,9 @@ public class ConnectionManager extends LoopingThread
protected ByteBuffer _outbuf = ByteBuffer.allocateDirect(64 * 1024);
protected ByteBuffer _databuf = ByteBuffer.allocateDirect(Client.MAX_DATAGRAM_SIZE);
protected HashMap<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. */
protected long _bytesIn, _bytesOut;
@@ -1075,14 +1091,8 @@ public class ConnectionManager extends LoopingThread
/** A runnable to execute when the connection manager thread exits. */
protected volatile Runnable _onExit;
/** Used to create an overflow queue on the first partial write. */
protected PartialWriteHandler _oflowHandler = new PartialWriteHandler() {
public void handlePartialWrite (Connection conn, ByteBuffer msgbuf) {
// if we couldn't write all the data for this message, we'll need to establish an
// overflow queue
_oflowqs.put(conn, new OverflowQueue(conn, msgbuf));
}
};
// injected dependencies
@Inject protected PresentsDObjectMgr _omgr;
/** How long we wait for network events before checking our running flag to see if we should
* still be running. We don't want to loop too tightly, but we need to make sure we don't sit
+3 -68
View File
@@ -129,73 +129,8 @@
<move overwrite="true" file="../etc/empty.abc" tofile="${deploy.dir}/testslib.abc"/>
</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 -->
<target name="test" depends="compile,cbundles,tsbundles"
<target name="test" depends="compile"
description="Run the tests.">
<junit printsummary="no" haltonfailure="yes" fork="${junit.fork}">
<classpath refid="classpath"/>
@@ -233,7 +168,8 @@
</import>
<filespec dir="src/as" includes="com/threerings/bureau/client/TestClientMain.as"/>
</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 name="bureau-runserver" depends="compile"
@@ -243,7 +179,6 @@
</java>
</target>
<target name="bureau-testregistry" depends="compile"
description="Run the bureau test server and tests the registry.">
<java fork="true" classname="com.threerings.bureau.server.RegistryTester">
@@ -25,9 +25,14 @@ import java.util.List;
import java.util.Random;
import com.google.common.collect.Lists;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.threerings.bureau.data.AgentObject;
import com.threerings.presents.server.ShutdownManager;
import static com.threerings.bureau.Log.log;
/**
@@ -59,11 +64,12 @@ public class RegistryTester
*/
public static void main (String[] args)
{
TestServer server = new TestServer();
RegistryTester tester = new RegistryTester(server);
Injector injector = Guice.createInjector(new TestServer.Module());
TestServer server = injector.getInstance(TestServer.class);
RegistryTester tester = injector.getInstance(RegistryTester.class);
try {
server.init();
server.init(injector);
tester.start();
server.run();
@@ -75,7 +81,7 @@ public class RegistryTester
/**
* Creates a new registry tester.
*/
public RegistryTester (TestServer server)
@Inject public RegistryTester (TestServer server, ShutdownManager shutmgr)
{
_server = server;
@@ -92,7 +98,7 @@ public class RegistryTester
// stop the tests when the server shuts down
// TODO: this is not called on Ctrl-C, need a way to shut down gracefully
TestServer.registerShutdowner(new TestServer.Shutdowner() {
shutmgr.registerShutdowner(new ShutdownManager.Shutdowner() {
public void shutdown () {
log.info("Shutting down tests");
_stop = true;
@@ -21,6 +21,9 @@
package com.threerings.bureau.server;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.threerings.presents.server.PresentsServer;
import static com.threerings.bureau.Log.log;
@@ -40,9 +43,10 @@ public class TestServer extends PresentsServer
*/
public static void main (String[] args)
{
final TestServer server = new TestServer();
Injector injector = Guice.createInjector(new Module());
TestServer server = injector.getInstance(TestServer.class);
try {
server.init();
server.init(injector);
setClientTarget("bureau-runclient");
server.run();
@@ -51,20 +55,20 @@ public class TestServer extends PresentsServer
}
}
// inherit documentation - from PresentsServer
public void init ()
@Override // from PresentsServer
public void init (Injector injector)
throws Exception
{
super.init();
super.init(injector);
breg = new BureauRegistry("localhost:47624", invmgr, omgr, invoker);
}
static public void setClientTarget (String target)
public static void setClientTarget (String target)
{
breg.setCommandGenerator("test", antCommandGenerator(target));
}
static public BureauRegistry.CommandGenerator antCommandGenerator (
public static BureauRegistry.CommandGenerator antCommandGenerator (
final String target)
{
return new BureauRegistry.CommandGenerator() {
@@ -3,6 +3,9 @@
package com.threerings.crowd.server;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.threerings.crowd.data.JabberConfig;
import com.threerings.crowd.data.PlaceObject;
@@ -15,10 +18,10 @@ import static com.threerings.crowd.Log.log;
public class JabberServer extends CrowdServer
{
// documentation inherited
public void init ()
public void init (Injector injector)
throws Exception
{
super.init();
super.init(injector);
// create a single location
_pmgr = plreg.createPlace(new JabberConfig());
@@ -26,9 +29,11 @@ public class JabberServer extends CrowdServer
public static void main (String[] args)
{
JabberServer server = new JabberServer();
Injector injector = Guice.createInjector(new Module());
JabberServer server = injector.getInstance(JabberServer.class);
try {
server.init();
server.init(injector);
server.run();
} catch (Exception e) {
log.warning("Unable to initialize server.", e);
@@ -21,43 +21,29 @@
package com.threerings.presents.server;
import junit.framework.Test;
import junit.framework.TestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import atunit.AtUnit;
import atunit.Container;
import atunit.Unit;
import com.google.inject.Inject;
import com.threerings.presents.data.TestObject;
import com.threerings.presents.dobj.*;
import static org.junit.Assert.*;
/**
* A simple test case for the dobjmgr.
*/
public class DOMTest extends TestCase
@RunWith(AtUnit.class)
@Container(Container.Option.GUICE)
public class DOMTest
implements AttributeChangeListener, ElementUpdateListener
{
public DOMTest ()
{
super(DOMTest.class.getName());
}
public void attributeChanged (AttributeChangedEvent event)
{
assertTrue(fields[_fcount] + " == " + values[_fcount],
event.getName().equals(fields[_fcount]) &&
event.getValue().equals(values[_fcount]));
// shutdown once we receive our last update
if (++_fcount == fields.length) {
_omgr.harshShutdown();
}
}
public void elementUpdated (ElementUpdatedEvent event)
{
// Log.info("Element updated " + event);
// Log.info(StringUtil.toString(_test.ints));
// Log.info(StringUtil.toString(_test.strings));
}
public void runTest ()
@Test public void runTest ()
{
// request that a new TestObject be registered
_test = _omgr.registerObject(new TestObject());
@@ -87,27 +73,35 @@ public class DOMTest extends TestCase
_omgr.run();
}
public static Test suite ()
// from interface AttributeChangeListener
public void attributeChanged (AttributeChangedEvent event)
{
return new DOMTest();
assertTrue(fields[_fcount] + " == " + values[_fcount],
event.getName().equals(fields[_fcount]) &&
event.getValue().equals(values[_fcount]));
// shutdown once we receive our last update
if (++_fcount == fields.length) {
_omgr.harshShutdown();
}
}
public static void main (String[] args)
// from interface ElementUpdateListener
public void elementUpdated (ElementUpdatedEvent event)
{
DOMTest test = new DOMTest();
test.runTest();
// Log.info("Element updated " + event);
// Log.info(StringUtil.toString(_test.ints));
// Log.info(StringUtil.toString(_test.strings));
}
protected int _fcount = 0;
protected TestObject _test;
// the fields that will change in attribute changed events
protected Object[] fields = {
TestObject.FOO, TestObject.BAR, TestObject.FOO, TestObject.BAR };
protected Object[] fields = { TestObject.FOO, TestObject.BAR, TestObject.FOO, TestObject.BAR };
// the values we'll receive via attribute changed events
protected Object[] values = {
new Integer(99), "hoopie", new Integer(25), "howdy" };
protected Object[] values = { new Integer(99), "hoopie", new Integer(25), "howdy" };
protected static PresentsDObjectMgr _omgr = new PresentsDObjectMgr();
@Inject @Unit protected PresentsDObjectMgr _omgr;
}
@@ -21,60 +21,37 @@
package com.threerings.presents.server;
import junit.framework.Test;
import junit.framework.TestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import atunit.AtUnit;
import atunit.Container;
import atunit.Unit;
import com.google.inject.Inject;
import com.threerings.presents.data.TestObject;
import com.threerings.presents.dobj.*;
import static org.junit.Assert.*;
import static com.threerings.presents.Log.log;
/**
* Tests that the dobjmgr will not allow a destroyed object to be added to
* an oid list.
*/
public class DestroyedRefTest extends TestCase
implements EventListener
@RunWith(AtUnit.class)
@Container(Container.Option.GUICE)
public class DestroyedRefTest
{
public static Test suite ()
{
return new DestroyedRefTest();
}
public DestroyedRefTest ()
{
super(DestroyedRefTest.class.getName());
}
public void eventReceived (DEvent event)
{
int toid = event.getTargetOid();
// when we get the attribute change, we can exit
if (event instanceof ObjectDestroyedEvent) {
log.info("The upcoming object added event should be rejected.");
} else if (event instanceof ObjectAddedEvent &&
toid == _objtwo.getOid()) {
assertTrue("list should contain only one oid",
_objtwo.list.size() == 1);
} else if (event instanceof AttributeChangedEvent) {
// go bye bye
_omgr.harshShutdown();
} else {
fail("Got unexpected event: " + event);
}
}
public void runTest ()
@Test public void runTest ()
{
// create two test objects
_objone = _omgr.registerObject(new TestObject());
_objone.addListener(this);
_objone.addListener(_listener);
_objtwo = _omgr.registerObject(new TestObject());
_objtwo.addListener(this);
_objtwo.addListener(_listener);
// add object one to object two twice in a row to make sure repeated
// adds don't result in the object being listed twice
@@ -95,7 +72,30 @@ public class DestroyedRefTest extends TestCase
_omgr.run();
}
protected EventListener _listener = new EventListener() {
public void eventReceived (DEvent event) {
int toid = event.getTargetOid();
// when we get the attribute change, we can exit
if (event instanceof ObjectDestroyedEvent) {
log.info("The upcoming object added event should be rejected.");
} else if (event instanceof ObjectAddedEvent &&
toid == _objtwo.getOid()) {
assertTrue("list should contain only one oid",
_objtwo.list.size() == 1);
} else if (event instanceof AttributeChangedEvent) {
// go bye bye
_omgr.harshShutdown();
} else {
fail("Got unexpected event: " + event);
}
}
};
protected TestObject _objone, _objtwo;
protected static PresentsDObjectMgr _omgr = new PresentsDObjectMgr();
@Inject @Unit protected PresentsDObjectMgr _omgr;
}
@@ -21,65 +21,34 @@
package com.threerings.presents.server;
import junit.framework.Test;
import junit.framework.TestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import atunit.AtUnit;
import atunit.Container;
import atunit.Unit;
import com.google.inject.Inject;
import com.threerings.presents.data.TestObject;
import com.threerings.presents.dobj.*;
import static org.junit.Assert.*;
/**
* Tests the oid list reference tracking code.
*/
public class RefTest extends TestCase
implements EventListener
@RunWith(AtUnit.class)
@Container(Container.Option.GUICE)
public class RefTest
{
public static Test suite ()
{
return new RefTest();
}
public RefTest ()
{
super(RefTest.class.getName());
}
public void eventReceived (DEvent event)
{
// Log.info("Got event: " + event);
int toid = event.getTargetOid();
// once we receive the second object added we can destroy the
// target object to see if the reference is cleaned up
if (event instanceof ObjectAddedEvent &&
toid == _objtwo.getOid()) {
// Log.info("Destroying object two " + _objtwo + ".");
_objtwo.destroy();
} else if (event instanceof ObjectDestroyedEvent) {
if (toid == _objtwo.getOid()) {
// Log.info("List won't yet be empty: " + _objone.list);
assertTrue("List not empty", _objone.list.size() > 0);
} else {
// Log.info("Other object destroyed.");
// go bye bye
_omgr.harshShutdown();
}
} else if (event instanceof ObjectRemovedEvent) {
// Log.info("List should be empty: " + _objone.list);
assertTrue("List empty", _objone.list.size() == 0);
// finally destroy the other object to complete the circle
_objone.destroy();
}
}
public void runTest ()
@Test public void runTest ()
{
// create two test objects
_objone = _omgr.registerObject(new TestObject());
_objone.addListener(this);
_objone.addListener(_listener);
_objtwo = _omgr.registerObject(new TestObject());
_objtwo.addListener(this);
_objtwo.addListener(_listener);
// now that we have both objects, set up the references
_objone.addToList(_objtwo.getOid());
@@ -89,8 +58,39 @@ public class RefTest extends TestCase
_omgr.run();
}
protected EventListener _listener = new EventListener() {
public void eventReceived (DEvent event) {
// Log.info("Got event: " + event);
int toid = event.getTargetOid();
// once we receive the second object added we can destroy the
// target object to see if the reference is cleaned up
if (event instanceof ObjectAddedEvent &&
toid == _objtwo.getOid()) {
// Log.info("Destroying object two " + _objtwo + ".");
_objtwo.destroy();
} else if (event instanceof ObjectDestroyedEvent) {
if (toid == _objtwo.getOid()) {
// Log.info("List won't yet be empty: " + _objone.list);
assertTrue("List not empty", _objone.list.size() > 0);
} else {
// Log.info("Other object destroyed.");
// go bye bye
_omgr.harshShutdown();
}
} else if (event instanceof ObjectRemovedEvent) {
// Log.info("List should be empty: " + _objone.list);
assertTrue("List empty", _objone.list.size() == 0);
// finally destroy the other object to complete the circle
_objone.destroy();
}
}
};
protected TestObject _objone;
protected TestObject _objtwo;
protected static PresentsDObjectMgr _omgr = new PresentsDObjectMgr();
@Inject @Unit protected PresentsDObjectMgr _omgr;
}
@@ -21,6 +21,9 @@
package com.threerings.presents.server;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.threerings.presents.data.TestObject;
import com.threerings.presents.dobj.*;
@@ -30,13 +33,13 @@ public class TestServer extends PresentsServer
{
public static TestObject testobj;
public void init ()
public void init (Injector injector)
throws Exception
{
super.init();
super.init(injector);
// register our test provider
invmgr.registerDispatcher(new TestDispatcher(new TestManager()), "test");
_invmgr.registerDispatcher(new TestDispatcher(new TestManager()), "test");
// create a test object
testobj = omgr.registerObject(new TestObject());
@@ -48,9 +51,10 @@ public class TestServer extends PresentsServer
public static void main (String[] args)
{
TestServer server = new TestServer();
Injector injector = Guice.createInjector(new Module());
TestServer server = injector.getInstance(TestServer.class);
try {
server.init();
server.init(injector);
server.run();
} catch (Exception e) {
log.warning("Unable to initialize server.", e);