Added a way to package up an action and data and send it to all appropriate

servers for execution. The class in question must exist on the remote server
but all of our servers are all always running the same code, so this follows.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@4882 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2007-11-28 00:25:23 +00:00
parent 195f78376b
commit 9e835ff6e9
5 changed files with 137 additions and 7 deletions
@@ -36,4 +36,9 @@ public interface PeerService extends InvocationService
* lock.
*/
public void ratifyLockAction (Client client, Lock lock, boolean acquire);
/**
* Requests that the specified action be invoked on this server.
*/
public void invokeAction (Client client, byte[] serializedAction);
}
@@ -37,8 +37,19 @@ import com.threerings.presents.peer.data.NodeObject;
public class PeerMarshaller extends InvocationMarshaller
implements PeerService
{
/** The method id used to dispatch {@link #invokeAction} requests. */
public static final int INVOKE_ACTION = 1;
// from interface PeerService
public void invokeAction (Client arg1, byte[] arg2)
{
sendRequest(arg1, INVOKE_ACTION, new Object[] {
arg2
});
}
/** The method id used to dispatch {@link #ratifyLockAction} requests. */
public static final int RATIFY_LOCK_ACTION = 1;
public static final int RATIFY_LOCK_ACTION = 2;
// from interface PeerService
public void ratifyLockAction (Client arg1, NodeObject.Lock arg2, boolean arg3)
@@ -44,18 +44,26 @@ public class PeerDispatcher extends InvocationDispatcher
this.provider = provider;
}
// from InvocationDispatcher
@Override // documentation inherited
public InvocationMarshaller createMarshaller ()
{
return new PeerMarshaller();
}
@SuppressWarnings("unchecked") // from InvocationDispatcher
@SuppressWarnings("unchecked")
@Override // documentation inherited
public void dispatchRequest (
ClientObject source, int methodId, Object[] args)
throws InvocationException
{
switch (methodId) {
case PeerMarshaller.INVOKE_ACTION:
((PeerProvider)provider).invokeAction(
source,
(byte[])args[0]
);
return;
case PeerMarshaller.RATIFY_LOCK_ACTION:
((PeerProvider)provider).ratifyLockAction(
source,
@@ -21,6 +21,12 @@
package com.threerings.presents.peer.server;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
@@ -37,6 +43,7 @@ import com.samskivert.util.ObjectUtil;
import com.samskivert.util.ObserverList;
import com.samskivert.util.ResultListener;
import com.samskivert.util.ResultListenerList;
import com.samskivert.util.StringUtil;
import com.samskivert.util.Tuple;
import com.threerings.io.Streamable;
@@ -134,6 +141,43 @@ public class PeerManager
public void fail (String peerName);
}
/**
* Encapsulates code that is meant to be executed one or more servers.
*
* <p><b>Note well</b>: the action you provide is serialized and sent to the server to which
* the member is currently connection. This means you MUST NOT instantiate a NodeAction
* anonymously and make reference to other classes because those implicit references will cause
* the referenced classes to be included in the anonymous inner class's serialization closure.
* Instead use the provided varargs constructor to pass along any information you need which
* will be serialized and sent to the destination server. This means that said arguments must
* of course be {@link Serializable}.
*/
public static abstract class NodeAction implements Serializable
{
public NodeAction (Serializable ... arguments) {
_arguments = arguments;
}
/** Returns true if this action should be executed on the specified node. This will be
* called on the originating server to decide whether or not to deliver the action to the
* server in question. */
public abstract boolean isApplicable (NodeObject nodeobj);
/** Invokes the action on the target server. */
public void invoke () {
try {
execute(_arguments);
} catch (Throwable t) {
log.log(Level.WARNING, getClass().getName() + " failed " +
StringUtil.safeToString(_arguments) + ".");
}
}
protected abstract void execute (Object[] arguments);
protected Object[] _arguments;
}
/**
* Returns the distributed object that represents this node to its peers.
*/
@@ -212,6 +256,15 @@ public class PeerManager
didInit();
}
/**
* Returns true if the supplied peer credentials match our shared secret.
*/
public boolean isAuthenticPeer (PeerCreds creds)
{
return PeerCreds.createPassword(creds.getNodeName(), _sharedSecret).equals(
creds.getPassword());
}
/**
* Locates the client with the specified name. Returns null if the client is not logged onto
* any peer.
@@ -278,12 +331,45 @@ public class PeerManager
}
/**
* Returns true if the supplied peer credentials match our shared secret.
* Invokes the supplied action on this and any other server that it indicates is appropriate.
* The action will be executed on the distributed object thread, but this method does not need
* to be called from the distributed object thread.
*/
public boolean isAuthenticPeer (PeerCreds creds)
public void invokeNodeAction (final NodeAction action)
{
return PeerCreds.createPassword(creds.getNodeName(), _sharedSecret).equals(
creds.getPassword());
// if we're not on the dobjmgr thread, get there
if (!PresentsServer.omgr.isDispatchThread()) {
PresentsServer.omgr.postRunnable(new Runnable() {
public void run () {
invokeNodeAction(action);
}
});
return;
}
// first serialize the action to make sure we can
byte[] actionBytes;
try {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream oout = new ObjectOutputStream(bout);
oout.writeObject(action);
actionBytes = bout.toByteArray();
} catch (Exception e) {
log.log(Level.WARNING, "Failed to serialize node action [action=" + action + "].", e);
return;
}
// invoke the action on our local server if appropriate
if (action.isApplicable(_nodeobj)) {
action.invoke();
}
// now send it to any remote node that is also appropriate
for (PeerNode peer : _peers.values()) {
if (peer.nodeobj != null && action.isApplicable(peer.nodeobj)) {
peer.nodeobj.peerService.invokeAction(peer.getClient(), actionBytes);
}
}
}
/**
@@ -658,6 +744,21 @@ public class PeerManager
}
}
// from interface PeerProvider
public void invokeAction (ClientObject caller, byte[] serializedAction)
{
NodeAction action = null;
try {
ObjectInputStream oin =
new ObjectInputStream(new ByteArrayInputStream(serializedAction));
action = (NodeAction)oin.readObject();
action.invoke();
} catch (Exception e) {
log.log(Level.WARNING, "Failed to execute node action [from=" + caller.who() +
", action=" + action + ", serializedSize=" + serializedAction.length + "].");
}
}
// from interface ClientManager.ClientObserver
public void clientSessionDidStart (PresentsClient client)
{
@@ -33,6 +33,11 @@ import com.threerings.presents.server.InvocationProvider;
*/
public interface PeerProvider extends InvocationProvider
{
/**
* Handles a {@link PeerService#invokeAction} request.
*/
public void invokeAction (ClientObject caller, byte[] arg1);
/**
* Handles a {@link PeerService#ratifyLockAction} request.
*/