diff --git a/docs/presents/notes.txt b/docs/presents/notes.txt
index 841a15523..ee9ef89f6 100644
--- a/docs/presents/notes.txt
+++ b/docs/presents/notes.txt
@@ -3,8 +3,6 @@ Cher Notes -*- outline -*-
* TODO
Pass cause back to client somehow via FailureResponse in Client.requestFailed
-Finish wiring up oid list tracking.
-
Ensure at time of OBJECT_ADDED that the object being added exists and has
not been destroyed.
@@ -12,10 +10,6 @@ clientWillLogff becomes clientMayLogoff?
Look into nbio waking up all sockets when any data comes in.
-Wire up Client.sessionDidTerminate; do session termination in general.
-
-Clear out subscriptions when we lose connection from the client.
-
* Server-side event concentrator
The client objects will not subscribe directly, but will subscribe through
the concentrator so that, at least, it can create a single
diff --git a/src/java/com/threerings/presents/dobj/DObject.java b/src/java/com/threerings/presents/dobj/DObject.java
index 081741af7..3764eba81 100644
--- a/src/java/com/threerings/presents/dobj/DObject.java
+++ b/src/java/com/threerings/presents/dobj/DObject.java
@@ -1,5 +1,5 @@
//
-// $Id: DObject.java,v 1.20 2001/08/07 20:38:58 mdb Exp $
+// $Id: DObject.java,v 1.21 2001/08/08 00:28:49 mdb Exp $
package com.threerings.cocktail.cher.dobj;
@@ -165,7 +165,7 @@ public class DObject
// scan the lock array to see if this lock is already acquired
int slot = -1;
for (int i = 0; i < _locks.length; i++) {
- if (_locks[i] == null) {
+ if (_locks[i] == null && slot == -1) {
// keep track of this for later
slot = i;
} else if (name.equals(_locks[i])) {
@@ -343,6 +343,17 @@ public class DObject
}
}
+ /**
+ * Returns true if this object is active and registered with the
+ * distributed object system. If an object is created via
+ * DObjectManager.createObject it will be active until
+ * such time as it is destroyed.
+ */
+ public boolean isActive ()
+ {
+ return _mgr != null;
+ }
+
/**
* Don't call this function! It initializes this distributed object
* with the supplied distributed object manager. This is called by the
diff --git a/src/java/com/threerings/presents/server/PresentsDObjectMgr.java b/src/java/com/threerings/presents/server/PresentsDObjectMgr.java
index ce3b4af18..8fc622200 100644
--- a/src/java/com/threerings/presents/server/PresentsDObjectMgr.java
+++ b/src/java/com/threerings/presents/server/PresentsDObjectMgr.java
@@ -1,9 +1,9 @@
//
-// $Id: PresentsDObjectMgr.java,v 1.11 2001/08/07 21:25:13 mdb Exp $
+// $Id: PresentsDObjectMgr.java,v 1.12 2001/08/08 00:28:49 mdb Exp $
package com.threerings.cocktail.cher.server;
-import java.lang.reflect.Method;
+import java.lang.reflect.*;
import java.util.HashMap;
import com.samskivert.util.Queue;
@@ -172,9 +172,191 @@ public class CherDObjectMgr implements DObjectManager
*/
public void objectDestroyed (DEvent event, DObject target)
{
- // Log.info("Removing destroyed object from table " +
- // "[oid=" + target.getOid() + "].");
- _objects.remove(target.getOid());
+ 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 = (Reference[])_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 = (DObject)_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 + "].");
+ }
+ }
+ }
+
+ /**
+ * Called by objectDestroyed; clears out the tracking
+ * info for a reference by the supplied object to the specified oid
+ * via the specified field.
+ */
+ protected void clearReference (
+ DObject reffer, String field, int reffedOid)
+ {
+ // look up the reference vector for the referenced object
+ Reference[] refs = (Reference[])_refs.get(reffedOid);
+ Reference ref = null;
+
+ if (refs != null) {
+ for (int i = 0; i < refs.length; i++) {
+ if (refs[i].equals(reffer.getOid(), field)) {
+ ref = refs[i];
+ refs[i] = null;
+ break;
+ }
+ }
+ }
+
+ if (ref == null) {
+ Log.warning("Requested to clear out non-existent reference " +
+ "[refferOid=" + reffer.getOid() +
+ ", field=" + field +
+ ", reffedOid=" + reffedOid + "].");
+
+// } else {
+// Log.info("Cleared out reference " + ref + ".");
+ }
+ }
+
+ /**
+ * Called as a helper for ObjectAddedEvent events. It
+ * updates the object/oid list tracking structures.
+ */
+ public void objectAdded (DEvent event, DObject target)
+ {
+ ObjectAddedEvent oae = (ObjectAddedEvent)event;
+ int oid = oae.getOid();
+ Reference ref = new Reference(target.getOid(), oae.getName(), oid);
+
+ // 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 = (Reference[])_refs.get(oid);
+ if (refs == null) {
+ refs = new Reference[DEFREFVEC_SIZE];
+ _refs.put(oid, refs);
+ }
+
+ // determine where to add the reference
+ 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;
+ } 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 + ".");
+ }
+
+ /**
+ * Called as a helper for ObjectRemovedEvent events. It
+ * updates the object/oid list tracking structures.
+ */
+ public void objectRemoved (DEvent event, DObject target)
+ {
+ ObjectRemovedEvent ore = (ObjectRemovedEvent)event;
+ String field = ore.getName();
+ int toid = target.getOid();
+ int oid = ore.getOid();
+
+ // get the reference vector for the referenced object
+ Reference[] refs = (Reference[])_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.warning("Object removed without reference to track it " +
+// "[toid=" + toid + ", field=" + field +
+// ", oid=" + oid + "].");
+ return;
+ }
+
+ // look for the matching reference
+ for (int i = 0; i < refs.length; i++) {
+ if (refs[i].equals(toid, field)) {
+// Log.info("Removed reference " + refs[i] + ".");
+ refs[i] = null;
+ return;
+ }
+ }
+
+ Log.warning("Unable to locate reference for removal " +
+ "[reffingOid=" + toid + ", field=" + field +
+ ", reffedOid=" + oid + "].");
}
protected synchronized boolean isRunning ()
@@ -224,7 +406,7 @@ public class CherDObjectMgr implements DObjectManager
// insert it into the table
_objects.put(oid, obj);
- // Log.info("Created object [obj=" + obj + "].");
+// Log.info("Created object [obj=" + obj + "].");
if (_target != null) {
// add the subscriber to this object's subscriber list
@@ -351,17 +533,74 @@ public class CherDObjectMgr implements DObjectManager
method = omgrcl.getMethod("objectDestroyed", ptypes);
_helpers.put(ObjectDestroyedEvent.class, method);
+ method = omgrcl.getMethod("objectAdded", ptypes);
+ _helpers.put(ObjectAddedEvent.class, method);
+
+ method = omgrcl.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.
+ */
+ protected static class Reference
+ {
+ public int reffingOid;
+ public String field;
+ public int reffedOid;
+
+ public Reference (int reffingOid, String field, int reffedOid)
+ {
+ this.reffingOid = reffingOid;
+ this.field = field;
+ this.reffedOid = reffedOid;
+ }
+
+ public boolean equals (Reference other)
+ {
+ if (other == null) {
+ return false;
+ } else {
+ return (reffingOid == other.reffingOid &&
+ field.equals(other.field));
+ }
+ }
+
+ public boolean equals (int reffingOid, String field)
+ {
+ return (this.reffingOid == reffingOid && this.field.equals(field));
+ }
+
+ public String toString ()
+ {
+ return "[reffingOid=" + reffingOid + ", field=" + field +
+ ", reffedOid=" + reffedOid + "]";
+ }
+ }
+
+ /** A flag indicating that the event dispatcher is still running. */
protected boolean _running = true;
+
+ /** The event queue via which all events are processed. */
protected Queue _evqueue = new Queue();
+
+ /** The managed distributed objects table. */
protected IntMap _objects = new IntMap();
+
+ /** Used to assign a unique oid to each distributed object. */
protected int _nextOid = 0;
+ /** Used to track oid list references of distributed objects. */
+ protected IntMap _refs = new IntMap();
+
+ /** 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.
diff --git a/src/java/com/threerings/presents/server/PresentsServer.java b/src/java/com/threerings/presents/server/PresentsServer.java
index 06eba0712..682275ab4 100644
--- a/src/java/com/threerings/presents/server/PresentsServer.java
+++ b/src/java/com/threerings/presents/server/PresentsServer.java
@@ -1,5 +1,5 @@
//
-// $Id: PresentsServer.java,v 1.9 2001/07/19 21:29:01 mdb Exp $
+// $Id: PresentsServer.java,v 1.10 2001/08/08 00:28:49 mdb Exp $
package com.threerings.cocktail.cher.server;
@@ -132,15 +132,49 @@ public class CherServer
// start up the connection manager
conmgr.start();
// invoke the dobjmgr event loop
- ((CherDObjectMgr)omgr).run();
+ omgr.run();
+ }
+
+ /**
+ * Requests that the server shut down.
+ */
+ public static void shutdown ()
+ {
+ // shut down our managers
+ authmgr.shutdown();
+ conmgr.shutdown();
+ omgr.shutdown();
}
public static void main (String[] args)
{
+ Log.info("Cher server starting...");
+
CherServer server = new CherServer();
try {
+ // initialize the server
server.init();
+
+ // check to see if we should load and invoke a test module
+ // before running the server
+ String testmod = System.getProperty("test_module");
+ if (testmod != null) {
+ try {
+ Log.info("Invoking test module [mod=" + testmod + "].");
+ Class tmclass = Class.forName(testmod);
+ Runnable trun = (Runnable)tmclass.newInstance();
+ trun.run();
+ } catch (Exception e) {
+ Log.warning("Unable to invoke test module " +
+ "[mod=" + testmod + "].");
+ Log.logStackTrace(e);
+ }
+ }
+
+ // start the server to running (this method call won't return
+ // until the server is shut down)
server.run();
+
} catch (Exception e) {
Log.warning("Unable to initialize server.");
Log.logStackTrace(e);
diff --git a/src/java/com/threerings/presents/server/net/AuthManager.java b/src/java/com/threerings/presents/server/net/AuthManager.java
index 8584a9412..bc13013be 100644
--- a/src/java/com/threerings/presents/server/net/AuthManager.java
+++ b/src/java/com/threerings/presents/server/net/AuthManager.java
@@ -1,5 +1,5 @@
//
-// $Id: AuthManager.java,v 1.3 2001/06/01 22:12:03 mdb Exp $
+// $Id: AuthManager.java,v 1.4 2001/08/08 00:28:49 mdb Exp $
package com.threerings.cocktail.cher.server.net;
@@ -55,8 +55,16 @@ public class AuthManager extends LoopingThread
protected void iterate ()
{
// grab the next authing connection from the queue
- AuthingConnection aconn = (AuthingConnection)_authq.get();
+ Object item = _authq.get();
+ // if we're being kicked and requested to exit, we'll just post
+ // some bogus item on the queue to wake up the auth manager and
+ // get him the hell out of dodge
+ if (!(item instanceof AuthingConnection)) {
+ return;
+ }
+
+ AuthingConnection aconn = (AuthingConnection)item;
try {
// instruct the authenticator to process the auth request
AuthResponse rsp = _author.process(aconn.getAuthRequest());
@@ -74,6 +82,12 @@ public class AuthManager extends LoopingThread
}
}
+ protected void kick ()
+ {
+ // we post something bogus to the queue to wake up the authmgr
+ _authq.append(new Integer(0));
+ }
+
protected Authenticator _author;
protected ConnectionManager _conmgr;
protected Queue _authq = new Queue();
diff --git a/tests/src/java/com/threerings/presents/server/RefTest.java b/tests/src/java/com/threerings/presents/server/RefTest.java
new file mode 100644
index 000000000..bafb69249
--- /dev/null
+++ b/tests/src/java/com/threerings/presents/server/RefTest.java
@@ -0,0 +1,73 @@
+//
+// $Id: RefTest.java,v 1.1 2001/08/08 00:28:49 mdb Exp $
+
+package com.threerings.cocktail.cher.server.test;
+
+import com.threerings.cocktail.cher.Log;
+import com.threerings.cocktail.cher.dobj.*;
+import com.threerings.cocktail.cher.server.CherServer;
+
+/**
+ * Tests the oid list reference tracking code.
+ */
+public class RefTest
+ implements Runnable, Subscriber
+{
+ public void run ()
+ {
+ // create two test objects
+ CherServer.omgr.createObject(TestObject.class, this, true);
+ CherServer.omgr.createObject(TestObject.class, this, true);
+ }
+
+ public void objectAvailable (DObject object)
+ {
+ // keep references to our test objects
+ if (_objone == null) {
+ _objone = (TestObject)object;
+
+ } else {
+ _objtwo = (TestObject)object;
+
+ // now that we have both objects, set up the references
+ _objone.addToList(_objtwo.getOid());
+ _objtwo.addToList(_objone.getOid());
+ }
+ }
+
+ public void requestFailed (int oid, ObjectAccessException cause)
+ {
+ Log.warning("Ack. Unable to create object [cause=" + cause + "].");
+ }
+
+ public boolean handleEvent (DEvent event, DObject target)
+ {
+ // Log.info("Got event: " + event);
+
+ // 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 && target == _objtwo) {
+ Log.info("Destroying object two " + _objtwo + ".");
+ _objtwo.destroy();
+
+ } else if (event instanceof ObjectDestroyedEvent) {
+ if (target == _objtwo) {
+ Log.info("List won't yet be empty: " + _objone.list);
+ } else {
+ Log.info("Other object destroyed.");
+ // go bye bye
+ CherServer.shutdown();
+ }
+
+ } else if (event instanceof ObjectRemovedEvent) {
+ Log.info("List should be empty: " + _objone.list);
+ // finally destroy the other object to complete the circle
+ _objone.destroy();
+ }
+
+ return true;
+ }
+
+ protected TestObject _objone;
+ protected TestObject _objtwo;
+}
diff --git a/tests/src/java/com/threerings/presents/server/TestObject.dobj b/tests/src/java/com/threerings/presents/server/TestObject.dobj
index 5e8ac4487..c5aaf838e 100644
--- a/tests/src/java/com/threerings/presents/server/TestObject.dobj
+++ b/tests/src/java/com/threerings/presents/server/TestObject.dobj
@@ -1,5 +1,5 @@
//
-// $Id: TestObject.dobj,v 1.2 2001/07/19 05:56:20 mdb Exp $
+// $Id: TestObject.dobj,v 1.3 2001/08/08 00:28:49 mdb Exp $
package com.threerings.cocktail.cher.server.test;
@@ -12,12 +12,12 @@ import com.threerings.cocktail.cher.dobj.*;
public class TestObject extends DObject
{
public static final String FOO = "foo";
+ public static final String BAR = "bar";
+ public static final String LIST = "list";
public int foo;
-
- public static final String BAR = "bar";
-
public String bar;
+ public OidList list = new OidList();
public void setFoo (int foo)
{
@@ -29,8 +29,29 @@ public class TestObject extends DObject
requestAttributeChange(BAR, bar);
}
- public String toString ()
+ /**
+ * Requests that the specified oid be added to the
+ * list oid list.
+ */
+ public void addToList (int oid)
{
- return "[foo=" + foo + ", bar=" + bar + "]";
+ requestOidAdd(LIST, oid);
+ }
+
+ /**
+ * Requests that the specified oid be removed from the
+ * list oid list.
+ */
+ public void removeFromList (int oid)
+ {
+ requestOidRemove(LIST, oid);
+ }
+
+ protected void toString (StringBuffer buf)
+ {
+ super.toString(buf);
+ buf.append(", foo=").append(foo);
+ buf.append(", bar=").append(bar);
+ buf.append(", list=").append(list);
}
}