From 6fe9ad8a6e5ae123cce02424cea26e01d4b14611 Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Wed, 8 Aug 2001 00:28:49 +0000 Subject: [PATCH] Finished wiring up oid list tracking; made shutdown() work on the cher server; added an easy mechanism to write test modules and invoke them on the server. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@192 542714f4-19e9-0310-aa3c-eee0fc999fb1 --- docs/presents/notes.txt | 6 - .../com/threerings/presents/dobj/DObject.java | 15 +- .../presents/server/PresentsDObjectMgr.java | 251 +++++++++++++++++- .../presents/server/PresentsServer.java | 38 ++- .../presents/server/net/AuthManager.java | 18 +- .../threerings/presents/server/RefTest.java | 73 +++++ .../presents/server/TestObject.dobj | 33 ++- 7 files changed, 410 insertions(+), 24 deletions(-) create mode 100644 tests/src/java/com/threerings/presents/server/RefTest.java 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); } }