From 9d8933829674b169b45f76b2a2fc2bcf5c7072d4 Mon Sep 17 00:00:00 2001 From: Mike Thomas Date: Fri, 13 Jun 2008 23:56:13 +0000 Subject: [PATCH] Add the ability to specify constraints for the order in which shutdowners should run. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@5185 542714f4-19e9-0310-aa3c-eee0fc999fb1 --- .../presents/server/ShutdownManager.java | 177 +++++++++++++++++- 1 file changed, 173 insertions(+), 4 deletions(-) diff --git a/src/java/com/threerings/presents/server/ShutdownManager.java b/src/java/com/threerings/presents/server/ShutdownManager.java index 2c6857576..f948ba2b7 100644 --- a/src/java/com/threerings/presents/server/ShutdownManager.java +++ b/src/java/com/threerings/presents/server/ShutdownManager.java @@ -21,6 +21,9 @@ package com.threerings.presents.server; +import java.util.ArrayList; +import java.util.HashMap; + import com.google.inject.Inject; import com.google.inject.Singleton; @@ -46,6 +49,8 @@ public class ShutdownManager public void shutdown (); } + public static enum Constraint { RUNS_BEFORE, RUNS_AFTER }; + @Inject ShutdownManager (@EventQueue RunQueue dobjq) { _dobjq = dobjq; @@ -67,6 +72,20 @@ public class ShutdownManager _downers.remove(downer); } + /** + * Adds a constraint that a certain shutdowner must be run before another. + */ + public void addConstraint (Shutdowner lhs, Constraint constraint, Shutdowner rhs) + throws IllegalArgumentException + { + if (lhs == null || rhs == null) { + throw new IllegalArgumentException("Cannot add constraint about null shutdowner."); + } + Shutdowner before = (constraint == Constraint.RUNS_BEFORE) ? lhs : rhs; + Shutdowner after = (constraint == Constraint.RUNS_BEFORE) ? rhs : lhs; + _downers.addDependency(after, before); + } + /** * Queues up a request to shutdown on the dobjmgr thread. This method may be safely called from * any thread. @@ -85,11 +104,17 @@ public class ShutdownManager */ public void shutdown () { - ObserverList downers = _downers; - if (downers == null) { + if (_downers == null) { log.warning("Refusing repeat shutdown request."); return; } + + ObserverList downers = ObserverList.newSafeInOrder(); + + while (!_downers.isEmpty()) { + downers.add(_downers.removeAvailableElement()); + } + _downers = null; // shut down all shutdown participants @@ -101,9 +126,153 @@ public class ShutdownManager }); } + /** + * We maintain a bidirectional graph to manage the order that the items are removed. Children + * must wait until their parents are accessed - thus removing an available element means that + * a node without parents (an orphan) is removed and returned. + * @param + */ + protected class DependencyGraph + { + /** + * Adds an element with no initial dependencies from the graph. + */ + public void add (T element) + { + DependencyNode node = new DependencyNode(element); + _nodes.put(element, node); + _orphans.add(element); + } + + /** + * Removes an element and its dependencies from the graph. + */ + public void remove (T element) + { + DependencyNode node = _nodes.remove(element); + _orphans.remove(element); + + // Remove ourselves as a child of our parents. + for (DependencyNode parent : node.parents) { + parent.children.remove(node); + } + + // Remove ourselves as a parent of our children, possibly orphaning them. + for (DependencyNode child : node.children) { + child.parents.remove(node); + if (child.parents.isEmpty()) { + _orphans.add(child.content); + } + } + } + + /** + * Removes and returns an element which is available, meaning not dependent upon any other + * still in the graph. + */ + public T removeAvailableElement () + { + T elem = _orphans.get(0); + DependencyNode node = _nodes.get(elem); + remove(elem); + + return elem; + } + + /** + * Returns the number of elements in the graph. + */ + public int size () + { + return _nodes.size(); + } + + /** + * Returns whether there are no more elements in the graph. + */ + public boolean isEmpty () + { + return size() == 0; + } + + /** + * Records a new dependency of the dependant upon the dependee. + */ + public void addDependency (T dependant, T dependee) + throws IllegalArgumentException + { + _orphans.remove(dependant); + DependencyNode dependantNode = _nodes.get(dependant); + DependencyNode dependeeNode = _nodes.get(dependee); + + if (dependsOn(dependee, dependant)) { + throw new IllegalArgumentException("Refusing to create circular dependency."); + } + dependantNode.parents.add(dependeeNode); + dependeeNode.children.add(dependantNode); + + } + + /** + * Returns whether elem1 is designated to depend on elem2. + */ + public boolean dependsOn (T elem1, T elem2) + { + DependencyNode node1 = _nodes.get(elem1); + DependencyNode node2 = _nodes.get(elem2); + + ArrayList> nodesToCheck = new ArrayList>(); + ArrayList> nodesAlreadyChecked = new ArrayList>(); + nodesToCheck.addAll(node1.parents); + + // We prevent circular dependencies when we add dependencies. Otherwise, this'd be + // potentially non-terminating. + while (!nodesToCheck.isEmpty()) { + // We take it off the end since we don't care about order and this is faster. + DependencyNode checkNode = nodesToCheck.remove(nodesToCheck.size() - 1); + + if (nodesAlreadyChecked.contains(checkNode)) { + // We've seen him before, no need to check again. + continue; + + } else if (checkNode == node2) { + // We've found our dependency + return true; + + } else { + nodesAlreadyChecked.add(checkNode); + nodesToCheck.addAll(checkNode.parents); + } + } + + return false; + } + + /** All the nodes included in the graph. */ + protected HashMap> _nodes = new HashMap>(); + + /** Nodes in the graph with no parents/dependencies. */ + protected ArrayList _orphans = new ArrayList(); + + protected class DependencyNode + { + public T content; + public ArrayList> parents; + public ArrayList> children; + + public DependencyNode (T contents) + { + this.content = contents; + this.parents = new ArrayList>(); + this.children = new ArrayList>(); + } + } + } + + /** All of the registered shutdowners along with related constraints. */ + protected DependencyGraph _downers = new DependencyGraph(); + /** The queue we'll use to get onto the dobjmgr thread before shutting down. */ protected RunQueue _dobjq; - /** A list of shutdown participants. */ - protected static ObserverList _downers = ObserverList.newSafeInOrder(); }