diff --git a/src/java/com/threerings/presents/server/ClientResolver.java b/src/java/com/threerings/presents/server/ClientResolver.java index e9ea39315..9ad804bd4 100644 --- a/src/java/com/threerings/presents/server/ClientResolver.java +++ b/src/java/com/threerings/presents/server/ClientResolver.java @@ -1,10 +1,11 @@ // -// $Id: ClientResolver.java,v 1.5 2004/06/13 08:51:57 mdb Exp $ +// $Id: ClientResolver.java,v 1.6 2004/06/29 03:22:22 mdb Exp $ package com.threerings.presents.server; import java.util.ArrayList; +import com.samskivert.util.Invoker; import com.threerings.util.Name; import com.threerings.presents.Log; @@ -12,7 +13,6 @@ import com.threerings.presents.data.ClientObject; import com.threerings.presents.dobj.DObject; import com.threerings.presents.dobj.ObjectAccessException; import com.threerings.presents.dobj.Subscriber; -import com.threerings.presents.util.Invoker; /** * Used to resolve client data when a user starts a session (or when some diff --git a/src/java/com/threerings/presents/server/PresentsDObjectMgr.java b/src/java/com/threerings/presents/server/PresentsDObjectMgr.java index 2f332fb92..d6f67d8c3 100644 --- a/src/java/com/threerings/presents/server/PresentsDObjectMgr.java +++ b/src/java/com/threerings/presents/server/PresentsDObjectMgr.java @@ -1,5 +1,5 @@ // -// $Id: PresentsDObjectMgr.java,v 1.42 2004/02/21 02:18:56 mdb Exp $ +// $Id: PresentsDObjectMgr.java,v 1.43 2004/06/29 03:22:22 mdb Exp $ package com.threerings.presents.server; @@ -12,6 +12,7 @@ import sun.misc.Perf; import com.samskivert.util.HashIntMap; import com.samskivert.util.Histogram; +import com.samskivert.util.Invoker; import com.samskivert.util.Queue; import com.samskivert.util.SortableArrayList; import com.samskivert.util.StringUtil; @@ -34,7 +35,8 @@ import com.threerings.presents.dobj.*; * requested to shut down. */ public class PresentsDObjectMgr - implements RootDObjectManager, PresentsServer.Reporter + implements RootDObjectManager, Invoker.ResultReceiver, + PresentsServer.Reporter { /** * Creates the dobjmgr and prepares it for operation. diff --git a/src/java/com/threerings/presents/server/PresentsInvoker.java b/src/java/com/threerings/presents/server/PresentsInvoker.java new file mode 100644 index 000000000..40718e1e7 --- /dev/null +++ b/src/java/com/threerings/presents/server/PresentsInvoker.java @@ -0,0 +1,160 @@ +// +// $Id: PresentsInvoker.java,v 1.1 2004/06/29 03:22:22 mdb Exp $ + +package com.threerings.presents.server; + +import java.util.Iterator; + +import com.samskivert.util.Histogram; +import com.samskivert.util.Invoker; +import com.samskivert.util.StringUtil; + +import com.threerings.presents.Log; + +/** + * Extends the generic {@link Invoker} and integrates it a bit more into + * the Presents system. + */ +public class PresentsInvoker extends Invoker + implements PresentsServer.Reporter +{ + /** + * Creates an invoker that will post results to the supplied + * distributed object manager. + */ + public PresentsInvoker (PresentsDObjectMgr omgr) + { + super("presents.Invoker", omgr); + _omgr = omgr; + if (PERF_TRACK) { + PresentsServer.registerReporter(this); + } + } + + /** + * Will do a sophisticated shutdown of both itself and the DObjectManager + * thread. + */ + public void shutdown () + { + _queue.append(new ShutdownUnit()); + } + + // documentation inherited from interface + public void appendReport (StringBuffer buffer, long now, long sinceLast) + { + buffer.append("* presents.util.Invoker:\n"); + buffer.append("- Units executed: ").append(_unitsRun).append("\n"); + _unitsRun = 0; + for (Iterator iter = _tracker.keySet().iterator(); iter.hasNext(); ) { + Class key = (Class)iter.next(); + Histogram histo = (Histogram)_tracker.get(key); + buffer.append(" "); + buffer.append(StringUtil.shortClassName(key)).append(": "); + buffer.append(histo.summarize()).append("\n"); + histo.clear(); + } + } + + /** + * This unit gets posted back and forth between the invoker and DObjectMgr + * until both of their queues are empty and they can both be safely + * shutdown. + */ + protected class ShutdownUnit extends Unit + { + // run on the invoker thread + public boolean invoke () + { + if (checkLoops()) { + return false; + + // if the invoker queue is not empty, we put ourselves back on it + } else if (_queue.hasElements()) { + _loopCount++; + postUnit(this); + return false; + + } else { + // the invoker is empty, let's go over to the omgr + _loopCount = 0; + _passCount++; + return true; + } + } + + // run on the dobj thread + public void handleResult () + { + if (checkLoops()) { + return; + + // if the queue isn't empty, re-post + } else if (!_omgr.queueIsEmpty()) { + _loopCount++; + _omgr.postUnit(this); + + // if the invoker still has stuff and we're still under the pass + // limit, go ahead and pass it back to the invoker + } else if (_queue.hasElements() && (_passCount < MAX_PASSES)) { + // pass the buck back to the invoker + _loopCount = 0; + postUnit(this); + + } else { + // otherwise end it, and complain if we're ending it + // because of passes + if (_passCount >= MAX_PASSES) { + Log.warning("Shutdown Unit passed 50 times without " + + "finishing, shutting down harshly."); + } + doShutdown(); + } + } + + /** + * Check to make sure we haven't looped too many times. + */ + protected boolean checkLoops () + { + if (_loopCount > MAX_LOOPS) { + Log.warning("Shutdown Unit looped on one thread 10000 times " + + "without finishing, shutting down harshly."); + doShutdown(); + return true; + } + + return false; + } + + /** + * Do the actual shutdown. + */ + protected void doShutdown () + { + _omgr.harshShutdown(); // end the dobj thread + + // end the invoker thread + postUnit(new Unit() { + public boolean invoke () { + _running = false; + return false; + } + }); + } + + /** The number of times we've been passed to the object manager. */ + protected int _passCount = 0; + + /** How many times we've looped on the thread we're currently on. */ + protected int _loopCount = 0; + + /** The maximum number of passes we allow before just ending things. */ + protected static final int MAX_PASSES = 50; + + /** The maximum number of loops we allow before just ending things. */ + protected static final int MAX_LOOPS = 10000; + } + + protected PresentsDObjectMgr _omgr; +} diff --git a/src/java/com/threerings/presents/server/PresentsServer.java b/src/java/com/threerings/presents/server/PresentsServer.java index 143545095..b631f49f1 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.40 2004/06/03 09:03:06 mdb Exp $ +// $Id: PresentsServer.java,v 1.41 2004/06/29 03:22:22 mdb Exp $ package com.threerings.presents.server; @@ -16,7 +16,6 @@ import com.threerings.presents.Log; import com.threerings.presents.client.Client; import com.threerings.presents.server.net.ConnectionManager; import com.threerings.presents.server.util.SafeInterval; -import com.threerings.presents.util.Invoker; /** * The presents server provides a central point of access to the various @@ -75,7 +74,7 @@ public class PresentsServer /** This is used to invoke background tasks that should not be allowed * to tie up the distributed object manager thread. This is generally * used to talk to databases and other (relatively) slow entities. */ - public static Invoker invoker; + public static PresentsInvoker invoker; /** * Initializes all of the server services and prepares for operation. @@ -96,7 +95,7 @@ public class PresentsServer omgr = new PresentsDObjectMgr(); // create and start up our invoker - invoker = new Invoker(omgr); + invoker = new PresentsInvoker(omgr); invoker.start(); // create our connection manager diff --git a/src/java/com/threerings/presents/util/Invoker.java b/src/java/com/threerings/presents/util/Invoker.java deleted file mode 100644 index 060015018..000000000 --- a/src/java/com/threerings/presents/util/Invoker.java +++ /dev/null @@ -1,314 +0,0 @@ -// -// $Id: Invoker.java,v 1.15 2004/06/13 08:03:45 mdb Exp $ - -package com.threerings.presents.util; - -import java.util.HashMap; -import java.util.Iterator; - -import com.samskivert.util.Histogram; -import com.samskivert.util.LoopingThread; -import com.samskivert.util.Queue; -import com.samskivert.util.StringUtil; - -import com.threerings.presents.Log; -import com.threerings.presents.server.PresentsDObjectMgr; -import com.threerings.presents.server.PresentsServer.Reporter; -import com.threerings.presents.server.PresentsServer; - -/** - * The invoker is used to invoke self-contained units of code on an - * invoking thread. Each invoker is associated with its own thread and - * that thread is used to invoke all of the units posted to that invoker - * in the order in which they were posted. The invoker also provides a - * convenient mechanism for processing the result of an invocation back on - * the dobjmgr thread. - * - *

