From 1bca84927f9d0c1aa6ea0a7f6f3e84932badb554 Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Sat, 9 Feb 2002 07:50:37 +0000 Subject: [PATCH] Added support for DObject transactions which are collections of events that are dispatched over the network at once and processed all at once. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@979 542714f4-19e9-0310-aa3c-eee0fc999fb1 --- .../presents/client/ClientDObjectMgr.java | 15 +- .../presents/dobj/CompoundEvent.java | 168 +++++++++++++++++ .../com/threerings/presents/dobj/DObject.java | 177 ++++++++++++++---- .../presents/server/PresentsDObjectMgr.java | 114 ++++++----- 4 files changed, 389 insertions(+), 85 deletions(-) create mode 100644 src/java/com/threerings/presents/dobj/CompoundEvent.java diff --git a/src/java/com/threerings/presents/client/ClientDObjectMgr.java b/src/java/com/threerings/presents/client/ClientDObjectMgr.java index a241370e5..a34998f5e 100644 --- a/src/java/com/threerings/presents/client/ClientDObjectMgr.java +++ b/src/java/com/threerings/presents/client/ClientDObjectMgr.java @@ -1,9 +1,11 @@ // -// $Id: ClientDObjectMgr.java,v 1.11 2002/02/07 00:24:36 shaper Exp $ +// $Id: ClientDObjectMgr.java,v 1.12 2002/02/09 07:50:37 mdb Exp $ package com.threerings.presents.client; import java.util.ArrayList; +import java.util.List; + import com.samskivert.util.HashIntMap; import com.samskivert.util.Queue; import com.samskivert.util.StringUtil; @@ -153,6 +155,17 @@ public class ClientDObjectMgr { // Log.info("Dispatch event: " + event); + // if this is a compound event, we need to process its contained + // events in order + if (event instanceof CompoundEvent) { + List events = ((CompoundEvent)event).getEvents(); + int ecount = events.size(); + for (int i = 0; i < ecount; i++) { + dispatchEvent((DEvent)events.get(i)); + } + return; + } + // look up the object on which we're dispatching this event DObject target = (DObject)_ocache.get(event.getTargetOid()); if (target == null) { diff --git a/src/java/com/threerings/presents/dobj/CompoundEvent.java b/src/java/com/threerings/presents/dobj/CompoundEvent.java new file mode 100644 index 000000000..0e5ef5118 --- /dev/null +++ b/src/java/com/threerings/presents/dobj/CompoundEvent.java @@ -0,0 +1,168 @@ +// +// $Id: CompoundEvent.java,v 1.1 2002/02/09 07:50:37 mdb Exp $ + +package com.threerings.presents.dobj; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +import java.util.ArrayList; +import java.util.List; + +import com.threerings.presents.io.TypedObjectFactory; + +/** + * Used to manage and submit groups of events on a collection of + * distributed objects in a single transaction. + * + * @see DObject#startTransaction + */ +public class CompoundEvent extends TypedEvent +{ + /** The typed object code for this event. */ + public static final short TYPE = TYPE_BASE + 50; + + /** + * Constructs a compound event and prepares it for operation. + */ + public CompoundEvent (DObjectManager omgr) + { + super(0); // we don't have a single target object oid + _omgr = omgr; + } + + /** + * Lets the event know that this dobject is participating in their + * transaction. The supplied dobject will have their transaction + * cleared when this event is committed or cancelled. + */ + public void addObject (DObject object) + { + if (_participants == null) { + _participants = new ArrayList(); + } + _participants.add(object); + } + + /** + * Posts an event to this transaction. The event will be delivered as + * part of the entire transaction if it is committed or discarded if + * the transaction is cancelled. + */ + public void postEvent (TypedEvent event) + { + _events.add(event); + } + + /** + * Returns the list of events contained within this compound event. + */ + public List getEvents () + { + return _events; + } + + /** + * Commits this transaction by posting this event to the distributed + * object event queue. All participating dobjects will have their + * transaction references cleared and will go back to normal + * operation. + */ + public void commit () + { + // first clear our participants + clearParticipants(); + + // then post this event onto the queue + _omgr.postEvent(this); + } + + /** + * Cancels this transaction. All events posted to this transaction + * will be discarded. + */ + public void cancel () + { + // clear our participants + clearParticipants(); + // clear our event queue in case someone holds onto us + _events.clear(); + } + + /** + * Nothing to apply here. + */ + public boolean applyToObject (DObject target) + throws ObjectAccessException + { + return false; + } + + // documentation inherited + public short getType () + { + return TYPE; + } + + // documentation inherited + public void writeTo (DataOutputStream out) + throws IOException + { + super.writeTo(out); + int ecount = _events.size(); + for (int i = 0; i < ecount; i++) { + TypedEvent event = (TypedEvent)_events.get(i); + TypedObjectFactory.writeTo(out, event); + } + } + + // documentation inherited + public void readFrom (DataInputStream in) + throws IOException + { + super.readFrom(in); + int ecount = in.readInt(); + for (int i = 0; i < ecount; i++) { + _events.add(TypedObjectFactory.readFrom(in)); + } + } + + /** + * Calls out to all of the participating dobjects, clearing their + * transaction reference. + */ + protected void clearParticipants () + { + if (_participants != null) { + int psize = _participants.size(); + for (int i = 0; i < psize; i++) { + DObject obj = (DObject)_participants.get(i); + obj.clearTransaction(); + } + _participants = null; + } + } + + // documentation inherited + protected void toString (StringBuffer buf) + { + buf.append("COMPOUND:"); + super.toString(buf); + for (int i = 0; i < _events.size(); i++) { + buf.append(", ").append(_events.get(i)); + } + } + + /** The object manager that we'll post ourselves to when we're + * committed. */ + protected DObjectManager _omgr; + + /** A list of the events associated with this compound event. */ + protected ArrayList _events = new ArrayList(); + + /** A list of the dobject participants in this transaction. They will + * be notified when we are committed or cancelled so that they can + * stop posting their events to us. */ + protected ArrayList _participants; +} diff --git a/src/java/com/threerings/presents/dobj/DObject.java b/src/java/com/threerings/presents/dobj/DObject.java index 0a1b37906..f89451277 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.38 2002/02/06 22:47:28 mdb Exp $ +// $Id: DObject.java,v 1.39 2002/02/09 07:50:37 mdb Exp $ package com.threerings.presents.dobj; @@ -130,8 +130,8 @@ public class DObject // the last subscriber from our list; we also want to be sure // that we're still active otherwise there's no need to notify // our objmgr because we don't have one - if (--_scount == 0 && _mgr != null) { - _mgr.removedLastSubscriber(this); + if (--_scount == 0 && _omgr != null) { + _omgr.removedLastSubscriber(this); } } } @@ -226,8 +226,7 @@ public class DObject public void releaseLock (String name) { // queue up a release lock event - ReleaseLockEvent event = new ReleaseLockEvent(_oid, name); - _mgr.postEvent(event); + postEvent(new ReleaseLockEvent(_oid, name)); } /** @@ -257,7 +256,7 @@ public class DObject */ public void destroy () { - _mgr.postEvent(new ObjectDestroyedEvent(_oid)); + postEvent(new ObjectDestroyedEvent(_oid)); } /** @@ -410,7 +409,7 @@ public class DObject */ public boolean isActive () { - return _mgr != null; + return _omgr != null; } /** @@ -421,9 +420,9 @@ public class DObject * * @see DObjectManager#createObject */ - public void setManager (DObjectManager mgr) + public void setManager (DObjectManager omgr) { - _mgr = mgr; + _omgr = omgr; } /** @@ -448,16 +447,115 @@ public class DObject return buf.append("]").toString(); } + /** + * Begins a transaction on this distributed object. In some + * situations, it is desirable to cause multiple changes to + * distributed object fields in one unified operation. Starting a + * transaction causes all subsequent field modifications to be stored + * in a single compound event which can then be committed, dispatching + * and applying all included events in a single group. Additionally, + * the events are dispatched over the network in a single unit which + * can significantly enhance network efficiency. + * + *

