From 371ca60bf5f8d1e11f96489cdddb8e60ddea7d89 Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Tue, 26 Nov 2002 02:14:25 +0000 Subject: [PATCH] Replaced old wacked-in-head system with proper reference counting for client objects. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@1989 542714f4-19e9-0310-aa3c-eee0fc999fb1 --- .../presents/data/ClientObject.dobj | 32 ++++++- .../presents/data/ClientObject.java | 32 ++++++- .../presents/server/ClientManager.java | 85 +++++-------------- .../presents/server/ClientResolver.java | 4 +- .../presents/server/PresentsClient.java | 20 +---- 5 files changed, 92 insertions(+), 81 deletions(-) diff --git a/src/java/com/threerings/presents/data/ClientObject.dobj b/src/java/com/threerings/presents/data/ClientObject.dobj index a022ba8c2..c4f0e4426 100644 --- a/src/java/com/threerings/presents/data/ClientObject.dobj +++ b/src/java/com/threerings/presents/data/ClientObject.dobj @@ -1,11 +1,13 @@ // -// $Id: ClientObject.dobj,v 1.2 2002/09/19 23:36:59 mdb Exp $ +// $Id: ClientObject.dobj,v 1.3 2002/11/26 02:14:24 mdb Exp $ package com.threerings.presents.data; import com.threerings.presents.dobj.DObject; import com.threerings.presents.dobj.DSet; +import com.threerings.presents.Log; + /** * Every client in the system has an associated client object to which * only they subscribe. The client object can be used to deliver messages @@ -29,4 +31,32 @@ public class ClientObject extends DObject { return "(" + getOid() + ")"; } + + /** + * Used for reference counting client objects, adds a reference to + * this object. + */ + public synchronized void reference () + { + _references++; +// Log.info("Incremented references [who=" + who() + +// ", refs=" + _references + "]."); + } + + /** + * Used for reference counting client objects, releases a reference to + * this object. + * + * @return true if the object has remaining references, false + * otherwise. + */ + public synchronized boolean release () + { +// Log.info("Decremented references [who=" + who() + +// ", refs=" + (_references-1) + "]."); + return (--_references > 0); + } + + /** Used to reference count resolved client objects. */ + protected transient int _references; } diff --git a/src/java/com/threerings/presents/data/ClientObject.java b/src/java/com/threerings/presents/data/ClientObject.java index 69f1ea81d..5ac7d9fb4 100644 --- a/src/java/com/threerings/presents/data/ClientObject.java +++ b/src/java/com/threerings/presents/data/ClientObject.java @@ -1,11 +1,13 @@ // -// $Id: ClientObject.java,v 1.4 2002/09/19 23:36:59 mdb Exp $ +// $Id: ClientObject.java,v 1.5 2002/11/26 02:14:24 mdb Exp $ package com.threerings.presents.data; import com.threerings.presents.dobj.DObject; import com.threerings.presents.dobj.DSet; +import com.threerings.presents.Log; + /** * Every client in the system has an associated client object to which * only they subscribe. The client object can be used to deliver messages @@ -33,6 +35,34 @@ public class ClientObject extends DObject return "(" + getOid() + ")"; } + /** + * Used for reference counting client objects, adds a reference to + * this object. + */ + public synchronized void reference () + { + _references++; +// Log.info("Incremented references [who=" + who() + +// ", refs=" + _references + "]."); + } + + /** + * Used for reference counting client objects, releases a reference to + * this object. + * + * @return true if the object has remaining references, false + * otherwise. + */ + public synchronized boolean release () + { +// Log.info("Decremented references [who=" + who() + +// ", refs=" + (_references-1) + "]."); + return (--_references > 0); + } + + /** Used to reference count resolved client objects. */ + protected transient int _references; + /** * Requests that the specified entry be added to the * receivers set. The set will not change until the event is diff --git a/src/java/com/threerings/presents/server/ClientManager.java b/src/java/com/threerings/presents/server/ClientManager.java index 06afd3abb..18f5a0517 100644 --- a/src/java/com/threerings/presents/server/ClientManager.java +++ b/src/java/com/threerings/presents/server/ClientManager.java @@ -1,5 +1,5 @@ // -// $Id: ClientManager.java,v 1.26 2002/11/05 02:17:56 mdb Exp $ +// $Id: ClientManager.java,v 1.27 2002/11/26 02:14:25 mdb Exp $ package com.threerings.presents.server; @@ -135,7 +135,9 @@ public class ClientManager * If the client object is already resolved, the request will be * processed immediately, otherwise the appropriate client object will * be instantiated and populated by the registered client resolver - * (which may involve talking to databases). + * (which may involve talking to databases). Note: this + * must be paired with a call to {@link #releaseClientObject} + * when the caller is finished with the client object. */ public synchronized void resolveClientObject ( String username, ClientResolutionListener listener) @@ -144,6 +146,7 @@ public class ClientManager String key = toKey(username); ClientObject clobj = (ClientObject)_objmap.get(key); if (clobj != null) { + clobj.reference(); listener.clientResolved(username, clobj); return; } @@ -189,67 +192,34 @@ public class ClientManager } /** - * If an entity resolves a client object outside the scope of a normal - * client session, it should call this to unmap the client object when - * it's finished. This won't actually unmap the client if someone came - * along and started a session for that client in the meanwhile, but - * if it does unmap the client it will also destroy the client object - * (finishing the job, as it were). + * Releases a client object that was obtained via a call to {@link + * #resolveClientObject}. If this caller is the last reference, the + * object will be flushed and destroyed. */ - public synchronized void unmapClientObject (String username) + public void releaseClientObject (String username) { - // we only remove the mapping if there's not a session in progress - // (which is indicated by an entry in the locks table) String key = toKey(username); - if (_locks.contains(key)) { + ClientObject clobj = (ClientObject)_objmap.get(key); + if (clobj == null) { + Log.warning("Requested to release unmapped client object " + + "[username=" + username + "]."); + Thread.dumpStack(); return; } - ClientObject clobj = (ClientObject)_objmap.remove(key); - if (clobj != null) { - PresentsServer.omgr.destroyObject(clobj.getOid()); - } else { - Log.warning("Requested to unmap non-existent client object " + - "[username=" + username + "]."); + // decrement the reference count and stop here if there are + // remaining references + if (clobj.release()) { + return; } - } - /** - * When a client object becomes part of an active session, this method - * should be called to ensure that it is not unloaded by any entities - * that temporarily resolve and release the object. This is called - * automatically when a real user starts a session by establishing a - * network connection with the server. If a client session is managed - * via some other mechanism (bots managed by the server, for example), - * this method and its corresponding {@link #releaseClientObject} - * should be called at the beginning and end of the faked client - * session respectively. - */ - public synchronized void lockClientObject (String username) - { - String key = toKey(username); - if (_locks.contains(key)) { - Log.warning("Requested to lock already locked user " + - "[username=" + username + "]."); - Thread.dumpStack(); + Log.debug("Destroying client " + clobj.who() + "."); - } else { - _locks.add(key); - } - } + // we're all clear to go; remove the mapping + _objmap.remove(key); - /** - * Releases a client object when their session has ended. - * - * @see #lockClientObject - */ - public synchronized void releaseClientObject (String username) - { - if (!_locks.remove(toKey(username))) { - Log.warning("Requested to unlock a user that was not locked " + - "[username=" + username + "]."); - Thread.dumpStack(); - } + // and destroy the object itself + PresentsServer.omgr.destroyObject(clobj.getOid()); } // documentation inherited @@ -355,21 +325,12 @@ public class ClientManager if (rc == null) { Log.warning("Unregistered client ended session " + client + "."); Thread.dumpStack(); - - // if they weren't in the username mapping, bail out now - // because the subsequent unmappings would just fail if we - // tried to do them for an unmapped client - return; - } else if (rc != client) { Log.warning("Different clients with same username!? " + "[c1=" + rc + ", c2=" + client + "]."); } else { Log.info("Ending session " + client + "."); } - - // unmap (and destroy) the client object - unmapClientObject(username); } /** diff --git a/src/java/com/threerings/presents/server/ClientResolver.java b/src/java/com/threerings/presents/server/ClientResolver.java index f6a1f922b..c6d3e1bb2 100644 --- a/src/java/com/threerings/presents/server/ClientResolver.java +++ b/src/java/com/threerings/presents/server/ClientResolver.java @@ -1,5 +1,5 @@ // -// $Id: ClientResolver.java,v 1.1 2002/03/05 05:33:25 mdb Exp $ +// $Id: ClientResolver.java,v 1.2 2002/11/26 02:14:25 mdb Exp $ package com.threerings.presents.server; @@ -99,6 +99,8 @@ public class ClientResolver extends Invoker.Unit ClientResolutionListener crl = (ClientResolutionListener) _listeners.get(i); try { + // add a reference for each listener + _clobj.reference(); crl.clientResolved(_username, _clobj); } catch (Exception e) { Log.warning("Client resolution listener choked during " + diff --git a/src/java/com/threerings/presents/server/PresentsClient.java b/src/java/com/threerings/presents/server/PresentsClient.java index 1c2ca0b3a..28dc17edf 100644 --- a/src/java/com/threerings/presents/server/PresentsClient.java +++ b/src/java/com/threerings/presents/server/PresentsClient.java @@ -1,5 +1,5 @@ // -// $Id: PresentsClient.java,v 1.44 2002/11/05 05:47:36 mdb Exp $ +// $Id: PresentsClient.java,v 1.45 2002/11/26 02:14:25 mdb Exp $ package com.threerings.presents.server; @@ -154,20 +154,13 @@ public class PresentsClient // call down to any derived classes clientObjectWillChange(_clobj, clobj); - // release our old client object + // release our old client object; this will destroy it _cmgr.releaseClientObject(_username); - // unmap the old client object; this will destroy it and - // clear out our username mapping - _cmgr.unmapClientObject(_username); - // update our internal fields _username = username; _clobj = clobj; - // lock our new client object - _cmgr.lockClientObject(_username); - // call down to any derived classes clientObjectDidChange(_clobj); @@ -234,10 +227,6 @@ public class PresentsClient // obtain our starting username assignStartingUsername(); - // obtain a lock for our username->client object mapping while we - // have an active session - cmgr.lockClientObject(_username); - // resolve our client object before we get fully underway cmgr.resolveClientObject(_username, this); @@ -448,11 +437,10 @@ public class PresentsClient // about to generate clearSubscrips(); - // release our locked client object + // release (and destroy) our client object _cmgr.releaseClientObject(_username); - // then let the client manager know what's up (it will take care - // of destroying our client object for us) + // let the client manager know that we're audi 5000 _cmgr.clientDidEndSession(this); // clear out the client object so that we know the session is over