Implemented object destruction; wired up some missing stuff; created a

test server to make some testing easier; various other cleanups.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@188 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2001-08-07 20:38:58 +00:00
parent b3e7f12f08
commit 782e9501d6
15 changed files with 340 additions and 54 deletions
@@ -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);
}
}
/**
@@ -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);
}
/**
@@ -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
@@ -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
@@ -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);
}
}
@@ -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);
@@ -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);
}
}
@@ -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 <code>ObjectDestroyedEvent</code> 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(); }
}
@@ -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;
}
@@ -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=<disconnected>]";
}
}
}
@@ -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
@@ -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;
}
@@ -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);
}
}
@@ -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));
}
}
@@ -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";
}