The invoker is a useful tool for services that need to block and - * therefore cannot be run on the distributed object thread. For example, - * a user of the Presents system might provide an invoker on which to run - * database queries. - * - *

Bear in mind that each invoker instance runs units on its own - * thread and care must be taken to ensure that code running on separate - * invokers properly synchronizes access to shared information. Where - * possible, complete isolation of the services provided by a particular - * invoker is desirable. - */ -public class Invoker extends LoopingThread - implements Reporter -{ - /** - * The unit encapsulates a unit of executable code that will be run on - * the invoker thread. It also provides facilities for additional code - * to be run on the dobjmgr thread once the primary code has completed - * on the invoker thread. - */ - public static abstract class Unit implements Runnable - { - /** The default constructor. */ - public Unit () {} - - /** Creates an invoker unit which will report the supplied name in - * {@link #toString}. */ - public Unit (String name) - { - _name = name; - } - - /** - * This method is called on the invoker thread and should be used - * to perform the primary function of the unit. It can return true - * to cause the handleResult method to be - * subsequently invoked on the dobjmgr thread (generally to allow - * the results of the invocation to be acted upon back in the - * context of the distributed object world) or false to indicate - * that no further processing should be performed. - * - *

Note that an invocation unit can do things like post events - * from the invoker thread (which includes modification of - * distributed object fields) and need not jump over to the - * dobjmgr thread to do so. However, it cannot read - * distributed object fields from the invoker thread. Any field - * values needed during the course of the invocation should be - * provided from the dobjmgr thread at the time that the - * invocation unit is created and posted. - * - * @return true if the handleResult method should be - * invoked on the dobjmgr thread, false if not. - */ - public abstract boolean invoke (); - - /** - * Invocation unit implementations can implement this function to - * perform any post-unit-invocation processing back on the dobjmgr - * thread. It will be invoked if invoke returns true. - */ - public void handleResult () - { - // do nothing by default - } - - // we want to be a runnable to make the dobjmgr happy, but we'd - // like for invocation unit implementations to be able to put - // their result handling code into an aptly named method - public void run () - { - handleResult(); - } - - /** Returns the name of this invoker. */ - public String toString () - { - return _name; - } - - protected String _name = "Unknown"; - } - - /** - * Creates an invoker that will post results to the supplied - * distributed object manager. - */ - public Invoker (PresentsDObjectMgr omgr) - { - super("presents.Invoker"); - _omgr = omgr; - if (PERF_TRACK) { - PresentsServer.registerReporter(this); - } - } - - /** - * Posts a unit to this invoker for subsequent invocation on the - * invoker's thread. - */ - public void postUnit (Unit unit) - { - // simply append it to the queue - _queue.append(unit); - } - - // documentation inherited - public void iterate () - { - // pop the next item off of the queue - Unit unit = (Unit) _queue.get(); - - long start; - if (PERF_TRACK) { - start = System.currentTimeMillis(); - _unitsRun++; - } - - try { - if (unit.invoke()) { - // if it returned true, we post it to the dobjmgr - // thread to invoke the result processing - _omgr.postUnit(unit); - } - - // track some performance metrics - if (PERF_TRACK) { - long duration = System.currentTimeMillis() - start; - Object key = unit.getClass(); - Histogram histo = (Histogram)_tracker.get(key); - if (histo == null) { - // track in buckets of 50ms up to 500ms - _tracker.put(key, histo = new Histogram(0, 50, 10)); - } - histo.addValue((int)duration); - - // report long runners - if (duration > 500L) { - Log.warning("Invoker unit ran for '" + duration + "ms: " + - unit + "' (" + key + ")."); - } - } - - } catch (Exception e) { - Log.warning("Invocation unit failed [unit=" + unit + "]."); - Log.logStackTrace(e); - } - } - - /** - * Will do a sophisticated shutdown of both itself and the DObjectManager - * thread. - */ - public void shutdown () - { - _queue.append(new ShutdownUnit()); - } - - // documentation inherited from interface - public void appendReport (StringBuffer buffer, long now, long sinceLast) - { - buffer.append("* presents.util.Invoker:\n"); - buffer.append("- Units executed: ").append(_unitsRun).append("\n"); - _unitsRun = 0; - for (Iterator iter = _tracker.keySet().iterator(); iter.hasNext(); ) { - Class key = (Class)iter.next(); - Histogram histo = (Histogram)_tracker.get(key); - buffer.append(" "); - buffer.append(StringUtil.shortClassName(key)).append(": "); - buffer.append(histo.summarize()).append("\n"); - histo.clear(); - } - } - - /** - * This unit gets posted back and forth between the invoker and DObjectMgr - * until both of their queues are empty and they can both be safely - * shutdown. - */ - protected class ShutdownUnit extends Unit - { - // run on the invoker thread - public boolean invoke () - { - if (checkLoops()) { - return false; - - // if the invoker queue is not empty, we put ourselves back on it - } else if (_queue.hasElements()) { - _loopCount++; - postUnit(this); - return false; - - } else { - // the invoker is empty, let's go over to the omgr - _loopCount = 0; - _passCount++; - return true; - } - } - - // run on the dobj thread - public void handleResult () - { - if (checkLoops()) { - return; - - // if the queue isn't empty, re-post - } else if (!_omgr.queueIsEmpty()) { - _loopCount++; - _omgr.postUnit(this); - - // if the invoker still has stuff and we're still under the pass - // limit, go ahead and pass it back to the invoker - } else if (_queue.hasElements() && (_passCount < MAX_PASSES)) { - // pass the buck back to the invoker - _loopCount = 0; - postUnit(this); - - } else { - // otherwise end it, and complain if we're ending it - // because of passes - if (_passCount >= MAX_PASSES) { - Log.warning("Shutdown Unit passed 50 times without " + - "finishing, shutting down harshly."); - } - doShutdown(); - } - } - - /** - * Check to make sure we haven't looped too many times. - */ - protected boolean checkLoops () - { - if (_loopCount > MAX_LOOPS) { - Log.warning("Shutdown Unit looped on one thread 10000 times " + - "without finishing, shutting down harshly."); - doShutdown(); - return true; - } - - return false; - } - - /** - * Do the actual shutdown. - */ - protected void doShutdown () - { - _omgr.harshShutdown(); // end the dobj thread - - // end the invoker thread - postUnit(new Unit() { - public boolean invoke () { - _running = false; - return false; - } - }); - } - - /** The number of times we've been passed to the object manager. */ - protected int _passCount = 0; - - /** How many times we've looped on the thread we're currently on. */ - protected int _loopCount = 0; - - /** The maximum number of passes we allow before just ending things. */ - protected static final int MAX_PASSES = 50; - - /** The maximum number of loops we allow before just ending things. */ - protected static final int MAX_LOOPS = 10000; - } - - /** The invoker's queue of units to be executed. */ - protected Queue _queue = new Queue(); - - /** The object manager with which we're working. */ - protected PresentsDObjectMgr _omgr; - - /** Tracks the counts of invocations by unit's class. */ - protected HashMap _tracker = new HashMap(); - - /** The total number of invoker units run since the last report. */ - protected int _unitsRun; - - /** Whether or not to track invoker unit performance. */ - protected static final boolean PERF_TRACK = true; -} diff --git a/src/java/com/threerings/whirled/server/SceneManager.java b/src/java/com/threerings/whirled/server/SceneManager.java index fbdb0ff44..867017627 100644 --- a/src/java/com/threerings/whirled/server/SceneManager.java +++ b/src/java/com/threerings/whirled/server/SceneManager.java @@ -1,13 +1,13 @@ // -// $Id: SceneManager.java,v 1.17 2004/05/19 22:09:04 ray Exp $ +// $Id: SceneManager.java,v 1.18 2004/06/29 03:22:22 mdb Exp $ package com.threerings.whirled.server; import com.samskivert.io.PersistenceException; +import com.samskivert.util.Invoker; import com.threerings.crowd.server.PlaceManager; import com.threerings.presents.server.PresentsServer; -import com.threerings.presents.util.Invoker; import com.threerings.whirled.Log; import com.threerings.whirled.data.Scene; diff --git a/src/java/com/threerings/whirled/server/SceneRegistry.java b/src/java/com/threerings/whirled/server/SceneRegistry.java index 73d37d855..c5e5c94d3 100644 --- a/src/java/com/threerings/whirled/server/SceneRegistry.java +++ b/src/java/com/threerings/whirled/server/SceneRegistry.java @@ -1,12 +1,12 @@ // -// $Id: SceneRegistry.java,v 1.22 2004/02/25 14:50:28 mdb Exp $ +// $Id: SceneRegistry.java,v 1.23 2004/06/29 03:22:22 mdb Exp $ package com.threerings.whirled.server; import java.util.ArrayList; import com.samskivert.util.HashIntMap; -import com.threerings.presents.util.Invoker; +import com.samskivert.util.Invoker; import com.threerings.presents.server.InvocationManager;