When the transaction is complete, the caller must call {@link + * #commitTransaction} or {@link CompoundEvent#commit} to commit the + * transaction and release all involved objects back to their normal + * non-transacting state. If the caller decides not to commit their + * transaction, they must call {@link #cancelTransaction} or {@link + * CompoundEvent#cancel} to cancel the transaction and release all + * involved objects. Failure to do so will cause the pooch to be + * totally screwed. + * + *

Note: like all other distributed object operations, + * transactions are not thread safe. It is expected that a single + * thread will handle all distributed object operations and that + * thread will begin and complete a transaction before giving up + * control to unknown code which might try to operate on the + * transacting distributed object (or objects). + * + * @return the compound event that encapsulates the transaction. This + * can be ignored if the transaction will be limited to this object, + * but if it is desired that other objects be involved in the + * transaction, the caller will need to pass the {@link CompoundEvent} + * returned by this method to a call to {@link #joinTransaction} on + * the other objects that are involved. + */ + public CompoundEvent startTransaction () + { + if (_tevent != null) { + String errmsg = "Cannot start transaction on dobject that " + + "is already transacting [dobj=" + this + "]"; + throw new IllegalStateException(errmsg); + } + _tevent = new CompoundEvent(_omgr); + _tevent.addObject(this); + return _tevent; + } + + /** + * Causes this object to join the supplied transaction. See {@link + * #startTransaction} for more information on transactions. + */ + public void joinTransaction (CompoundEvent event) + { + if (_tevent != null) { + String errmsg = "Cannot join transaction while already " + + "transacting [dobj=" + this + "]"; + throw new IllegalStateException(errmsg); + } + _tevent = event; + _tevent.addObject(this); + } + + /** + * Commits the transaction in which this distributed object is + * involved. + * + * @see CompoundEvent#commit + */ + public void commitTransaction () + { + if (_tevent == null) { + String errmsg = "Cannot commit: not involved in a transaction " + + "[dobj=" + this + "]"; + throw new IllegalStateException(errmsg); + } + _tevent.commit(); + } + + /** + * Cancels the transaction in which this distributed object is + * involved. + * + * @see CompoundEvent#cancel + */ + public void cancelTransaction () + { + if (_tevent == null) { + String errmsg = "Cannot cancel: not involved in a transaction " + + "[dobj=" + this + "]"; + throw new IllegalStateException(errmsg); + } + _tevent.cancel(); + } + + /** + * Removes this object from participation in any transaction in which + * it might be taking part. + */ + protected void clearTransaction () + { + _tevent = null; + } + /** * Called by derived instances when an attribute setter method was * called. */ protected void requestAttributeChange (String name, Object value) { - // generate an attribute changed event - DEvent event = new AttributeChangedEvent(_oid, name, value); - // and dispatch it to our dobjmgr - _mgr.postEvent(event); + // dispatch an attribute changed event + postEvent(new AttributeChangedEvent(_oid, name, value)); } /** @@ -465,10 +563,8 @@ public class DObject */ protected void requestOidAdd (String name, int oid) { - // generate an object added event - DEvent event = new ObjectAddedEvent(_oid, name, oid); - // and dispatch it to our dobjmgr - _mgr.postEvent(event); + // dispatch an object added event + postEvent(new ObjectAddedEvent(_oid, name, oid)); } /** @@ -476,10 +572,8 @@ public class DObject */ protected void requestOidRemove (String name, int oid) { - // generate an object removed event - DEvent event = new ObjectRemovedEvent(_oid, name, oid); - // and dispatch it to our dobjmgr - _mgr.postEvent(event); + // dispatch an object removed event + postEvent(new ObjectRemovedEvent(_oid, name, oid)); } /** @@ -489,11 +583,9 @@ public class DObject { try { DSet set = (DSet)getAttribute(name); - // generate an element added event - DEvent event = new ElementAddedEvent( - _oid, name, elem, !set.homogenous()); - // and dispatch it to our dobjmgr - _mgr.postEvent(event); + // dispatch an element added event + postEvent(new ElementAddedEvent( + _oid, name, elem, !set.homogenous())); } catch (ObjectAccessException oae) { Log.warning("Unable to request elementAdd [name=" + name + @@ -506,10 +598,8 @@ public class DObject */ protected void requestElementRemove (String name, Object key) { - // generate an element removed event - DEvent event = new ElementRemovedEvent(_oid, name, key); - // and dispatch it to our dobjmgr - _mgr.postEvent(event); + // dispatch an element removed event + postEvent(new ElementRemovedEvent(_oid, name, key)); } /** @@ -519,11 +609,9 @@ public class DObject { try { DSet set = (DSet)getAttribute(name); - // generate an element updated event - DEvent event = new ElementUpdatedEvent( - _oid, name, elem, !set.homogenous()); - // and dispatch it to our dobjmgr - _mgr.postEvent(event); + // dispatch an element updated event + postEvent(new ElementUpdatedEvent( + _oid, name, elem, !set.homogenous())); } catch (ObjectAccessException oae) { Log.warning("Unable to request elementUpdate [name=" + name + @@ -531,11 +619,24 @@ public class DObject } } + /** + * Posts the specified event either to our dobject manager or to the + * compound event for which we are currently transacting. + */ + protected void postEvent (TypedEvent event) + { + if (_tevent != null) { + _tevent.postEvent(event); + } else { + _omgr.postEvent(event); + } + } + /** Our object id. */ protected int _oid; /** A reference to our object manager. */ - protected DObjectManager _mgr; + protected DObjectManager _omgr; /** A list of outstanding locks. */ protected Object[] _locks; @@ -548,4 +649,8 @@ public class DObject /** Our subscriber count. */ protected int _scount; + + /** The compound event associated with our transaction, if we're + * currently in a transaction. */ + protected CompoundEvent _tevent; } diff --git a/src/java/com/threerings/presents/server/PresentsDObjectMgr.java b/src/java/com/threerings/presents/server/PresentsDObjectMgr.java index 722f1c877..802e33f05 100644 --- a/src/java/com/threerings/presents/server/PresentsDObjectMgr.java +++ b/src/java/com/threerings/presents/server/PresentsDObjectMgr.java @@ -1,10 +1,11 @@ // -// $Id: PresentsDObjectMgr.java,v 1.22 2002/02/02 09:53:00 mdb Exp $ +// $Id: PresentsDObjectMgr.java,v 1.23 2002/02/09 07:50:37 mdb Exp $ package com.threerings.presents.server; import java.lang.reflect.*; import java.util.HashMap; +import java.util.List; import com.samskivert.util.HashIntMap; import com.samskivert.util.Queue; @@ -132,62 +133,79 @@ public class PresentsDObjectMgr implements RootDObjectManager Log.warning("Execution unit failed [unit=" + unit + "]."); Log.logStackTrace(e); } - continue; - } - // otherwise it's an event, so we do more complicated - // processing - DEvent event = (DEvent)unit; - - // look up the target object - DObject target = (DObject)_objects.get(event.getTargetOid()); - if (target == null) { - Log.warning("Event target no longer exists " + - "[event=" + event + "]."); - continue; - } - - // check the event's permissions - if (!target.checkPermissions(event)) { - Log.warning("Event failed permissions check " + - "[event=" + event + ", target=" + target + "]."); - continue; - } - - try { - // do any internal management necessary based on this - // event - Method helper = (Method)_helpers.get(event.getClass()); - if (helper != null) { - // invoke the helper method - Object rv = - helper.invoke(this, new Object[] { event, target }); - // if helper returns false, we abort event processing - if (!((Boolean)rv).booleanValue()) { - continue; - } + } else if (unit instanceof CompoundEvent) { + // if this is a compound event, we need to apply each + // event indivdually + CompoundEvent event = (CompoundEvent)unit; + List events = event.getEvents(); + int ecount = events.size(); + for (int i = 0; i < ecount; i++) { + processEvent((DEvent)events.get(i)); } - // everything's good so far, apply the event to the object - boolean notify = event.applyToObject(target); - - // if the event returns false from applyToObject, this - // means it's a silent event and we shouldn't notify the - // listeners - if (notify) { - target.notifyListeners(event); - } - - } catch (Exception e) { - Log.warning("Failure processing event [event=" + event + - ", target=" + target + "]."); - Log.logStackTrace(e); + } else { + // otherwise it's a regular event, so do the standard + // processing + processEvent((DEvent)unit); } } Log.info("DOMGR exited."); } + /** + * Performs the processing associated with an event, notifying + * subscribers and the like. + */ + protected void processEvent (DEvent event) + { + // look up the target object + DObject target = (DObject)_objects.get(event.getTargetOid()); + if (target == null) { + Log.warning("Event target no longer exists " + + "[event=" + event + "]."); + return; + } + + // check the event's permissions + if (!target.checkPermissions(event)) { + Log.warning("Event failed permissions check " + + "[event=" + event + ", target=" + target + "]."); + return; + } + + try { + // do any internal management necessary based on this + // event + Method helper = (Method)_helpers.get(event.getClass()); + if (helper != null) { + // invoke the helper method + Object rv = + helper.invoke(this, new Object[] { event, target }); + // if helper returns false, we abort event processing + if (!((Boolean)rv).booleanValue()) { + return; + } + } + + // everything's good so far, apply the event to the object + boolean notify = event.applyToObject(target); + + // if the event returns false from applyToObject, this + // means it's a silent event and we shouldn't notify the + // listeners + if (notify) { + target.notifyListeners(event); + } + + } catch (Exception e) { + Log.warning("Failure processing event [event=" + event + + ", target=" + target + "]."); + Log.logStackTrace(e); + } + } + /** * Requests that the dobjmgr shut itself down. It will exit the event * processing loop which cause run() to return.