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
This commit is contained in:
Michael Bayne
2001-08-08 00:28:49 +00:00
parent 91d5813b37
commit 6fe9ad8a6e
7 changed files with 410 additions and 24 deletions
-6
View File
@@ -3,8 +3,6 @@ Cher Notes -*- outline -*-
* TODO * TODO
Pass cause back to client somehow via FailureResponse in Client.requestFailed 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 Ensure at time of OBJECT_ADDED that the object being added exists and has
not been destroyed. not been destroyed.
@@ -12,10 +10,6 @@ clientWillLogff becomes clientMayLogoff?
Look into nbio waking up all sockets when any data comes in. 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 * Server-side event concentrator
The client objects will not subscribe directly, but will subscribe through The client objects will not subscribe directly, but will subscribe through
the concentrator so that, at least, it can create a single the concentrator so that, at least, it can create a single
@@ -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; 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 // scan the lock array to see if this lock is already acquired
int slot = -1; int slot = -1;
for (int i = 0; i < _locks.length; i++) { for (int i = 0; i < _locks.length; i++) {
if (_locks[i] == null) { if (_locks[i] == null && slot == -1) {
// keep track of this for later // keep track of this for later
slot = i; slot = i;
} else if (name.equals(_locks[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
* <code>DObjectManager.createObject</code> 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 * Don't call this function! It initializes this distributed object
* with the supplied distributed object manager. This is called by the * with the supplied distributed object manager. This is called by the
@@ -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; package com.threerings.cocktail.cher.server;
import java.lang.reflect.Method; import java.lang.reflect.*;
import java.util.HashMap; import java.util.HashMap;
import com.samskivert.util.Queue; import com.samskivert.util.Queue;
@@ -172,9 +172,191 @@ public class CherDObjectMgr implements DObjectManager
*/ */
public void objectDestroyed (DEvent event, DObject target) public void objectDestroyed (DEvent event, DObject target)
{ {
// Log.info("Removing destroyed object from table " + int oid = target.getOid();
// "[oid=" + target.getOid() + "].");
_objects.remove(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 <code>objectDestroyed</code>; 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 <code>ObjectAddedEvent</code> 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 <code>ObjectRemovedEvent</code> 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 () protected synchronized boolean isRunning ()
@@ -224,7 +406,7 @@ public class CherDObjectMgr implements DObjectManager
// insert it into the table // insert it into the table
_objects.put(oid, obj); _objects.put(oid, obj);
// Log.info("Created object [obj=" + obj + "]."); // Log.info("Created object [obj=" + obj + "].");
if (_target != null) { if (_target != null) {
// add the subscriber to this object's subscriber list // add the subscriber to this object's subscriber list
@@ -351,17 +533,74 @@ public class CherDObjectMgr implements DObjectManager
method = omgrcl.getMethod("objectDestroyed", ptypes); method = omgrcl.getMethod("objectDestroyed", ptypes);
_helpers.put(ObjectDestroyedEvent.class, method); _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) { } catch (Exception e) {
Log.warning("Unable to register event helpers " + Log.warning("Unable to register event helpers " +
"[error=" + e + "]."); "[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; protected boolean _running = true;
/** The event queue via which all events are processed. */
protected Queue _evqueue = new Queue(); protected Queue _evqueue = new Queue();
/** The managed distributed objects table. */
protected IntMap _objects = new IntMap(); protected IntMap _objects = new IntMap();
/** Used to assign a unique oid to each distributed object. */
protected int _nextOid = 0; 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 * This table maps event classes to helper methods that perform some
* additional processing for particular events. * additional processing for particular events.
@@ -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; package com.threerings.cocktail.cher.server;
@@ -132,15 +132,49 @@ public class CherServer
// start up the connection manager // start up the connection manager
conmgr.start(); conmgr.start();
// invoke the dobjmgr event loop // 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) public static void main (String[] args)
{ {
Log.info("Cher server starting...");
CherServer server = new CherServer(); CherServer server = new CherServer();
try { try {
// initialize the server
server.init(); 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(); server.run();
} catch (Exception e) { } catch (Exception e) {
Log.warning("Unable to initialize server."); Log.warning("Unable to initialize server.");
Log.logStackTrace(e); Log.logStackTrace(e);
@@ -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; package com.threerings.cocktail.cher.server.net;
@@ -55,8 +55,16 @@ public class AuthManager extends LoopingThread
protected void iterate () protected void iterate ()
{ {
// grab the next authing connection from the queue // 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 { try {
// instruct the authenticator to process the auth request // instruct the authenticator to process the auth request
AuthResponse rsp = _author.process(aconn.getAuthRequest()); 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 Authenticator _author;
protected ConnectionManager _conmgr; protected ConnectionManager _conmgr;
protected Queue _authq = new Queue(); protected Queue _authq = new Queue();
@@ -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;
}
@@ -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; package com.threerings.cocktail.cher.server.test;
@@ -12,12 +12,12 @@ import com.threerings.cocktail.cher.dobj.*;
public class TestObject extends DObject public class TestObject extends DObject
{ {
public static final String FOO = "foo"; public static final String FOO = "foo";
public static final String BAR = "bar";
public static final String LIST = "list";
public int foo; public int foo;
public static final String BAR = "bar";
public String bar; public String bar;
public OidList list = new OidList();
public void setFoo (int foo) public void setFoo (int foo)
{ {
@@ -29,8 +29,29 @@ public class TestObject extends DObject
requestAttributeChange(BAR, bar); requestAttributeChange(BAR, bar);
} }
public String toString () /**
* Requests that the specified oid be added to the
* <code>list</code> oid list.
*/
public void addToList (int oid)
{ {
return "[foo=" + foo + ", bar=" + bar + "]"; requestOidAdd(LIST, oid);
}
/**
* Requests that the specified oid be removed from the
* <code>list</code> 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);
} }
} }