Asynchronous programming is hard. We have long had the problem where code would
queue up an invoker unit which would go off and do some database stuff and then by the time it came back and was ready to publish its results to a distributed object, the object in question would have been destroyed for any of a variety of fairly natural reasons (client disconnected or logged off, game was abandoned, dog ate homework). One "solution" to this problem would be to litter our games' code with thousands of calls to isActive() in the handleResult() methods of our invoker units. We've done a bit of that in Yohoho but I've resisted starting down that path in our other games. Another solution would be to create an Invoker.Unit wrapper that takes a reference to the distributed object (or objects) that it will be modifying and have the common unit code check that the object(s) in question are still alive at the end of the asynchronous operation and not call handleResult() if they are not. This has numerous problems: what do you do if one object is alive but not another, how do you incorporate this functionality in with the numerous other Invoker.Unit derivations we have that simplify our lives in other ways (without getting crazy and starting to use something like AOP), do you silently abort the operation or log something? So instead, I've come around to the idea that this is simply a dirty fact of life in asynchronous programming and the fact that we can accept modifications to distributed state after the distribted object in question is dead is a good thing. We used to log a warning every time this happened and freak out even more substantially if one tried to start a transaction on a dead object. Now we will simply log an informational message (I don't think this sort of thing should be silently ignored because there are some cases where it is an indication of incorrect code, those are simply more rare). We will also allow a transaction to be started on a dead object and when the transaction is committed, all the events involved will be dropped just like a single modification would have been dropped on that object. This allows the most sensible thing to happen which is any results that are published to still live objects will actually be published and results published to dead objects will be dropped without making a big fuss. Since a dead object by definition cannot have subscribers, no one could possibly have cared about the dropped events anyway. Also widened. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@4545 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
@@ -39,34 +39,28 @@ import com.threerings.util.TrackedObject;
|
|||||||
import com.threerings.presents.Log;
|
import com.threerings.presents.Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The distributed object forms the foundation of the Presents system. All
|
* The distributed object forms the foundation of the Presents system. All information shared among
|
||||||
* information shared among users of the system is done via distributed
|
* users of the system is done via distributed objects. A distributed object has a set of
|
||||||
* objects. A distributed object has a set of listeners. These listeners
|
* listeners. These listeners have access to the object or a proxy of the object and therefore have
|
||||||
* have access to the object or a proxy of the object and therefore have
|
|
||||||
* access to the data stored in the object's members at all times.
|
* access to the data stored in the object's members at all times.
|
||||||
*
|
*
|
||||||
* <p> Additionally, an object as a set of subscribers. Subscribers manage
|
* <p> Additionally, an object as a set of subscribers. Subscribers manage the lifespan of the
|
||||||
* the lifespan of the object; while a subscriber is subscribed, the
|
* object; while a subscriber is subscribed, the listeners registered with an object will be
|
||||||
* listeners registered with an object will be notified of events. When
|
* notified of events. When the subscriber unsubscribes, the object becomes non-live and the
|
||||||
* the subscriber unsubscribes, the object becomes non-live and the
|
* listeners are no longer notified. <em>Note:</em> on the server, object subscription is merely a
|
||||||
* listeners are no longer notified. <em>Note:</em> on the server, object
|
* formality as all objects remain live all the time, so <em>do not</em> depend on event
|
||||||
* subscription is merely a formality as all objects remain live all the
|
* notifications ceasing when a subscriber has relinquished its subscription. Always unregister all
|
||||||
* time, so <em>do not</em> depend on event notifications ceasing when a
|
|
||||||
* subscriber has relinquished its subscription. Always unregister all
|
|
||||||
* listeners when they no longer need to hear from an object.
|
* listeners when they no longer need to hear from an object.
|
||||||
*
|
*
|
||||||
* <p> When there is any change to the the object's fields data, an event
|
* <p> When there is any change to the the object's fields data, an event is generated which is
|
||||||
* is generated which is dispatched to all listeners of the object,
|
* dispatched to all listeners of the object, notifying them of that change and effecting that
|
||||||
* notifying them of that change and effecting that change to the copy of
|
* change to the copy of the object maintained at each client. In this way, both a respository of
|
||||||
* the object maintained at each client. In this way, both a respository
|
* shared information and a mechanism for asynchronous notification are made available as a
|
||||||
* of shared information and a mechanism for asynchronous notification are
|
* fundamental application building blocks.
|
||||||
* made available as a fundamental application building blocks.
|
|
||||||
*
|
*
|
||||||
* <p> To define what information is shared, an application creates a
|
* <p> To define what information is shared, an application creates a distributed object
|
||||||
* distributed object declaration which is much like a class declaration
|
* declaration which is much like a class declaration except that it is transformed into a proper
|
||||||
* except that it is transformed into a proper derived class of
|
* derived class of <code>DObject</code> by a script. A declaration looks something like this:
|
||||||
* <code>DObject</code> by a script. A declaration looks something like
|
|
||||||
* this:
|
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* public dclass RoomObject
|
* public dclass RoomObject
|
||||||
@@ -108,11 +102,10 @@ import com.threerings.presents.Log;
|
|||||||
* }
|
* }
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* These method calls on the actual distributed object will result in the
|
* These method calls on the actual distributed object will result in the proper attribute change
|
||||||
* proper attribute change events being generated and dispatched.
|
* events being generated and dispatched.
|
||||||
*
|
*
|
||||||
* <p> Note that distributed object fields can be any of the following set
|
* <p> Note that distributed object fields can be any of the following set of primitive types:
|
||||||
* of primitive types:
|
|
||||||
*
|
*
|
||||||
* <code><pre>
|
* <code><pre>
|
||||||
* boolean, byte, short, int, long, float, double
|
* boolean, byte, short, int, long, float, double
|
||||||
@@ -136,8 +129,7 @@ public class DObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the object id of this object. All objects in the system
|
* Returns the object id of this object. All objects in the system have a unique object id.
|
||||||
* have a unique object id.
|
|
||||||
*/
|
*/
|
||||||
public int getOid ()
|
public int getOid ()
|
||||||
{
|
{
|
||||||
@@ -145,9 +137,8 @@ public class DObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the dobject manager under the auspices of which this object
|
* Returns the dobject manager under the auspices of which this object operates. This could be
|
||||||
* operates. This could be <code>null</code> if the object is not
|
* <code>null</code> if the object is not active.
|
||||||
* active.
|
|
||||||
*/
|
*/
|
||||||
public DObjectManager getManager ()
|
public DObjectManager getManager ()
|
||||||
{
|
{
|
||||||
@@ -155,11 +146,10 @@ public class DObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Don't call this function! Go through the distributed object manager
|
* Don't call this function! Go through the distributed object manager instead to ensure that
|
||||||
* instead to ensure that everything is done on the proper thread.
|
* everything is done on the proper thread. This function can only safely be called directly
|
||||||
* This function can only safely be called directly when you know you
|
* when you know you are operating on the omgr thread (you are in the middle of a call to
|
||||||
* are operating on the omgr thread (you are in the middle of a call
|
* <code>objectAvailable</code> or to a listener callback).
|
||||||
* to <code>objectAvailable</code> or to a listener callback).
|
|
||||||
*
|
*
|
||||||
* @see DObjectManager#subscribeToObject
|
* @see DObjectManager#subscribeToObject
|
||||||
*/
|
*/
|
||||||
@@ -173,28 +163,26 @@ public class DObject
|
|||||||
_scount++;
|
_scount++;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
Log.warning("Refusing subscriber that's already in the list " +
|
Log.warning("Refusing subscriber that's already in the list [dobj=" + which() +
|
||||||
"[dobj=" + which() + ", subscriber=" + sub + "]");
|
", subscriber=" + sub + "]");
|
||||||
Thread.dumpStack();
|
Thread.dumpStack();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Don't call this function! Go through the distributed object manager
|
* Don't call this function! Go through the distributed object manager instead to ensure that
|
||||||
* instead to ensure that everything is done on the proper thread.
|
* everything is done on the proper thread. This function can only safely be called directly
|
||||||
* This function can only safely be called directly when you know you
|
* when you know you are operating on the omgr thread (you are in the middle of a call to
|
||||||
* are operating on the omgr thread (you are in the middle of a call
|
* <code>objectAvailable</code> or to a listener callback).
|
||||||
* to <code>objectAvailable</code> or to a listener callback).
|
|
||||||
*
|
*
|
||||||
* @see DObjectManager#unsubscribeFromObject
|
* @see DObjectManager#unsubscribeFromObject
|
||||||
*/
|
*/
|
||||||
public void removeSubscriber (Subscriber sub)
|
public void removeSubscriber (Subscriber sub)
|
||||||
{
|
{
|
||||||
if (ListUtil.clearRef(_subs, sub) != null) {
|
if (ListUtil.clearRef(_subs, sub) != null) {
|
||||||
// if we removed something, check to see if we just removed
|
// if we removed something, check to see if we just removed the last subscriber from
|
||||||
// the last subscriber from our list; we also want to be sure
|
// our list; we also want to be sure that we're still active otherwise there's no need
|
||||||
// that we're still active otherwise there's no need to notify
|
// to notify our objmgr because we don't have one
|
||||||
// our objmgr because we don't have one
|
|
||||||
if (--_scount == 0 && _omgr != null) {
|
if (--_scount == 0 && _omgr != null) {
|
||||||
_omgr.removedLastSubscriber(this, _deathWish);
|
_omgr.removedLastSubscriber(this, _deathWish);
|
||||||
}
|
}
|
||||||
@@ -202,8 +190,8 @@ public class DObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instructs this object to request to have a fork stuck in it when
|
* Instructs this object to request to have a fork stuck in it when its last subscriber is
|
||||||
* its last subscriber is removed.
|
* removed.
|
||||||
*/
|
*/
|
||||||
public void setDestroyOnLastSubscriberRemoved (boolean deathWish)
|
public void setDestroyOnLastSubscriberRemoved (boolean deathWish)
|
||||||
{
|
{
|
||||||
@@ -211,19 +199,16 @@ public class DObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds an event listener to this object. The listener will be
|
* Adds an event listener to this object. The listener will be notified when any events are
|
||||||
* notified when any events are dispatched on this object that match
|
* dispatched on this object that match their particular listener interface.
|
||||||
* their particular listener interface.
|
|
||||||
*
|
*
|
||||||
* <p> Note that the entity adding itself as a listener should have
|
* <p> Note that the entity adding itself as a listener should have obtained the object
|
||||||
* obtained the object reference by subscribing to it or should be
|
* reference by subscribing to it or should be acting on behalf of some other entity that
|
||||||
* acting on behalf of some other entity that subscribed to the
|
* subscribed to the object, <em>and</em> that it must be sure to remove itself from the
|
||||||
* object, <em>and</em> that it must be sure to remove itself from the
|
* listener list (via {@link #removeListener}) when it is done because unsubscribing from the
|
||||||
* listener list (via {@link #removeListener}) when it is done because
|
* object (done by whatever entity subscribed in the first place) is not guaranteed to result
|
||||||
* unsubscribing from the object (done by whatever entity subscribed
|
* in the listeners added through that subscription being automatically removed (in most cases,
|
||||||
* in the first place) is not guaranteed to result in the listeners
|
* they definitely will not be removed).
|
||||||
* added through that subscription being automatically removed (in
|
|
||||||
* most cases, they definitely will not be removed).
|
|
||||||
*
|
*
|
||||||
* @param listener the listener to be added.
|
* @param listener the listener to be added.
|
||||||
*
|
*
|
||||||
@@ -239,15 +224,15 @@ public class DObject
|
|||||||
if (els != null) {
|
if (els != null) {
|
||||||
_listeners = els;
|
_listeners = els;
|
||||||
} else {
|
} else {
|
||||||
Log.warning("Refusing repeat listener registration " +
|
Log.warning("Refusing repeat listener registration [dobj=" + which() +
|
||||||
"[dobj=" + which() + ", list=" + listener + "]");
|
", list=" + listener + "]");
|
||||||
Thread.dumpStack();
|
Thread.dumpStack();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes an event listener from this object. The listener will no
|
* Removes an event listener from this object. The listener will no longer be notified when
|
||||||
* longer be notified when events are dispatched on this object.
|
* events are dispatched on this object.
|
||||||
*
|
*
|
||||||
* @param listener the listener to be removed.
|
* @param listener the listener to be removed.
|
||||||
*/
|
*/
|
||||||
@@ -257,11 +242,10 @@ public class DObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides this object with an entity that can be used to validate
|
* Provides this object with an entity that can be used to validate subscription requests and
|
||||||
* subscription requests and events before they are processed. The
|
* events before they are processed. The access controller is handy for ensuring that clients
|
||||||
* access controller is handy for ensuring that clients are behaving
|
* are behaving as expected and for preventing impermissible modifications or event dispatches
|
||||||
* as expected and for preventing impermissible modifications or event
|
* on a distributed object.
|
||||||
* dispatches on a distributed object.
|
|
||||||
*/
|
*/
|
||||||
public void setAccessController (AccessController controller)
|
public void setAccessController (AccessController controller)
|
||||||
{
|
{
|
||||||
@@ -269,8 +253,8 @@ public class DObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a reference to the access controller in use by this object
|
* Returns a reference to the access controller in use by this object or null if none has been
|
||||||
* or null if none has been configured.
|
* configured.
|
||||||
*/
|
*/
|
||||||
public AccessController getAccessController ()
|
public AccessController getAccessController ()
|
||||||
{
|
{
|
||||||
@@ -283,8 +267,7 @@ public class DObject
|
|||||||
public final <T extends DSet.Entry> DSet<T> getSet (String setName)
|
public final <T extends DSet.Entry> DSet<T> getSet (String setName)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@SuppressWarnings("unchecked") DSet<T> casted =
|
@SuppressWarnings("unchecked") DSet<T> casted = (DSet<T>)getField(setName).get(this);
|
||||||
(DSet<T>)getField(setName).get(this);
|
|
||||||
return casted;
|
return casted;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new IllegalArgumentException("No such set: " + setName);
|
throw new IllegalArgumentException("No such set: " + setName);
|
||||||
@@ -316,32 +299,27 @@ public class DObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* At times, an entity on the server may need to ensure that events it
|
* At times, an entity on the server may need to ensure that events it has queued up have made
|
||||||
* has queued up have made it through the event queue and are applied
|
* it through the event queue and are applied to their respective objects before a service may
|
||||||
* to their respective objects before a service may safely be
|
* safely be undertaken again. To make this possible, it can acquire a lock on a distributed
|
||||||
* undertaken again. To make this possible, it can acquire a lock on a
|
* object, generate the events in question and then release the lock (via a call to
|
||||||
* distributed object, generate the events in question and then
|
* <code>releaseLock</code>) which will queue up a final event, the processing of which will
|
||||||
* release the lock (via a call to <code>releaseLock</code>) which
|
* release the lock. Thus the lock will not be released until all of the previously generated
|
||||||
* will queue up a final event, the processing of which will release
|
* events have been processed. If the service is invoked again before that lock is released,
|
||||||
* the lock. Thus the lock will not be released until all of the
|
* the associated call to <code>acquireLock</code> will fail and the code can respond
|
||||||
* previously generated events have been processed. If the service is
|
* accordingly. An object may have any number of outstanding locks as long as they each have a
|
||||||
* invoked again before that lock is released, the associated call to
|
* unique name.
|
||||||
* <code>acquireLock</code> will fail and the code can respond
|
|
||||||
* accordingly. An object may have any number of outstanding locks as
|
|
||||||
* long as they each have a unique name.
|
|
||||||
*
|
*
|
||||||
* @param name the name of the lock to acquire.
|
* @param name the name of the lock to acquire.
|
||||||
*
|
*
|
||||||
* @return true if the lock was acquired, false if the lock was not
|
* @return true if the lock was acquired, false if the lock was not acquired because it has not
|
||||||
* acquired because it has not yet been released from a previous
|
* yet been released from a previous acquisition.
|
||||||
* acquisition.
|
|
||||||
*
|
*
|
||||||
* @see #releaseLock
|
* @see #releaseLock
|
||||||
*/
|
*/
|
||||||
public boolean acquireLock (String name)
|
public boolean acquireLock (String name)
|
||||||
{
|
{
|
||||||
// check for the existence of the lock in the list and add it if
|
// check for the existence of the lock in the list and add it if it's not already there
|
||||||
// it's not already there
|
|
||||||
Object[] list = ListUtil.testAndAdd(_locks, name);
|
Object[] list = ListUtil.testAndAdd(_locks, name);
|
||||||
if (list == null) {
|
if (list == null) {
|
||||||
// a null list means the object was already in the list
|
// a null list means the object was already in the list
|
||||||
@@ -355,8 +333,7 @@ public class DObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queues up an event that when processed will release the lock of the
|
* Queues up an event that when processed will release the lock of the specified name.
|
||||||
* specified name.
|
|
||||||
*
|
*
|
||||||
* @see #acquireLock
|
* @see #acquireLock
|
||||||
*/
|
*/
|
||||||
@@ -367,11 +344,9 @@ public class DObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Don't call this function! It is called by a remove lock event when
|
* Don't call this function! It is called by a remove lock event when that event is processed
|
||||||
* that event is processed and shouldn't be called at any other time.
|
* and shouldn't be called at any other time. If you mean to release a lock that was acquired
|
||||||
* If you mean to release a lock that was acquired with
|
* with <code>acquireLock</code> you should be using <code>releaseLock</code>.
|
||||||
* <code>acquireLock</code> you should be using
|
|
||||||
* <code>releaseLock</code>.
|
|
||||||
*
|
*
|
||||||
* @see #acquireLock
|
* @see #acquireLock
|
||||||
* @see #releaseLock
|
* @see #releaseLock
|
||||||
@@ -381,15 +356,13 @@ public class DObject
|
|||||||
// clear the lock from the list
|
// clear the lock from the list
|
||||||
if (ListUtil.clear(_locks, name) == null) {
|
if (ListUtil.clear(_locks, name) == null) {
|
||||||
// complain if we didn't find the lock
|
// complain if we didn't find the lock
|
||||||
Log.info("Unable to clear non-existent lock [lock=" + name +
|
Log.info("Unable to clear non-existent lock [lock=" + name + ", dobj=" + this + "].");
|
||||||
", dobj=" + this + "].");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests that this distributed object be destroyed. It does so by
|
* Requests that this distributed object be destroyed. It does so by queueing up an object
|
||||||
* queueing up an object destroyed event which the server will
|
* destroyed event which the server will validate and process.
|
||||||
* validate and process.
|
|
||||||
*/
|
*/
|
||||||
public void destroy ()
|
public void destroy ()
|
||||||
{
|
{
|
||||||
@@ -397,17 +370,14 @@ public class DObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks to ensure that the specified subscriber has access to this
|
* Checks to ensure that the specified subscriber has access to this object. This will be
|
||||||
* object. This will be called before satisfying a subscription
|
* called before satisfying a subscription request. If an {@link AccessController} has been
|
||||||
* request. If an {@link AccessController} has been specified for this
|
* specified for this object, it will be used to determine whether or not to allow the
|
||||||
* object, it will be used to determine whether or not to allow the
|
* subscription request. If no controller is set, the subscription will be allowed.
|
||||||
* subscription request. If no controller is set, the subscription
|
|
||||||
* will be allowed.
|
|
||||||
*
|
*
|
||||||
* @param sub the subscriber that will subscribe to this object.
|
* @param sub the subscriber that will subscribe to this object.
|
||||||
*
|
*
|
||||||
* @return true if the subscriber has access to the object, false if
|
* @return true if the subscriber has access to the object, false if they do not.
|
||||||
* they do not.
|
|
||||||
*/
|
*/
|
||||||
public boolean checkPermissions (Subscriber sub)
|
public boolean checkPermissions (Subscriber sub)
|
||||||
{
|
{
|
||||||
@@ -419,16 +389,15 @@ public class DObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks to ensure that this event which is about to be processed,
|
* Checks to ensure that this event which is about to be processed, has the appropriate
|
||||||
* has the appropriate permissions. If an {@link AccessController} has
|
* permissions. If an {@link AccessController} has been specified for this object, it will be
|
||||||
* been specified for this object, it will be used to determine
|
* used to determine whether or not to allow the even dispatch. If no controller is set, all
|
||||||
* whether or not to allow the even dispatch. If no controller is set,
|
* events are allowed.
|
||||||
* all events are allowed.
|
|
||||||
*
|
*
|
||||||
* @param event the event that will be dispatched, object permitting.
|
* @param event the event that will be dispatched, object permitting.
|
||||||
*
|
*
|
||||||
* @return true if the event is valid and should be dispatched, false
|
* @return true if the event is valid and should be dispatched, false if the event fails the
|
||||||
* if the event fails the permissions check and should be aborted.
|
* permissions check and should be aborted.
|
||||||
*/
|
*/
|
||||||
public boolean checkPermissions (DEvent event)
|
public boolean checkPermissions (DEvent event)
|
||||||
{
|
{
|
||||||
@@ -440,9 +409,8 @@ public class DObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called by the distributed object manager after it has applied an
|
* Called by the distributed object manager after it has applied an event to this object. This
|
||||||
* event to this object. This dispatches an event notification to all
|
* dispatches an event notification to all of the listeners registered with this object.
|
||||||
* of the listeners registered with this object.
|
|
||||||
*
|
*
|
||||||
* @param event the event that was just applied.
|
* @param event the event that was just applied.
|
||||||
*/
|
*/
|
||||||
@@ -469,17 +437,16 @@ public class DObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.warning("Listener choked during notification " +
|
Log.warning("Listener choked during notification [list=" + listener +
|
||||||
"[list=" + listener + ", event=" + event + "].");
|
", event=" + event + "].");
|
||||||
Log.logStackTrace(e);
|
Log.logStackTrace(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called by the distributed object manager after it has applied an
|
* Called by the distributed object manager after it has applied an event to this object. This
|
||||||
* event to this object. This dispatches an event notification to all
|
* dispatches an event notification to all of the proxy listeners registered with this object.
|
||||||
* of the proxy listeners registered with this object.
|
|
||||||
*
|
*
|
||||||
* @param event the event that was just applied.
|
* @param event the event that was just applied.
|
||||||
*/
|
*/
|
||||||
@@ -496,18 +463,17 @@ public class DObject
|
|||||||
((ProxySubscriber)sub).eventReceived(event);
|
((ProxySubscriber)sub).eventReceived(event);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.warning("Proxy choked during notification " +
|
Log.warning("Proxy choked during notification [sub=" + sub +
|
||||||
"[sub=" + sub + ", event=" + event + "].");
|
", event=" + event + "].");
|
||||||
Log.logStackTrace(e);
|
Log.logStackTrace(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests that the specified attribute be changed to the specified
|
* Requests that the specified attribute be changed to the specified value. Normally the
|
||||||
* value. Normally the generated setter methods should be used but in
|
* generated setter methods should be used but in rare cases a caller may wish to update
|
||||||
* rare cases a caller may wish to update distributed fields in a
|
* distributed fields in a generic manner.
|
||||||
* generic manner.
|
|
||||||
*/
|
*/
|
||||||
public void changeAttribute (String name, Object value)
|
public void changeAttribute (String name, Object value)
|
||||||
throws ObjectAccessException
|
throws ObjectAccessException
|
||||||
@@ -518,18 +484,16 @@ public class DObject
|
|||||||
f.set(this, value);
|
f.set(this, value);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
String errmsg = "changeAttribute() failure [name=" + name +
|
String errmsg = "changeAttribute() failure [name=" + name + ", value=" + value +
|
||||||
", value=" + value +
|
|
||||||
", vclass=" + value.getClass().getName() + "].";
|
", vclass=" + value.getClass().getName() + "].";
|
||||||
throw new ObjectAccessException(errmsg, e);
|
throw new ObjectAccessException(errmsg, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the named attribute to the specified value. This is only used
|
* Sets the named attribute to the specified value. This is only used by the internals of the
|
||||||
* by the internals of the event dispatch mechanism and should not be
|
* event dispatch mechanism and should not be called directly by users. Use the generated
|
||||||
* called directly by users. Use the generated attribute setter
|
* attribute setter methods instead.
|
||||||
* methods instead.
|
|
||||||
*/
|
*/
|
||||||
public void setAttribute (String name, Object value)
|
public void setAttribute (String name, Object value)
|
||||||
throws ObjectAccessException
|
throws ObjectAccessException
|
||||||
@@ -538,28 +502,24 @@ public class DObject
|
|||||||
getField(name).set(this, value);
|
getField(name).set(this, value);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
String errmsg = "setAttribute() failure [name=" + name +
|
String errmsg = "setAttribute() failure [name=" + name + ", value=" + value +
|
||||||
", value=" + value +
|
|
||||||
", vclass=" + value.getClass().getName() + "].";
|
", vclass=" + value.getClass().getName() + "].";
|
||||||
throw new ObjectAccessException(errmsg, e);
|
throw new ObjectAccessException(errmsg, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Looks up the named attribute and returns a reference to it. This
|
* Looks up the named attribute and returns a reference to it. This should only be used by the
|
||||||
* should only be used by the internals of the event dispatch
|
* internals of the event dispatch mechanism and should not be called directly by users. Use
|
||||||
* mechanism and should not be called directly by users. Use the
|
* the generated attribute getter methods instead.
|
||||||
* generated attribute getter methods instead.
|
|
||||||
*/
|
*/
|
||||||
public Object getAttribute (String name)
|
public Object getAttribute (String name)
|
||||||
throws ObjectAccessException
|
throws ObjectAccessException
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
return getField(name).get(this);
|
return getField(name).get(this);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
String errmsg = "getAttribute() failure [name=" + name + "].";
|
throw new ObjectAccessException("getAttribute() failure [name=" + name + "].", e);
|
||||||
throw new ObjectAccessException(errmsg, e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -572,8 +532,8 @@ public class DObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Posts the specified event either to our dobject manager or to the
|
* Posts the specified event either to our dobject manager or to the compound event for which
|
||||||
* compound event for which we are currently transacting.
|
* we are currently transacting.
|
||||||
*/
|
*/
|
||||||
public void postEvent (DEvent event)
|
public void postEvent (DEvent event)
|
||||||
{
|
{
|
||||||
@@ -584,16 +544,14 @@ public class DObject
|
|||||||
_omgr.postEvent(event);
|
_omgr.postEvent(event);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
Log.warning("Unable to post event, object has no omgr " +
|
Log.info("Dropping event for non- or no longer managed object [oid=" + getOid() +
|
||||||
"[oid=" + getOid() + ", class=" + getClass().getName() +
|
", class=" + getClass().getName() + ", event=" + event + "].");
|
||||||
", event=" + event + "].");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if this object is active and registered with the
|
* Returns true if this object is active and registered with the distributed object system. If
|
||||||
* distributed object system. If an object is created via
|
* an object is created via <code>DObjectManager.createObject</code> it will be active until
|
||||||
* <code>DObjectManager.createObject</code> it will be active until
|
|
||||||
* such time as it is destroyed.
|
* such time as it is destroyed.
|
||||||
*/
|
*/
|
||||||
public final boolean isActive ()
|
public final boolean isActive ()
|
||||||
@@ -602,10 +560,9 @@ public class DObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Don't call this function! It initializes this distributed object
|
* Don't call this function! It initializes this distributed object with the supplied
|
||||||
* with the supplied distributed object manager. This is called by the
|
* distributed object manager. This is called by the distributed object manager when an object
|
||||||
* distributed object manager when an object is created and registered
|
* is created and registered with the system.
|
||||||
* with the system.
|
|
||||||
*
|
*
|
||||||
* @see DObjectManager#createObject
|
* @see DObjectManager#createObject
|
||||||
*/
|
*/
|
||||||
@@ -615,8 +572,8 @@ public class DObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Don't call this function. It is called by the distributed object
|
* Don't call this function. It is called by the distributed object manager when an object is
|
||||||
* manager when an object is created and registered with the system.
|
* created and registered with the system.
|
||||||
*
|
*
|
||||||
* @see DObjectManager#createObject
|
* @see DObjectManager#createObject
|
||||||
*/
|
*/
|
||||||
@@ -668,80 +625,60 @@ public class DObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Begins a transaction on this distributed object. In some
|
* Begins a transaction on this distributed object. In some situations, it is desirable to
|
||||||
* situations, it is desirable to cause multiple changes to
|
* cause multiple changes to distributed object fields in one unified operation. Starting a
|
||||||
* distributed object fields in one unified operation. Starting a
|
* transaction causes all subsequent field modifications to be stored in a single compound
|
||||||
* transaction causes all subsequent field modifications to be stored
|
* event which can then be committed, dispatching and applying all included events in a single
|
||||||
* in a single compound event which can then be committed, dispatching
|
* group. Additionally, the events are dispatched over the network in a single unit which can
|
||||||
* and applying all included events in a single group. Additionally,
|
* significantly enhance network efficiency.
|
||||||
* the events are dispatched over the network in a single unit which
|
|
||||||
* can significantly enhance network efficiency.
|
|
||||||
*
|
*
|
||||||
* <p> When the transaction is complete, the caller must call {@link
|
* <p> When the transaction is complete, the caller must call {@link #commitTransaction} or
|
||||||
* #commitTransaction} or {@link CompoundEvent#commit} to commit the
|
* {@link CompoundEvent#commit} to commit the transaction and release the object back to its
|
||||||
* transaction and release the object back to its normal
|
* normal non-transacting state. If the caller decides not to commit their transaction, they
|
||||||
* non-transacting state. If the caller decides not to commit their
|
* must call {@link #cancelTransaction} or {@link CompoundEvent#cancel} to cancel the
|
||||||
* transaction, they must call {@link #cancelTransaction} or {@link
|
* transaction. Failure to do so will cause the pooch to be totally screwed.
|
||||||
* CompoundEvent#cancel} to cancel the transaction. Failure to do so
|
|
||||||
* will cause the pooch to be totally screwed.
|
|
||||||
*
|
*
|
||||||
* <p> Note: like all other distributed object operations,
|
* <p> Note: like all other distributed object operations, transactions are not thread safe. It
|
||||||
* transactions are not thread safe. It is expected that a single
|
* is expected that a single thread will handle all distributed object operations and that
|
||||||
* thread will handle all distributed object operations and that
|
* thread will begin and complete a transaction before giving up control to unknown code which
|
||||||
* thread will begin and complete a transaction before giving up
|
* might try to operate on the transacting distributed object.
|
||||||
* control to unknown code which might try to operate on the
|
|
||||||
* transacting distributed object.
|
|
||||||
*
|
*
|
||||||
* <p> Note also: if the object is already engaged in a transaction, a
|
* <p> Note also: if the object is already engaged in a transaction, a transaction participant
|
||||||
* transaction participant count will be incremented to note that an
|
* count will be incremented to note that an additional call to {@link #commitTransaction} is
|
||||||
* additional call to {@link #commitTransaction} is required before
|
* required before the transaction should actually be committed. Thus <em>every</em> call to
|
||||||
* the transaction should actually be committed. Thus <em>every</em>
|
* {@link #startTransaction} must be accompanied by a call to either {@link #commitTransaction}
|
||||||
* call to {@link #startTransaction} must be accompanied by a call to
|
* or {@link #cancelTransaction}. Additionally, if any transaction participant cancels the
|
||||||
* either {@link #commitTransaction} or {@link
|
* transaction, the entire transaction is cancelled for all participants, regardless of whether
|
||||||
* #cancelTransaction}. Additionally, if any transaction participant
|
* the other participants attempted to commit the transaction.
|
||||||
* cancels the transaction, the entire transaction is cancelled for
|
|
||||||
* all participants, regardless of whether the other participants
|
|
||||||
* attempted to commit the transaction.
|
|
||||||
*/
|
*/
|
||||||
public void startTransaction ()
|
public void startTransaction ()
|
||||||
{
|
{
|
||||||
// sanity check
|
if (_tevent != null) {
|
||||||
if (!isActive()) {
|
|
||||||
String errmsg = "Refusing to start transaction on inactive " +
|
|
||||||
"object [dobj=" + this + "]";
|
|
||||||
throw new IllegalArgumentException(errmsg);
|
|
||||||
|
|
||||||
} else if (_tevent != null) {
|
|
||||||
_tcount++;
|
_tcount++;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
_tevent = new CompoundEvent(this, _omgr);
|
_tevent = new CompoundEvent(this, _omgr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Commits the transaction in which this distributed object is
|
* Commits the transaction in which this distributed object is involved.
|
||||||
* involved.
|
|
||||||
*
|
*
|
||||||
* @see CompoundEvent#commit
|
* @see CompoundEvent#commit
|
||||||
*/
|
*/
|
||||||
public void commitTransaction ()
|
public void commitTransaction ()
|
||||||
{
|
{
|
||||||
if (_tevent == null) {
|
if (_tevent == null) {
|
||||||
String errmsg = "Cannot commit: not involved in a transaction " +
|
String errmsg = "Cannot commit: not involved in a transaction [dobj=" + this + "]";
|
||||||
"[dobj=" + this + "]";
|
|
||||||
throw new IllegalStateException(errmsg);
|
throw new IllegalStateException(errmsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we are nested, we decrement our nesting count rather than
|
// if we are nested, we decrement our nesting count rather than committing the transaction
|
||||||
// committing the transaction
|
|
||||||
if (_tcount > 0) {
|
if (_tcount > 0) {
|
||||||
_tcount--;
|
_tcount--;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// we may actually be doing our final commit after someone
|
// we may actually be doing our final commit after someone already cancelled this
|
||||||
// already cancelled this transaction, so we need to perform
|
// transaction, so we need to perform the appropriate action at this point
|
||||||
// the appropriate action at this point
|
|
||||||
if (_tcancelled) {
|
if (_tcancelled) {
|
||||||
_tevent.cancel();
|
_tevent.cancel();
|
||||||
} else {
|
} else {
|
||||||
@@ -751,8 +688,7 @@ public class DObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if this object is in the middle of a transaction or
|
* Returns true if this object is in the middle of a transaction or false if it is not.
|
||||||
* false if it is not.
|
|
||||||
*/
|
*/
|
||||||
public boolean inTransaction ()
|
public boolean inTransaction ()
|
||||||
{
|
{
|
||||||
@@ -760,21 +696,19 @@ public class DObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cancels the transaction in which this distributed object is
|
* Cancels the transaction in which this distributed object is involved.
|
||||||
* involved.
|
|
||||||
*
|
*
|
||||||
* @see CompoundEvent#cancel
|
* @see CompoundEvent#cancel
|
||||||
*/
|
*/
|
||||||
public void cancelTransaction ()
|
public void cancelTransaction ()
|
||||||
{
|
{
|
||||||
if (_tevent == null) {
|
if (_tevent == null) {
|
||||||
String errmsg = "Cannot cancel: not involved in a transaction " +
|
String errmsg = "Cannot cancel: not involved in a transaction [dobj=" + this + "]";
|
||||||
"[dobj=" + this + "]";
|
|
||||||
throw new IllegalStateException(errmsg);
|
throw new IllegalStateException(errmsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we're in a nested transaction, make a note that it is to be
|
// if we're in a nested transaction, make a note that it is to be cancelled when all
|
||||||
// cancelled when all parties commit and decrement the nest count
|
// parties commit and decrement the nest count
|
||||||
if (_tcount > 0) {
|
if (_tcount > 0) {
|
||||||
_tcancelled = true;
|
_tcancelled = true;
|
||||||
_tcount--;
|
_tcount--;
|
||||||
@@ -785,15 +719,13 @@ public class DObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes this object from participation in any transaction in which
|
* Removes this object from participation in any transaction in which it might be taking part.
|
||||||
* it might be taking part.
|
|
||||||
*/
|
*/
|
||||||
protected void clearTransaction ()
|
protected void clearTransaction ()
|
||||||
{
|
{
|
||||||
// sanity check
|
// sanity check
|
||||||
if (_tcount != 0) {
|
if (_tcount != 0) {
|
||||||
Log.warning("Transaction cleared with non-zero nesting count " +
|
Log.warning("Transaction cleared with non-zero nesting count [dobj=" + this + "].");
|
||||||
"[dobj=" + this + "].");
|
|
||||||
_tcount = 0;
|
_tcount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -803,22 +735,18 @@ public class DObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called by derived instances when an attribute setter method was
|
* Called by derived instances when an attribute setter method was called.
|
||||||
* called.
|
|
||||||
*/
|
*/
|
||||||
protected void requestAttributeChange (
|
protected void requestAttributeChange (String name, Object value, Object oldValue)
|
||||||
String name, Object value, Object oldValue)
|
|
||||||
{
|
{
|
||||||
// dispatch an attribute changed event
|
// dispatch an attribute changed event
|
||||||
postEvent(new AttributeChangedEvent(_oid, name, value, oldValue));
|
postEvent(new AttributeChangedEvent(_oid, name, value, oldValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called by derived instances when an element updater method was
|
* Called by derived instances when an element updater method was called.
|
||||||
* called.
|
|
||||||
*/
|
*/
|
||||||
protected void requestElementUpdate (
|
protected void requestElementUpdate (String name, int index, Object value, Object oldValue)
|
||||||
String name, int index, Object value, Object oldValue)
|
|
||||||
{
|
{
|
||||||
// dispatch an attribute changed event
|
// dispatch an attribute changed event
|
||||||
postEvent(new ElementUpdatedEvent(
|
postEvent(new ElementUpdatedEvent(
|
||||||
@@ -846,8 +774,7 @@ public class DObject
|
|||||||
/**
|
/**
|
||||||
* Calls by derived instances when a set adder method was called.
|
* Calls by derived instances when a set adder method was called.
|
||||||
*/
|
*/
|
||||||
protected <T extends DSet.Entry> void requestEntryAdd (
|
protected <T extends DSet.Entry> void requestEntryAdd (String name, DSet<T> set, T entry)
|
||||||
String name, DSet<T> set, T entry)
|
|
||||||
{
|
{
|
||||||
// if we're on the authoritative server, we update the set immediately
|
// if we're on the authoritative server, we update the set immediately
|
||||||
boolean alreadyApplied = false;
|
boolean alreadyApplied = false;
|
||||||
@@ -882,8 +809,7 @@ public class DObject
|
|||||||
/**
|
/**
|
||||||
* Calls by derived instances when a set updater method was called.
|
* Calls by derived instances when a set updater method was called.
|
||||||
*/
|
*/
|
||||||
protected <T extends DSet.Entry> void requestEntryUpdate (
|
protected <T extends DSet.Entry> void requestEntryUpdate (String name, DSet<T> set, T entry)
|
||||||
String name, DSet<T> set, T entry)
|
|
||||||
{
|
{
|
||||||
// if we're on the authoritative server, we update the set immediately
|
// if we're on the authoritative server, we update the set immediately
|
||||||
T oldEntry = null;
|
T oldEntry = null;
|
||||||
@@ -898,8 +824,7 @@ public class DObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the {@link Field} with the specified name or null if there
|
* Returns the {@link Field} with the specified name or null if there is none such.
|
||||||
* is none such.
|
|
||||||
*/
|
*/
|
||||||
protected final Field getField (String name)
|
protected final Field getField (String name)
|
||||||
{
|
{
|
||||||
@@ -928,8 +853,7 @@ public class DObject
|
|||||||
/** A reference to our object manager. */
|
/** A reference to our object manager. */
|
||||||
protected transient DObjectManager _omgr;
|
protected transient DObjectManager _omgr;
|
||||||
|
|
||||||
/** The entity that tells us if an event or subscription request
|
/** The entity that tells us if an event or subscription request should be allowed. */
|
||||||
* should be allowed. */
|
|
||||||
protected transient AccessController _controller;
|
protected transient AccessController _controller;
|
||||||
|
|
||||||
/** A list of outstanding locks. */
|
/** A list of outstanding locks. */
|
||||||
@@ -944,8 +868,7 @@ public class DObject
|
|||||||
/** Our subscriber count. */
|
/** Our subscriber count. */
|
||||||
protected transient int _scount;
|
protected transient int _scount;
|
||||||
|
|
||||||
/** The compound event associated with our transaction, if we're
|
/** The compound event associated with our transaction, if we're currently in a transaction. */
|
||||||
* currently in a transaction. */
|
|
||||||
protected transient CompoundEvent _tevent;
|
protected transient CompoundEvent _tevent;
|
||||||
|
|
||||||
/** The nesting depth of our current transaction. */
|
/** The nesting depth of our current transaction. */
|
||||||
@@ -954,18 +877,14 @@ public class DObject
|
|||||||
/** Whether or not our nested transaction has been cancelled. */
|
/** Whether or not our nested transaction has been cancelled. */
|
||||||
protected transient boolean _tcancelled;
|
protected transient boolean _tcancelled;
|
||||||
|
|
||||||
/** Indicates whether we want to be destroyed when our last subscriber
|
/** Indicates whether we want to be destroyed when our last subscriber is removed. */
|
||||||
* is removed. */
|
|
||||||
protected transient boolean _deathWish = false;
|
protected transient boolean _deathWish = false;
|
||||||
|
|
||||||
/** Maintains a mapping of sorted field arrays for each distributed
|
/** Maintains a mapping of sorted field arrays for each distributed object class. */
|
||||||
* object class. */
|
protected static HashMap<Class,Field[]> _ftable = new HashMap<Class,Field[]>();
|
||||||
protected static HashMap<Class,Field[]> _ftable =
|
|
||||||
new HashMap<Class,Field[]>();
|
|
||||||
|
|
||||||
/** Used to sort and search {@link #_fields}. */
|
/** Used to sort and search {@link #_fields}. */
|
||||||
protected static final Comparator<Field> FIELD_COMP =
|
protected static final Comparator<Field> FIELD_COMP = new Comparator<Field>() {
|
||||||
new Comparator<Field>() {
|
|
||||||
public int compare (Field f1, Field f2) {
|
public int compare (Field f1, Field f2) {
|
||||||
return f1.getName().compareTo(f2.getName());
|
return f1.getName().compareTo(f2.getName());
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user