diff --git a/src/java/com/threerings/presents/client/ClientDObjectMgr.java b/src/java/com/threerings/presents/client/ClientDObjectMgr.java index de9fed78f..be8be5772 100644 --- a/src/java/com/threerings/presents/client/ClientDObjectMgr.java +++ b/src/java/com/threerings/presents/client/ClientDObjectMgr.java @@ -1,5 +1,5 @@ // -// $Id: ClientDObjectMgr.java,v 1.4 2001/07/19 07:09:16 mdb Exp $ +// $Id: ClientDObjectMgr.java,v 1.5 2001/08/07 20:38:58 mdb Exp $ package com.threerings.cocktail.cher.client; @@ -75,6 +75,13 @@ public class ClientDObjectMgr _comm.postMessage(new ForwardEventRequest(tevent)); } + // inherit documentation from the interface + public void destroyObject (int oid) + { + // forward an object destroyed event to the server + postEvent(new ObjectDestroyedEvent(oid)); + } + // inherit documentation from the interface public void removedLastSubscriber (DObject obj) { @@ -154,8 +161,29 @@ public class ClientDObjectMgr return; } - // have the object pass this event on to its subscribers - target.notifySubscribers(event); + try { + // apply the event to the object + boolean notify = event.applyToObject(target); + + // if this is an object destroyed event, we need to remove the + // object from our object table + if (event instanceof ObjectDestroyedEvent) { + Log.info("Uncaching destroyed object " + + "[oid=" + target.getOid() + "]."); + _ocache.remove(target.getOid()); + } + + // have the object pass this event on to its subscribers if + // desired + if (notify) { + target.notifySubscribers(event); + } + + } catch (Exception e) { + Log.warning("Failure processing event [event=" + event + + ", target=" + target + "]."); + Log.logStackTrace(e); + } } /** @@ -193,7 +221,19 @@ public class ClientDObjectMgr */ protected void notifyFailure (int oid) { - Log.info("Get failed: " + oid); + // let the penders know that the object is not available + PendingRequest req = (PendingRequest)_penders.remove(oid); + if (req == null) { + Log.warning("Failed to get object, but no one cares?! " + + "[oid=" + oid + "]."); + return; + } + + for (int i = 0; i < req.targets.size(); i++) { + Subscriber target = (Subscriber)req.targets.get(i); + // and let them know that the object is in + target.requestFailed(oid, null); + } } /** diff --git a/src/java/com/threerings/presents/client/Communicator.java b/src/java/com/threerings/presents/client/Communicator.java index a89316eb1..867bf43bc 100644 --- a/src/java/com/threerings/presents/client/Communicator.java +++ b/src/java/com/threerings/presents/client/Communicator.java @@ -1,5 +1,5 @@ // -// $Id: Communicator.java,v 1.12 2001/08/03 02:11:20 mdb Exp $ +// $Id: Communicator.java,v 1.13 2001/08/07 20:38:58 mdb Exp $ package com.threerings.cocktail.cher.client; @@ -91,6 +91,9 @@ public class Communicator return; } + // post a logoff message + postMessage(new LogoffRequest()); + // let our reader and writer know that it's time to go if (_reader != null) { // if logoff() is being called by the client as part of a @@ -110,28 +113,12 @@ public class Communicator if (_writer != null) { // shutting down the writer thread is simpler because we can // post a termination message on the queue and be sure that it - // will receive it. we do run the risk that it is in the - // middle of trying to send a message when we close the - // socket, but in theory the send will fail, it will complain - // and then it will cleanly exit. if we were uber paranoid - // about JVMs misbehaving on simultaneous close()/write(), we - // could wait here for the writer thread to exit, but that - // makes me even more nervous because I know that some JVMs - // don't handle Thread.join() properly (hopefully no one is - // still using those JVMs but one can never be sure) + // will receive it. when the writer thread has delivered our + // logoff request and exited, we will complete the logoff + // process by closing our socket and invoking the + // clientDidLogoff callback _writer.shutdown(); } - - // close down our socket - try { - _socket.close(); - } catch (IOException cle) { - Log.warning("Error closing failed socket: " + cle); - } - _socket = null; - - // let the client observers know that we're logged off - _client.notifyObservers(Client.CLIENT_DID_LOGOFF, null); } /** @@ -226,8 +213,20 @@ public class Communicator { // clear out our writer reference _writer = null; - Log.info("Writer thread exited."); + + // now that the writer thread has gone away, we can safely close + // our socket and let the client know that the logoff process has + // completed + try { + _socket.close(); + } catch (IOException cle) { + Log.warning("Error closing failed socket: " + cle); + } + _socket = null; + + // let the client observers know that we're logged off + _client.notifyObservers(Client.CLIENT_DID_LOGOFF, null); } /** diff --git a/src/java/com/threerings/presents/dobj/DObject.java b/src/java/com/threerings/presents/dobj/DObject.java index 3ed20f68e..081741af7 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.19 2001/08/04 00:39:44 mdb Exp $ +// $Id: DObject.java,v 1.20 2001/08/07 20:38:58 mdb Exp $ package com.threerings.cocktail.cher.dobj; @@ -233,6 +233,16 @@ public class DObject } } + /** + * Requests that this distributed object be destroyed. It does so by + * queueing up an object destroyed event which the server will + * validate and process. + */ + public void destroy () + { + _mgr.postEvent(new ObjectDestroyedEvent(_oid)); + } + /** * Checks to ensure that the specified subscriber has access to this * object. This will be called before satisfying a subscription diff --git a/src/java/com/threerings/presents/dobj/DObjectManager.java b/src/java/com/threerings/presents/dobj/DObjectManager.java index de8e7cab2..7624fab9c 100644 --- a/src/java/com/threerings/presents/dobj/DObjectManager.java +++ b/src/java/com/threerings/presents/dobj/DObjectManager.java @@ -1,5 +1,5 @@ // -// $Id: DObjectManager.java,v 1.7 2001/08/02 04:49:08 mdb Exp $ +// $Id: DObjectManager.java,v 1.8 2001/08/07 20:38:58 mdb Exp $ package com.threerings.cocktail.cher.dobj; @@ -59,6 +59,15 @@ public interface DObjectManager */ public void unsubscribeFromObject (int oid, Subscriber target); + /** + * Requests that the specified object be destroyed. Once destroyed an + * object is removed from the runtime system and may no longer have + * events dispatched on it. + * + * @param oid The object id of the distributed object to be destroyed. + */ + public void destroyObject (int oid); + /** * Posts a distributed object event into the system. Instead of * requesting the modification of a distributed object attribute by diff --git a/src/java/com/threerings/presents/dobj/ObjectDestroyedEvent.java b/src/java/com/threerings/presents/dobj/ObjectDestroyedEvent.java new file mode 100644 index 000000000..c21755221 --- /dev/null +++ b/src/java/com/threerings/presents/dobj/ObjectDestroyedEvent.java @@ -0,0 +1,63 @@ +// +// $Id: ObjectDestroyedEvent.java,v 1.1 2001/08/07 20:38:58 mdb Exp $ + +package com.threerings.cocktail.cher.dobj; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.reflect.Method; + +/** + * An object destroyed event is dispatched when an object has been removed + * from the distributed object system. It can also be constructed to + * request an attribute change on an object and posted to the dobjmgr. + * + * @see DObjectManager#postEvent + */ +public class ObjectDestroyedEvent extends TypedEvent +{ + /** The typed object code for this event. */ + public static final short TYPE = TYPE_BASE + 7; + + /** + * Constructs a new object destroyed event for the specified + * distributed object. + * + * @param targetOid the object id of the object that will be destroyed. + */ + public ObjectDestroyedEvent (int targetOid) + { + super(targetOid); + } + + /** + * Constructs a blank instance of this event in preparation for + * unserialization from the network. + */ + public ObjectDestroyedEvent () + { + } + + /** + * Applies this attribute change to the object. + */ + public boolean applyToObject (DObject target) + throws ObjectAccessException + { + // nothing to do in preparation for destruction, the omgr will + // have to recognize this type of event and do the right thing + return true; + } + + public short getType () + { + return TYPE; + } + + protected void toString (StringBuffer buf) + { + buf.append("DESTROY:"); + super.toString(buf); + } +} diff --git a/src/java/com/threerings/presents/dobj/io/DObjectFactory.java b/src/java/com/threerings/presents/dobj/io/DObjectFactory.java index 3c6b194da..925269c0b 100644 --- a/src/java/com/threerings/presents/dobj/io/DObjectFactory.java +++ b/src/java/com/threerings/presents/dobj/io/DObjectFactory.java @@ -1,5 +1,5 @@ // -// $Id: DObjectFactory.java,v 1.6 2001/08/03 02:11:40 mdb Exp $ +// $Id: DObjectFactory.java,v 1.7 2001/08/07 20:38:58 mdb Exp $ package com.threerings.cocktail.cher.dobj.io; @@ -49,7 +49,7 @@ public class DObjectFactory Class clazz = Class.forName(in.readUTF()); DObject dobj = (DObject)clazz.newInstance(); dobj.setOid(in.readInt()); // read and set the oid - Log.info("Unmarshalling object: " + dobj); + // Log.info("Unmarshalling object: " + dobj); // look up the marshaller for that class Marshaller marsh = getMarshaller(clazz); diff --git a/src/java/com/threerings/presents/io/TypedObjectRegistry.java b/src/java/com/threerings/presents/io/TypedObjectRegistry.java index f463aec98..4d1ce1a5f 100644 --- a/src/java/com/threerings/presents/io/TypedObjectRegistry.java +++ b/src/java/com/threerings/presents/io/TypedObjectRegistry.java @@ -1,5 +1,5 @@ // -// $Id: TypedObjectRegistry.java,v 1.4 2001/08/04 01:05:25 mdb Exp $ +// $Id: TypedObjectRegistry.java,v 1.5 2001/08/07 20:38:58 mdb Exp $ package com.threerings.cocktail.cher.io; @@ -64,5 +64,7 @@ public class TypedObjectRegistry ObjectRemovedEvent.class); TypedObjectFactory.registerClass(ReleaseLockEvent.TYPE, ReleaseLockEvent.class); + TypedObjectFactory.registerClass(ObjectDestroyedEvent.TYPE, + ObjectDestroyedEvent.class); } } diff --git a/src/java/com/threerings/presents/server/PresentsDObjectMgr.java b/src/java/com/threerings/presents/server/PresentsDObjectMgr.java index 92c314159..42e8361d8 100644 --- a/src/java/com/threerings/presents/server/PresentsDObjectMgr.java +++ b/src/java/com/threerings/presents/server/PresentsDObjectMgr.java @@ -1,8 +1,11 @@ // -// $Id: PresentsDObjectMgr.java,v 1.9 2001/07/23 21:12:55 mdb Exp $ +// $Id: PresentsDObjectMgr.java,v 1.10 2001/08/07 20:38:58 mdb Exp $ package com.threerings.cocktail.cher.server; +import java.lang.reflect.Method; +import java.util.HashMap; + import com.samskivert.util.Queue; import com.threerings.cocktail.cher.Log; @@ -61,6 +64,13 @@ public class CherDObjectMgr implements DObjectManager AccessObjectEvent.UNSUBSCRIBE)); } + // inherit documentation from the interface + public void destroyObject (int oid) + { + // queue up an object destroyed event + postEvent(new ObjectDestroyedEvent(oid)); + } + // inherit documentation from the interface public void postEvent (DEvent event) { @@ -121,7 +131,10 @@ public class CherDObjectMgr implements DObjectManager // do any internal management necessary based on this // event - // **TBD** + Method helper = (Method)_helpers.get(event.getClass()); + if (helper != null) { + helper.invoke(this, new Object[] { event, target }); + } // if the event returns false from applyToObject, this // means it's a silent event and we shouldn't notify the @@ -153,6 +166,17 @@ public class CherDObjectMgr implements DObjectManager _evqueue.append(new ShutdownEvent()); } + /** + * Called as a helper for ObjectDestroyedEvent events. It + * removes the object from the object table. + */ + public void objectDestroyed (DEvent event, DObject target) + { + Log.info("Removing destroyed object from table " + + "[oid=" + target.getOid() + "]."); + _objects.remove(target.getOid()); + } + protected synchronized boolean isRunning () { return _running; @@ -170,11 +194,6 @@ public class CherDObjectMgr implements DObjectManager return _nextOid; } - protected boolean _running = true; - protected Queue _evqueue = new Queue(); - protected IntMap _objects = new IntMap(); - protected int _nextOid = 0; - /** * Used to create a distributed object and register it with the * system. @@ -318,4 +337,35 @@ public class CherDObjectMgr implements DObjectManager return false; } } + + /** + * Registers our event helper methods. + */ + protected static void registerEventHelpers () + { + Class[] ptypes = new Class[] { DEvent.class, DObject.class }; + Class omgrcl = CherDObjectMgr.class; + Method method; + + try { + method = omgrcl.getMethod("objectDestroyed", ptypes); + _helpers.put(ObjectDestroyedEvent.class, method); + + } catch (Exception e) { + Log.warning("Unable to register event helpers " + + "[error=" + e + "]."); + } + } + + protected boolean _running = true; + protected Queue _evqueue = new Queue(); + protected IntMap _objects = new IntMap(); + protected int _nextOid = 0; + + /** + * This table maps event classes to helper methods that perform some + * additional processing for particular events. + */ + protected static HashMap _helpers = new HashMap(); + static { registerEventHelpers(); } } diff --git a/src/java/com/threerings/presents/server/net/ConnectionManager.java b/src/java/com/threerings/presents/server/net/ConnectionManager.java index 3677bcf23..7b2664873 100644 --- a/src/java/com/threerings/presents/server/net/ConnectionManager.java +++ b/src/java/com/threerings/presents/server/net/ConnectionManager.java @@ -1,5 +1,5 @@ // -// $Id: ConnectionManager.java,v 1.7 2001/08/03 02:12:52 mdb Exp $ +// $Id: ConnectionManager.java,v 1.8 2001/08/07 20:38:58 mdb Exp $ package com.threerings.cocktail.cher.server.net; @@ -216,7 +216,7 @@ public class ConnectionManager extends LoopingThread if (socket == null) { // in theory this shouldn't happen because we got an // ACCEPT_READY event, but better safe than sorry - Log.info("Psych! Got ACCEPT_READY, but no connection."); + // Log.info("Psych! Got ACCEPT_READY, but no connection."); return; } diff --git a/src/java/com/threerings/presents/server/net/RunningConnection.java b/src/java/com/threerings/presents/server/net/RunningConnection.java index cc5a22a3f..98357b320 100644 --- a/src/java/com/threerings/presents/server/net/RunningConnection.java +++ b/src/java/com/threerings/presents/server/net/RunningConnection.java @@ -1,5 +1,5 @@ // -// $Id: RunningConnection.java,v 1.3 2001/06/02 01:30:37 mdb Exp $ +// $Id: RunningConnection.java,v 1.4 2001/08/07 20:38:58 mdb Exp $ package com.threerings.cocktail.cher.server.net; @@ -33,6 +33,10 @@ public class RunningConnection extends Connection public String toString () { - return "[mode=RUNNING, addr=" + _socket.getInetAddress() + "]"; + if (_socket != null) { + return "[mode=RUNNING, addr=" + _socket.getInetAddress() + "]"; + } else { + return "[mode=RUNNING, addr=]"; + } } } diff --git a/tests/rsrc/config/presents/server.properties b/tests/rsrc/config/presents/server.properties new file mode 100644 index 000000000..e76d61acd --- /dev/null +++ b/tests/rsrc/config/presents/server.properties @@ -0,0 +1,9 @@ +# +# $Id: server.properties,v 1.1 2001/08/07 20:38:58 mdb Exp $ +# +# Configuration for the Cher test server + +# These invocation service providers will be registered with the +# invocation manager during the server init process +providers = \ + test = com.threerings.cocktail.cher.server.test.TestProvider diff --git a/tests/src/java/com/threerings/presents/client/TestClient.java b/tests/src/java/com/threerings/presents/client/TestClient.java index 404051ba7..273071ec0 100644 --- a/tests/src/java/com/threerings/presents/client/TestClient.java +++ b/tests/src/java/com/threerings/presents/client/TestClient.java @@ -1,5 +1,5 @@ // -// $Id: TestClient.java,v 1.7 2001/07/19 19:18:06 mdb Exp $ +// $Id: TestClient.java,v 1.8 2001/08/07 20:38:58 mdb Exp $ package com.threerings.cocktail.cher.client.test; @@ -18,6 +18,11 @@ import com.threerings.cocktail.cher.server.test.TestObject; public class TestClient implements Client.Invoker, ClientObserver, Subscriber { + public void setClient (Client client) + { + _client = client; + } + public void invokeLater (Runnable run) { // queue it on up @@ -36,13 +41,11 @@ public class TestClient public void clientDidLogon (Client client) { Log.info("Client did logon [client=" + client + "]."); - // try subscribing to a test object - client.getDObjectManager().subscribeToObject(2, this); // register our test notification receiver client.getInvocationManager().registerReceiver(TestService.MODULE, new TestReceiver()); - // issue a test invocation request - TestService.test(client, "foo", 1, this); + // get the test object id + TestService.getTestOid(client, this); } public void clientFailedToLogon (Client client, Exception cause) @@ -79,28 +82,41 @@ public class TestClient { Log.info("Object unavailable [oid=" + oid + ", reason=" + cause + "]."); + // nothing to do, so might as well logoff + _client.logoff(true); } public boolean handleEvent (DEvent event, DObject target) { Log.info("Got event [event=" + event + ", target=" + target + "]."); - // dispatch a second event - ((TestObject)target).setBar("rofl!"); - // unsubscribe to the object to make sure we don't get the event - return false; + if (event instanceof AttributeChangedEvent) { + // request to destroy the object + target.destroy(); + } else { + // request that we log off + _client.logoff(true); + } + return true; } - public void handleTestSucceeded (String one, int two) + public void handleTestSucceeded (int invid, String one, int two) { Log.info("Got test response [one=" + one + ", two=" + two + "]."); } + public void handleGotTestOid (int invid, int oid) + { + // subscribe to the test object + _client.getDObjectManager().subscribeToObject(oid, this); + } + public static void main (String[] args) { TestClient tclient = new TestClient(); UsernamePasswordCreds creds = new UsernamePasswordCreds("test", "test"); Client client = new Client(creds, tclient); + tclient.setClient(client); client.addObserver(tclient); client.setServer("localhost", 4007); client.logon(); @@ -109,4 +125,5 @@ public class TestClient } protected Queue _queue = new Queue(); + protected Client _client; } diff --git a/tests/src/java/com/threerings/presents/client/TestService.java b/tests/src/java/com/threerings/presents/client/TestService.java index 3f320faaa..7da48394d 100644 --- a/tests/src/java/com/threerings/presents/client/TestService.java +++ b/tests/src/java/com/threerings/presents/client/TestService.java @@ -1,5 +1,5 @@ // -// $Id: TestService.java,v 1.1 2001/07/19 07:48:25 mdb Exp $ +// $Id: TestService.java,v 1.2 2001/08/07 20:38:58 mdb Exp $ package com.threerings.cocktail.cher.client.test; @@ -22,4 +22,11 @@ public class TestService invmgr.invoke(MODULE, "Test", args, rsptarget); Log.info("Sent test request [one=" + one + ", two=" + two + "]."); } + + public static void getTestOid (Client client, Object rsptarget) + { + InvocationManager invmgr = client.getInvocationManager(); + Object[] args = new Object[0]; + invmgr.invoke(MODULE, "GetTestOid", args, rsptarget); + } } diff --git a/tests/src/java/com/threerings/presents/server/TestProvider.java b/tests/src/java/com/threerings/presents/server/TestProvider.java index a3141b168..659f8f0a5 100644 --- a/tests/src/java/com/threerings/presents/server/TestProvider.java +++ b/tests/src/java/com/threerings/presents/server/TestProvider.java @@ -1,5 +1,5 @@ // -// $Id: TestProvider.java,v 1.4 2001/07/19 19:18:07 mdb Exp $ +// $Id: TestProvider.java,v 1.5 2001/08/07 20:38:58 mdb Exp $ package com.threerings.cocktail.cher.server.test; @@ -27,4 +27,10 @@ public class TestProvider extends InvocationProvider // and issue a response to this invocation request return createResponse("TestSucceeded", one, new Integer(two)); } + + public Object[] handleGetTestOidRequest (ClientObject source) + { + int oid = TestServer.testobj.getOid(); + return createResponse("GotTestOid", new Integer(oid)); + } } diff --git a/tests/src/java/com/threerings/presents/server/TestServer.java b/tests/src/java/com/threerings/presents/server/TestServer.java new file mode 100644 index 000000000..5f72cb26a --- /dev/null +++ b/tests/src/java/com/threerings/presents/server/TestServer.java @@ -0,0 +1,70 @@ +// +// $Id: TestServer.java,v 1.1 2001/08/07 20:38:58 mdb Exp $ + +package com.threerings.cocktail.cher.server.test; + +import java.io.IOException; + +import com.threerings.cocktail.cher.Log; +import com.threerings.cocktail.cher.dobj.*; +import com.threerings.cocktail.cher.server.*; + +public class TestServer extends CherServer +{ + /** The namespace used for server config properties. */ + public static final String CONFIG_KEY = "test"; + + public static TestObject testobj; + + public void init () + throws IOException + { + super.init(); + + // bind the party server config into the namespace + config.bindProperties(CONFIG_KEY, CONFIG_PATH); + + // register our invocation service providers + registerProviders(config.getValue(PROVIDERS_KEY, (String[])null)); + + // create a test object + Subscriber sub = new Subscriber() + { + public void objectAvailable (DObject object) + { + testobj = (TestObject)object; + } + + public void requestFailed (int oid, ObjectAccessException cause) + { + Log.warning("Unable to create test object " + + "[error=" + cause + "]."); + } + + public boolean handleEvent (DEvent event, DObject target) + { + return false; + } + }; + omgr.createObject(TestObject.class, sub, false); + } + + public static void main (String[] args) + { + TestServer server = new TestServer(); + try { + server.init(); + server.run(); + } catch (Exception e) { + Log.warning("Unable to initialize server."); + Log.logStackTrace(e); + } + } + + // the path to the config file + protected final static String CONFIG_PATH = + "rsrc/config/cocktail/cher/test/server"; + + // the config key for our list of invocation provider mappings + protected final static String PROVIDERS_KEY = CONFIG_KEY + ".providers"; +}