diff options
author | eea1 <eea1@ae88bc3d-4319-0410-8dbf-d08b4c9d3795> | 1999-08-24 23:12:46 +0000 |
---|---|---|
committer | eea1 <eea1@ae88bc3d-4319-0410-8dbf-d08b4c9d3795> | 1999-08-24 23:12:46 +0000 |
commit | b63497639790e8470a84e5f622f1ba6ce09d5c48 (patch) | |
tree | 149fc9d2627fda46d537166a749076cffbc4f7c1 /java | |
parent | 2277d3e8270d04092497d70fa24250bae88211a4 (diff) | |
download | ATCD-b63497639790e8470a84e5f622f1ba6ce09d5c48.tar.gz |
Updated source files for netsvcs/Token.
Diffstat (limited to 'java')
-rw-r--r-- | java/JACE/netsvcs/Token/LockHandler.java | 38 | ||||
-rw-r--r-- | java/JACE/netsvcs/Token/LockHandlerAdapter.java | 380 | ||||
-rw-r--r-- | java/JACE/netsvcs/Token/LockOperations.java | 16 | ||||
-rw-r--r-- | java/JACE/netsvcs/Token/LockTypes.java | 19 | ||||
-rw-r--r-- | java/JACE/netsvcs/Token/MutexHandler.java | 51 | ||||
-rw-r--r-- | java/JACE/netsvcs/Token/RWMutexHandler.java | 54 | ||||
-rw-r--r-- | java/JACE/netsvcs/Token/RemoteLock.java | 543 | ||||
-rw-r--r-- | java/JACE/netsvcs/Token/RemoteMutex.java | 28 | ||||
-rw-r--r-- | java/JACE/netsvcs/Token/RemoteRWMutex.java | 29 | ||||
-rw-r--r-- | java/JACE/netsvcs/Token/TokenAcceptor.java | 353 | ||||
-rw-r--r-- | java/JACE/netsvcs/Token/TokenReply.java | 171 | ||||
-rw-r--r-- | java/JACE/netsvcs/Token/TokenRequest.java | 426 | ||||
-rw-r--r-- | java/JACE/netsvcs/Token/TokenRequestHandler.java | 180 | ||||
-rw-r--r-- | java/JACE/netsvcs/Token/package.html | 16 |
14 files changed, 2304 insertions, 0 deletions
diff --git a/java/JACE/netsvcs/Token/LockHandler.java b/java/JACE/netsvcs/Token/LockHandler.java new file mode 100644 index 00000000000..8e3612efb42 --- /dev/null +++ b/java/JACE/netsvcs/Token/LockHandler.java @@ -0,0 +1,38 @@ +package JACE.netsvcs.Token; + +/** + * Defines a handler for a certain type of lock. This allows new types + * of synchronization mechanisms to be added to the Token service without + * any modification of existing code. Implementing class instances that + * are registered (via the command line or another way) + * with the token service can be created as requests for that type of + * lock come into the service. + * + *@see LockHandlerAdapter + *@see MutexHandler + *@author Everett Anderson + */ +public interface LockHandler +{ + /** + * Process a given TokenRequest and construct the appropriate + * reply. The request has already been read from the connection, + * and the reply will be sent without the LockHandler having to + * worry about the details. + * + *@param caller TokenRequestHandler which is accessing this LockHandler + *@param request request read from the connection + *@return appropriate TokenReply (success, failure, etc) + */ + TokenReply handleRequest(TokenRequestHandler caller, + TokenRequest request); + + /** + * Release any claim the client represented with the given ID + * has on this handler's lock. This is used when a client + * disconnects. + * + *@param clientID ID of the client whose claims to abandon + */ + void abandonLock (String clientID); +} diff --git a/java/JACE/netsvcs/Token/LockHandlerAdapter.java b/java/JACE/netsvcs/Token/LockHandlerAdapter.java new file mode 100644 index 00000000000..e240000161c --- /dev/null +++ b/java/JACE/netsvcs/Token/LockHandlerAdapter.java @@ -0,0 +1,380 @@ +package JACE.netsvcs.Token; + +import java.util.*; +import JACE.ASX.*; +import JACE.OS.*; +import JACE.Concurrency.*; + +/** + * LockHandler implementation for any AbstractLock. + * <P> + * Provides the dispatching to appropriate methods on an AbstractLock + * as requests come in. + */ +public class LockHandlerAdapter implements LockHandler +{ + /** + * Constructor taking an AbstractLock to use as the locking + * mechanism the requests work on. + */ + public LockHandlerAdapter (AbstractLock lock) + { + lock_ = lock; + } + + /** + * Default constructor. + */ + public LockHandlerAdapter () + { + lock_ = null; + } + + /** + * Dispatch the request according to its type, calling the + * appropriate methods on the AbstractLock member. + * + *@param caller TokenRequestHandler which called handleRequest (unused) + *@param request request to process + *@return appropriate reply to send to the client + */ + public TokenReply handleRequest (TokenRequestHandler caller, + TokenRequest request) + { + String client = request.clientID (); + String token = request.tokenName (); + TokenReply result = null; + + // Dispatch according to operation type + switch (request.operationType ()) + { + case LockOperations.ACQUIRE: + ACE.DEBUG (client + " begins ACQUIRE for " + token); + result = acquireDispatcher (request); + break; + case LockOperations.RELEASE: + ACE.DEBUG (client + " begins RELEASE for " + token); + result = release (request); + break; + case LockOperations.RENEW: + ACE.DEBUG (client + " begins RENEW for " + token); + result = renew (request); + break; + case LockOperations.REMOVE: + ACE.DEBUG (client + " begins REMOVE for " + token); + result = remove (request); + break; + case LockOperations.TRY_ACQUIRE: + ACE.DEBUG (client + " begins TRY_ACQUIRE for " + token); + result = tryAcquireDispatcher (request); + break; + default: + ACE.ERROR ("Unknown operation: " + request.operationType ()); + break; + } + + ACE.DEBUG (client + " result: " + result); + + return result; + } + + /** + * Create a TimeValue from the given request's timeout information. Note + * that the time in the request is an absolute time timeout. + * + *@param request request to obtain the timeout info from + *@return null if useTimeout is false, otherwise a TimeValue + * representing the appropriate time period + */ + protected TimeValue getTimeout (TokenRequest request) + { + if (request.useTimeout ()) + return new TimeValue (request.sec (), + request.usec () * 1000); + else + return null; + } + + /** + * Call acquireWrite on the lock, returning its return value. + * + *@see AbstractLock#acquireWrite + *@return value from the lock's operation + */ + protected int acquireWrite (TokenRequest request, TimeValue timeout) + throws LockException, TimeoutException, InterruptedException + { + int result; + + if (timeout != null) + result = lock_.acquireWrite (timeout); + else + result = lock_.acquireWrite (); + + return result; + } + + /** + * Call acquireRead on the lock, returning its return value. + * + *@see AbstractLock#acquireRead + *@return value from the lock's operation + */ + protected int acquireRead (TokenRequest request, TimeValue timeout) + throws LockException, TimeoutException, InterruptedException + { + int result; + + if (timeout != null) + result = lock_.acquireRead (timeout); + else + result = lock_.acquireRead (); + + return result; + } + + /** + * Call acquire on the lock, returning its return value. + * + *@see AbstractLock#acquire + *@return value from the lock's operation + */ + protected int acquire (TokenRequest request, TimeValue timeout) + throws LockException, TimeoutException, InterruptedException + { + int result; + + if (timeout != null) + result = lock_.acquire (timeout); + else + result = lock_.acquire (); + + return result; + } + + /** + * Dispatch to the appropriate acquire method. In C++ ACE, when + * the type is LockTypes.RWLOCK and the proxy type is + * LockTypes.WRITE_LOCK_PROXY, then this calls acquireWrite. + * If it's RWLOCK and the proxy is READ_LOCK_PROXY, it calls + * acquireRead. In the normal case, it just calls acquire. + * + *@return reply to be sent back to the client (values for errno + * include constants in TokenReply such as EFAULT, ETIME, + * EINTR, or NO_ERRORS) + */ + protected TokenReply acquireDispatcher (TokenRequest request) + { + int result; + TimeValue timeout = getTimeout (request); + + try { + + /* + ACE specifies that when requesting a reader lock, the + token type will be RWLOCK and the proxy type is 0. + When it's a writer lock, the proxy type is 1. + */ + if (request.tokenType () == LockTypes.RWLOCK) { + if (request.proxyType () == LockTypes.READ_LOCK_PROXY) + result = acquireRead (request, timeout); + else + result = acquireWrite (request, timeout); + } else + result = acquire (request, timeout); + + } catch (LockException e) { + return new TokenReply (TokenReply.EFAULT, + request.arg ()); + } catch (TimeoutException e) { + return new TokenReply (TokenReply.ETIME, + request.arg ()); + } catch (InterruptedException e) { + return new TokenReply (TokenReply.EINTR, + request.arg ()); + } + + if (result == AbstractLock.FAILURE) { + return new TokenReply (TokenReply.EFAULT, + request.arg ()); + } else { + return new TokenReply (TokenReply.NO_ERRORS, + request.arg ()); + } + } + + /** + * Process a release request and construct a reply. The values + * for errno include TokenReply constants EFAULT, EACCES, or + * NO_ERRORS. + */ + protected TokenReply release (TokenRequest request) + { + int result; + + try { + result = lock_.release (); + } catch (LockException e) { + return new TokenReply (TokenReply.EFAULT, + request.arg ()); + } + + if (result == AbstractLock.FAILURE) { + return new TokenReply (TokenReply.EACCES, + request.arg ()); + } else { + return new TokenReply (TokenReply.NO_ERRORS, + request.arg ()); + } + } + + /** + * Process a renew request and construct a reply. The values for + * errno include TokenReply constants EFAULT, ETIME, EINTR, EACCES, + * or NO_ERRORS. + */ + protected TokenReply renew (TokenRequest request) + { + int result = AbstractLock.FAILURE; + TimeValue timeout = getTimeout (request); + + try { + + if (timeout != null) { + result = lock_.renew (request.requeuePosition (), + timeout); + } else { + result = lock_.renew (request.requeuePosition ()); + } + + } catch (LockException e) { + return new TokenReply (TokenReply.EFAULT, + request.arg ()); + } catch (TimeoutException e) { + return new TokenReply (TokenReply.ETIME, + request.arg ()); + } catch (InterruptedException e) { + return new TokenReply (TokenReply.EINTR, + request.arg ()); + } + + if (result == AbstractLock.FAILURE) { + return new TokenReply (TokenReply.EACCES, + request.arg ()); + } else { + return new TokenReply (TokenReply.NO_ERRORS, + request.arg ()); + } + } + + /** + * Process a remove request and construct a reply. This currently + * is not supported in the normal AbstractLock interface, so the + * default implementation returns a reply with errno set to + * TokenReply.ENOTSUP. + */ + protected TokenReply remove (TokenRequest request) + { + ACE.ERROR ("Remove is unimplemented"); + return new TokenReply (TokenReply.ENOTSUP, + request.arg ()); + } + + /** + * Call tryAcquireWrite on the lock, returning the result. + */ + protected int tryAcquireWrite (TokenRequest request) + throws LockException + { + return lock_.tryAcquireWrite (); + } + + /** + * Call tryAcquireRead on the lock, returning the result. + */ + protected int tryAcquireRead (TokenRequest request) + throws LockException + { + return lock_.tryAcquireRead (); + } + + /** + * Call tryAcquire on the lock, returning the result. + */ + protected int tryAcquire (TokenRequest request) throws LockException + { + return lock_.tryAcquire (); + } + + /** + * Dispatch to the appropriate tryAcquire method. In C++ ACE, when + * the type is LockTypes.RWLOCK and the proxy type is + * LockTypes.WRITE_LOCK_PROXY, then this calls acquireWrite. + * If it's RWLOCK and the proxy is READ_LOCK_PROXY, it calls + * acquireRead. In the normal case, it just calls acquire. + * + *@return reply to be sent back to the client (values for errno + * include constants in TokenReply such as EFAULT, + * EWOULDBLOCK, or NO_ERRORS). + */ + protected TokenReply tryAcquireDispatcher (TokenRequest request) + { + int result; + + try { + + /* + ACE specifies that when requesting a reader lock, the + token type will be RWLOCK and the proxy type is 0. + When it's a writer lock, the proxy type is 1. + */ + if (request.tokenType () == LockTypes.RWLOCK) { + if (request.proxyType () == LockTypes.READ_LOCK_PROXY) + result = tryAcquireRead (request); + else + result = tryAcquireWrite (request); + } else + result = tryAcquire (request); + + } catch (LockException e) { + return new TokenReply (TokenReply.EFAULT, + request.arg ()); + } + + if (result == AbstractLock.FAILURE) { + return new TokenReply (TokenReply.EWOULDBLOCK, + request.arg ()); + } else { + return new TokenReply (TokenReply.NO_ERRORS, + request.arg ()); + } + } + + /** + * Abandon any claim the specified client has on the lock. + * + *@param clientID identification of the client + */ + public void abandonLock (String clientID) + { + ACE.DEBUG (clientID + " abandoning lock"); + try { + int nesting_level = 0; + while (lock_.release () != AbstractLock.FAILURE) + { + nesting_level++; + // Loop until not the owner in case the lock + // supports nested acquires + } + if (nesting_level == 0) + ACE.DEBUG (clientID + " was not the owner"); + else + ACE.DEBUG (clientID + " had " + nesting_level + " locks"); + } catch (LockException e) { + ACE.ERROR ("While abandoning lock: " + e.getMessage ()); + // Don't need to send a reply to the client + } + } + + protected AbstractLock lock_; +} diff --git a/java/JACE/netsvcs/Token/LockOperations.java b/java/JACE/netsvcs/Token/LockOperations.java new file mode 100644 index 00000000000..f5dfa5bc486 --- /dev/null +++ b/java/JACE/netsvcs/Token/LockOperations.java @@ -0,0 +1,16 @@ +package JACE.netsvcs.Token; + +/** + * Constants defining the operation types available on a lock. + * For information on specifying a read/write style lock, see LockTypes. + * + *@see LockTypes + */ +public interface LockOperations +{ + int ACQUIRE = 0; + int RELEASE = 1; + int RENEW = 2; + int REMOVE = 3; + int TRY_ACQUIRE = 4; +} diff --git a/java/JACE/netsvcs/Token/LockTypes.java b/java/JACE/netsvcs/Token/LockTypes.java new file mode 100644 index 00000000000..f377529367a --- /dev/null +++ b/java/JACE/netsvcs/Token/LockTypes.java @@ -0,0 +1,19 @@ +package JACE.netsvcs.Token; + +/** + * Constants related to the default lock and proxy types. New types + * of LockHandlers and lock types can be created and registered with + * the token service on the command line without modifying this. + * <P> + * C++ ACE handles RWLOCK in this way: + * When a request comes through for a RWLOCK, the proxy type is + * 0 for a read lock request and 1 for a write lock request. + */ +public interface LockTypes +{ + int MUTEX = 0; + int RWLOCK = 1; + + int READ_LOCK_PROXY = 0; + int WRITE_LOCK_PROXY = 1; +} diff --git a/java/JACE/netsvcs/Token/MutexHandler.java b/java/JACE/netsvcs/Token/MutexHandler.java new file mode 100644 index 00000000000..82f79fe5a76 --- /dev/null +++ b/java/JACE/netsvcs/Token/MutexHandler.java @@ -0,0 +1,51 @@ +package JACE.netsvcs.Token; + +import JACE.Concurrency.*; + +/** + * LockHandler implementation for a mutex lock. + * <P> + * Currently, this uses JACE.Concurrency.Token as the actual lock since + * it supports nested acquires. + * + *@see LockHandler + */ +public class MutexHandler extends LockHandlerAdapter +{ + // Uses token since it supports nested acquires. + static class ExtendedMutex extends Token + { + // This is so that we don't make any assumptions about previous + // implementations of LockAdapter, and enable owner checking with + // the client ID from TokenRequest. The thread name is set in + // handleRequest. + protected Object accessorID () + { + return Thread.currentThread().getName(); + } + } + + /** + * Default constructor. + */ + public MutexHandler () + { + super (new ExtendedMutex ()); + } + + public TokenReply handleRequest (TokenRequestHandler caller, + TokenRequest request) + { + Thread.currentThread().setName (request.clientID ()); + + return super.handleRequest (caller, request); + } + + public void abandonLock (String clientID) + { + Thread.currentThread().setName (clientID); + + super.abandonLock (clientID); + } +} + diff --git a/java/JACE/netsvcs/Token/RWMutexHandler.java b/java/JACE/netsvcs/Token/RWMutexHandler.java new file mode 100644 index 00000000000..89dc679dd8d --- /dev/null +++ b/java/JACE/netsvcs/Token/RWMutexHandler.java @@ -0,0 +1,54 @@ +package JACE.netsvcs.Token; + +import JACE.Concurrency.*; + +/** + * LockHandler implementation for a reader/writer mutex lock. + * <P> + * Since it uses RWMutex as the actual lock, it doesn't support + * nested acquires. + * + *@see LockHandler + */ +public class RWMutexHandler extends LockHandlerAdapter +{ + static class ExtendedRWMutex extends RWMutex + { + // This is so that we don't make any assumptions about previous + // implementations of LockAdapter, and enable owner checking with + // the client ID from TokenRequest. The thread name is set in + // handleRequest. + protected Object accessorID () + { + return Thread.currentThread().getName(); + } + } + + /** + * Default constructor. + */ + public RWMutexHandler () + { + super (new ExtendedRWMutex ()); + } + + public TokenReply handleRequest (TokenRequestHandler caller, + TokenRequest request) + { + // Set the name of this thread to the client ID to perform + // proper owner checking. + Thread.currentThread().setName (request.clientID ()); + + // process the request + return super.handleRequest (caller, request); + } + + public void abandonLock (String clientID) + { + // Set the name of this thread to the client ID to perform + // proper owner checking. + Thread.currentThread().setName (clientID); + + super.abandonLock (clientID); + } +} diff --git a/java/JACE/netsvcs/Token/RemoteLock.java b/java/JACE/netsvcs/Token/RemoteLock.java new file mode 100644 index 00000000000..824e05a31f0 --- /dev/null +++ b/java/JACE/netsvcs/Token/RemoteLock.java @@ -0,0 +1,543 @@ +package JACE.netsvcs.Token; + +import java.io.*; +import JACE.Concurrency.*; +import JACE.ASX.*; +import JACE.Connection.*; +import JACE.OS.*; + +/** + * Proxy used by clients to connect to the token service. This + * implements the AbstractLock interface, so can be used like any + * other synchronization mechanism. The user can either use this + * class directly, or use a proxy which already inputs its type. + * <P> + * Currently, a separate instance (and thus a separate socket connection) + * must be used for each thread which accesses the service. The token + * service itself could handle multiple client IDs and token names per + * connection with the following requirement -- since the service blocks + * in its operations, a shadow mutex would have to be used in the proxy. + * <P> + * It would be best if the user called the close () method after finishing + * up with a RemoteLock, but that is not absolutely necessary. The socket + * will be closed when the JVM exits or finalize is called. (That will also + * free the actual token in the token service in case release was never + * called.) + * <P> + * The SLEEPHOOK result is never returned, only SUCCESS or FAILURE. (C++ + * version doesn't seem to indicate the sleep hook result.) + * + *@see MutexHandler + *@see RWMutexHandler + *@see JACE.Concurrency.AbstractLock + * + *@author Everett Anderson + */ +public class RemoteLock extends SvcHandler implements AbstractLock +{ + /** + * Accessor for the token name. + * + *@return name of the token + */ + public String tokenName () + { + return request_.tokenName (); + } + + /** + * Set the name of the token. + */ + public void tokenName (String name) + { + request_.tokenName (name); + } + + /** + * Accessor for the client ID. + */ + public String clientID () + { + return request_.clientID (); + } + + /** + * Set the client ID. + */ + public void clientID (String clientID) + { + request_.clientID (clientID); + } + + /** + * Constructor. + * + *@see LockTypes + *@param tokenType type of token to create in the token service + *@param proxyType type of proxy to define this RemoteLock as + *@param tokenName name of the token to connect to in the token service + *@param clientID clientID to use to refer to this client + *@param host host name of the token service + *@param port port to connect to for the token service + */ + public RemoteLock (int tokenType, + int proxyType, + String tokenName, + String clientID, + String host, + int port) + { + host_ = host; + port_ = port; + + // Only allocates one reply and one request + reply_ = new TokenReply (); + + request_ = new TokenRequest (tokenType, + proxyType, + 0, + tokenName, + clientID); + } + + /** + * Reconnect this proxy to the token service. + * + *@exception LockException problem occured in reconnecting + */ + protected void reconnect () throws LockException + { + Connector c = new Connector (); + c.open (host_, port_); + + try { + c.connect (this); + } catch (InstantiationException e) { + throw new LockException (e.getMessage()); + } catch (IllegalAccessException e) { + throw new LockException (e.getMessage()); + } catch (IOException e) { + throw new LockException (e.getMessage()); + } + } + + /** + * Check to see if this RemoteLock is connected. + */ + public boolean connected () + { + return connected_; + } + + /** + * Initialize this RemoteLock. Called by Connector. + */ + public int open (Object obj) + { + connected_ = true; + return 0; + } + + /** + * Shut down the connection to the server. Current implementation + * calls close (). + */ + public int close (long flags) + { + return close (); + } + + /** + * Shut down the connection to the server and mark this lock + * as disconnected. + */ + public int close () + { + if (connected ()) { + try { + connected_ = false; + peer ().close (); + } catch (IOException e) { + return -1; + } + } + + return 0; + } + + /** + * Send the given request to the token service, throwing a + * LockException on error. + */ + protected void sendRequest (TokenRequest request) throws LockException + { + try { + if (!connected ()) + reconnect (); + + request.streamOutTo (peer ().dataOutputStream ()); + + } catch (IOException e) { + close (); + throw new LockException (e.getMessage ()); + } + } + + /** + * Receive a reply from the token service, throwing a LockException + * on error. + */ + protected void receiveReply (TokenReply reply) throws LockException + { + if (!connected ()) + throw new LockException ("Proxy wasn't connected, any replies lost"); + + try { + + reply.streamInFrom (peer ().dataInputStream ()); + + } catch (IOException e) { + close (); + throw new LockException (e.getMessage ()); + } + } + + /** + * For errors that shouldn't generate exceptions, return the + * appropriate result code as defined in AbstractLock. + * + *@return AbstractLock.SUCCESS or AbstractLock.FAILURE + */ + protected int processErrno (TokenReply reply) + { + switch (reply.errno ()) + { + case TokenReply.NO_ERRORS: + return AbstractLock.SUCCESS; + case TokenReply.EIO: + close (); + return AbstractLock.FAILURE; + default: + return AbstractLock.FAILURE; + } + } + + /** + * Make a request to the token service with the given operation + * type and arguments. + * + *@see LockOperations + *@see LockTypes + *@param operationType type of operation to perform + *@param proxyType type of proxy this is + *@param requeuePosition put this owner at this position in the + * waiting queue (only makes sense if the + * operation is renew) + *@return AbstractLock.SUCCESS or AbstractLock.FAILURE + *@exception LockException remote access error occured + */ + protected int makeRequest (int operationType, + int proxyType, + int requeuePosition) + throws LockException + { + request_.operationType (operationType); + request_.proxyType (proxyType); + request_.requeuePosition (requeuePosition); + request_.useTimeout (false); + + sendRequest (request_); + receiveReply (reply_); + + // make sure that if someone does send a magic cookie arg back, + // to keep it going + request_.arg (reply_.arg ()); + + return processErrno (reply_); + } + + /** + * Make a request to the token service with the given arguments + * that must be performed by the given absolute time timeout. + * Currently, the timeout is managed by the remote service. + * + *@see LockOperations + *@see LockTypes + *@param operationType type of operation to perform + *@param proxyType type of proxy this is + *@param requeuePosition put this owner at this position in the + * waiting queue (only makes sense if the + * operation is renew) + *@param timeout absolute time timeout to accomplish the operation by + *@return AbstractLock.SUCCESS or AbstractLock.FAILURE + *@exception LockException remote access error occured + */ + protected int makeRequest (int operationType, + int proxyType, + int requeuePosition, + TimeValue timeout) + throws LockException, TimeoutException + { + request_.operationType (operationType); + request_.proxyType (proxyType); + request_.requeuePosition (requeuePosition); + request_.useTimeout (timeout); + + sendRequest (request_); + receiveReply (reply_); + + request_.arg (reply_.arg ()); + + if (reply_.errno () == TokenReply.ETIME) + throw new TimeoutException (timeout, "Remote Lock"); + + return processErrno (reply_); + } + + /** + * Acquire ownership of the lock, blocking indefinitely if necessary. + * <P> + *@return AbstractLock.FAILURE or AbstractLock.SUCCESS + *@exception LockException a remote error occured + */ + public int acquire () throws LockException + { + return makeRequest (LockOperations.ACQUIRE, 0, 0); + } + + /** + * Acquire ownership of the lock by the given absolute time time-out. + * A value of null for the timeout parameter results in a blocking + * acquire. + * A value of TimeValue.zero throws a TimeoutException if the + * acquire would block. + * <P> + *@param timeout absolute time by which the lock must be acquired + *@return appropriate Lock return value (AbstractLock.FAILURE, + * AbstractLock.SUCCESS or AbstractLock.SLEEPHOOK) + *@exception LockException a remote error occured + *@exception JACE.ASX.TimeoutException thrown when the lock is not + * obtained by the desired time + *@see #tryAcquire + */ + public int acquire (TimeValue timeout) + throws LockException, TimeoutException + { + return makeRequest (LockOperations.ACQUIRE, 0, 0, timeout); + } + + /** + * Acquire a read lock, blocking indefinitely if necessary. + * + *@return AbstractLock.FAILURE or AbstractLock.SUCCESS + *@exception LockException a remote error occured + */ + public int acquireRead () throws LockException + { + return makeRequest (LockOperations.ACQUIRE, + LockTypes.READ_LOCK_PROXY, + 0); + } + + /** + * Acquire a read lock by the given absolute time time-out. + * + *@param timeout absolute time by which the lock must be acquired + *@return appropriate lock return value (AbstractLock.FAILURE, + * AbstractLock.SUCCESS or AbstractLock.SLEEPHOOK) + *@exception LockException a remote error occured + *@exception JACE.ASX.TimeoutException thrown when the lock is not + * obtained by the desired time + *@see #tryAcquireRead + */ + public int acquireRead (TimeValue timeout) + throws LockException, TimeoutException + { + return makeRequest (LockOperations.ACQUIRE, + LockTypes.READ_LOCK_PROXY, + 0, + timeout); + } + + /** + * Acquire a write lock, blocking indefinitely if necessary. + * + *@return AbstractLock.FAILURE or AbstractLock.SUCCESS + *@exception LockException a remote error occured + */ + public int acquireWrite () + throws LockException + { + return makeRequest (LockOperations.ACQUIRE, + LockTypes.WRITE_LOCK_PROXY, + 0); + } + + /** + * Acquire a write lock by the given absolute time time-out. + * + *@param timeout absolute time by which the lock must be acquired + *@return appropriate lock return value (AbstractLock.FAILURE, + * AbstractLock.SUCCESS or AbstractLock.SLEEPHOOK) + *@exception LockException a remote error occured + *@exception JACE.ASX.TimeoutException thrown when the lock is not + * obtained by the desired time + *@see #tryAcquireWrite + */ + public int acquireWrite (TimeValue timeout) + throws LockException, TimeoutException + { + return makeRequest (LockOperations.ACQUIRE, + LockTypes.WRITE_LOCK_PROXY, + 0, + timeout); + } + + + /** + * Give up the lock to some number of waiting threads (if any), then + * reacquire, blocking indefinitely if necessary. + * <P> + * An optimized method that efficiently reacquires the token if no + * other threads are waiting. This is useful for situations where + * you don't want to degrade the quality of service if there are + * other threads waiting to get the token. + * <P> + *@param requeuePosition position in the waiters queue to insert + * this thread. If this value is -1 and there are other + * threads waiting to obtain the token, this thread is queued + * at the end. If this value is greater than -1, then it + * indicates how many entries to skip over before inserting + * our thread into the queue. (For example, if it is 0, + * this thread is put at the front of the queue.) If this + * value is greater than the number of waiters, this thread is + * simply put at the end of the current waiters queue. + *@return AbstractLock.FAILURE or AbstractLock.SUCCESS + *@exception LockException a remote error occured + */ + public int renew (int requeuePosition) + throws LockException + { + return makeRequest (LockOperations.RENEW, + 0, + requeuePosition); + } + + /** + * Give up the lock to some waiting threads (if any), then reacquire + * by the given absolute time time-out. + * <P> + * An optimized method that efficiently reacquires the token if no + * other threads are waiting. This is useful for situations where + * you don't want to degrade the quality of service if there are + * other threads waiting to get the token. + * <P> + * A value of null for the timeout indicates a blocking renew. + * <P> + *@param requeuePosition position in the waiters queue to insert + * this thread. If this value is -1 and there are other + * threads waiting to obtain the token, this thread is queued + * at the end. If this value is greater than -1, then it + * indicates how many entries to skip over before inserting + * our thread into the queue. (For example, if it is 0, + * this thread is put at the front of the queue.) If this + * value is greater than the number of waiters, this thread is + * simply put at the end of the current waiters queue. + * + *@param timeout absolute time by which the lock must be reacquired + * + *@return appropriate AbstractLock return value + * (AbstractLock.FAILURE or AbstractLock.SUCCESS) + *@exception LockException a remote error occured + *@exception JACE.ASX.TimeoutException thrown when the lock is not + * obtained by the desired time + */ + public int renew (int requeuePosition, TimeValue timeout) + throws LockException, TimeoutException + { + return makeRequest (LockOperations.RENEW, + 0, + requeuePosition, + timeout); + } + + /** + * Try to acquire the lock without blocking. + * <P> + *@return appropriate AbstractLock return value + * (AbstractLock.FAILURE or AbstractLock.SUCCESS) + *@exception LockException a remote error occured + */ + public int tryAcquire () throws LockException + { + return makeRequest (LockOperations.TRY_ACQUIRE, 0, 0); + } + + /** + * Try to acquire a read lock without blocking. + * <P> + *@return appropriate AbstractLock return value + * (AbstractLock.FAILURE or AbstractLock.SUCCESS) + *@exception LockException a remote error occured + */ + public int tryAcquireRead () throws LockException + { + return makeRequest (LockOperations.TRY_ACQUIRE, + LockTypes.READ_LOCK_PROXY, + 0); + } + + /** + * Try to acquire a write lock without blocking. + * + *@return appropriate AbstractLock return value + * (AbstractLock.FAILURE or AbstractLock.SUCCESS) + *@exception LockException a remote error occured + */ + public int tryAcquireWrite () throws LockException + { + return makeRequest (LockOperations.TRY_ACQUIRE, + LockTypes.WRITE_LOCK_PROXY, + 0); + } + + /** + * Release ownership of this lock. + * + *@return appropriate AbstractLock return value + * (AbstractLock.FAILURE or AbstractLock.SUCCESS) + *@exception LockException a remote error occured + */ + public int release () throws LockException + { + return makeRequest (LockOperations.RELEASE, 0, 0); + } + + /** + * Closes the connection to the server (if it is still open). + */ + protected void finalize () throws Throwable + { + close (); + } + + /** + * No-op implementation for the sleep hook (unused). + */ + public void sleepHook () {} + + /** Status of whether this RemoteLock is connected to the server or not */ + protected boolean connected_ = false; + + /** Request object for transmissions to the server */ + protected TokenRequest request_; + + /** Reply object for receiving transmissions from the server */ + protected TokenReply reply_; + + /** Host name of the token service */ + protected String host_; + + /** Port number of the token service */ + protected int port_; +} diff --git a/java/JACE/netsvcs/Token/RemoteMutex.java b/java/JACE/netsvcs/Token/RemoteMutex.java new file mode 100644 index 00000000000..7f2a4311116 --- /dev/null +++ b/java/JACE/netsvcs/Token/RemoteMutex.java @@ -0,0 +1,28 @@ +package JACE.netsvcs.Token; + +/** + * Proxy used by clients for accessing a mutex at the token service. + */ +public class RemoteMutex extends RemoteLock +{ + /** + * Constructor. + * + *@param tokenName name of the mutex to access + *@param clientID identification of this client + *@param host host of the token service + *@param port port number of the token service + */ + public RemoteMutex (String tokenName, + String clientID, + String host, + int port) + { + super (LockTypes.MUTEX, + 0, + tokenName, + clientID, + host, + port); + } +} diff --git a/java/JACE/netsvcs/Token/RemoteRWMutex.java b/java/JACE/netsvcs/Token/RemoteRWMutex.java new file mode 100644 index 00000000000..cc666bfd70f --- /dev/null +++ b/java/JACE/netsvcs/Token/RemoteRWMutex.java @@ -0,0 +1,29 @@ +package JACE.netsvcs.Token; + +/** + * Proxy used by clients for accessing a reader/writer mutex + * at the token service. + */ +public class RemoteRWMutex extends RemoteLock +{ + /** + * Constructor. + * + *@param tokenName name of the reader/writer lock to access + *@param clientID identification of this client + *@param host host of the token service + *@param port port number of the token service + */ + public RemoteRWMutex (String tokenName, + String clientID, + String host, + int port) + { + super (LockTypes.RWLOCK, + 0, + tokenName, + clientID, + host, + port); + } +} diff --git a/java/JACE/netsvcs/Token/TokenAcceptor.java b/java/JACE/netsvcs/Token/TokenAcceptor.java new file mode 100644 index 00000000000..53adf08753b --- /dev/null +++ b/java/JACE/netsvcs/Token/TokenAcceptor.java @@ -0,0 +1,353 @@ +package JACE.netsvcs.Token; + +import java.io.*; +import java.net.*; +import java.util.*; +import JACE.OS.*; +import JACE.Misc.*; +import JACE.Connection.*; +import JACE.netsvcs.Server; + +/** + * Server for the token service. Launches TokenRequestHandlers as + * connections are made. Currently, the activation strategy must be + * thread per connection since the operations are allowed to block + * during acquires, etc. + * <P> + * Two types of locks are supported by default -- Mutex and RWMutex. + * New lock types can be added from the command line without changing + * any code in the service. To do this, just create a class which + * implements the LockHandler interface. + * <P> + * When a request for a new lock comes in, a LockHandler of the corresponding + * type is created and a mapping is created between the lock name and the + * handler. Later requests reuse that mapping. + * <P> + * <B>Valid command line arguments:</B> + * <PRE> + * -f (class name):(type) Specify a LockHandler for a type of lock"); + * -p (port number) Port to listen on for clients"); + * -d Enable debugging messages"); + * </PRE> + * + *@see JACE.netsvcs.Server + *@see TokenRequestHandler + *@see LockHandler + *@see LockTypes + */ +public class TokenAcceptor extends Server +{ + protected void addDefaultFactories() throws ClassNotFoundException { + addHandlerFactory(LockTypes.MUTEX, + Class.forName("JACE.netsvcs.Token.MutexHandler")); + addHandlerFactory(LockTypes.RWLOCK, + Class.forName("JACE.netsvcs.Token.RWMutexHandler")); + } + + /** + * Default constructor. + */ + public TokenAcceptor() { + + // Set the name in case we aren't using the service configurator. + name ("Token Service"); + + lockHandlerMap_ = new Hashtable(); + handlerFactoryMap_ = new Hashtable(); + clientHandlerMap_ = new Hashtable (); + } + + /** + * Add a map between a type of lock and the factory which + * creates LockHandler instances that handle it. + * + *@see LockTypes + *@param type number representing the type of lock + *@param factory Class object for a LockHandler class + */ + public void addHandlerFactory(Integer type, Class factory) { + handlerFactoryMap_.put(type, factory); + } + + /** + * Add a map between a type of lock and the factory which + * creates LockHandler instances that handle it. + * + *@see LockTypes + *@param type number representing the type of lock + *@param factory Class object for a LockHandler class + */ + public void addHandlerFactory(int type, Class factory) { + addHandlerFactory(new Integer(type), factory); + } + + /** + * Remove the LockHandler factory which handles locks + * of the specified type. + * + *@param type type of LockHandler to cease supporting + *@return the LockHandler instance (or null if not found) + */ + public Object removeHandlerFactory(Integer type) { + return handlerFactoryMap_.remove(type); + } + + /** + * Remove the LockHandler factory which handles locks + * of the specified type. + * + *@param type type of LockHandler to cease supporting + *@return the LockHandler instance (or null if not found) + */ + public Object removeHandlerFactory(int type) { + return handlerFactoryMap_.remove(new Integer(type)); + } + + /** + * Retrieve the LockHandler corresponding to the given name + * or create a new one if it doesn't exist. This is called by + * TokenRequestHandlers. + * + *@param lockName name of the lock to retrieve or create a LockHandler for + *@param lockType type of the lock + *@return LockHandler which handles the lock with that name + */ + public LockHandler getLockHandler (String lockName, + int lockType) { + synchronized (lockHandlerMap_) { + + Object obj = lockHandlerMap_.get(lockName); + + if (obj != null) + return (LockHandler)obj; + else { + LockHandler handler = newHandler (lockType); + lockHandlerMap_.put (lockName, handler); + return handler; + } + } + } + + /** + * Create a new LockHandler of the specified type. + * + *@param type type of LockHandler to create + *@return a new LockHandler instance + */ + protected LockHandler newHandler (int type) { + ACE.DEBUG ("Creating new handler of type " + type); + Object factoryObj = handlerFactoryMap_.get(new Integer(type)); + if (factoryObj == null) + return null; + + Class factory = (Class)factoryObj; + LockHandler handler = null; + + try { + handler = (LockHandler)factory.newInstance(); + } catch (InstantiationException e) { + ACE.ERROR("Can't create a handler of type " + type); + } catch (IllegalAccessException e) { + ACE.ERROR("Handler of type " + type + + " must have a default constructor"); + } + return handler; + } + + /** + * Simple main program. See the class description for more + * information about command line arguments. + */ + public static void main(String args[]) { + TokenAcceptor ta = new TokenAcceptor(); + + ta.init(args); + } + + /** + * Create a new TokenRequestHandler instance. + */ + protected SvcHandler makeSvcHandler() + { + return new TokenRequestHandler(); + } + + /** + * Sets up the default factories so the user can override them on + * the command line, then delegates back to Server.init (String[]). + * + *@see JACE.netsvcs.Server#init + *@param args command line arguments + *@return -1 on failure, 0 on success + */ + public int init(String [] args) { + try { + addDefaultFactories (); + } catch (ClassNotFoundException e) { + ACE.ERROR ("Can't find default factory " + e.getMessage ()); + return -1; + } + + return super.init (args); + } + + /** + * Prints out the valid command line arguments. See the class + * description for more information. Called by Server.init when + * parseArgs returns -1. + */ + protected void printUsage () + { + ACE.ERROR ("Valid options:\n"); + ACE.ERROR ("-f <class name>:<type> Specify a handler for a type of lock"); + ACE.ERROR ("-p <port number> Port to listen on for clients"); + ACE.ERROR ("-d Enable debugging messages"); + } + + /** + * Parses the command line arguments. See the class description + * for more information. + * + *@param args command line arguments + *@return -1 on failure, 0 on success + */ + protected int parseArgs(String [] args) + { + int c = 0; + GetOpt opt = new GetOpt(args, "p:f:d", true); + + try { + + while ((c = opt.next ()) != -1) { + switch (c) + { + case 'd': + ACE.enableDebugging (); + ACE.DEBUG ("Debugging is enabled"); + break; + case 'p': + if (!port (opt.optarg ())) + return -1; + break; + case 'f': + if (newHandlerFactory (opt.optarg ()) < 0) + return -1; + break; + default: + ACE.ERROR("Unknown argument: " + (char)c); + return -1; + } + } + } catch (ArrayIndexOutOfBoundsException e) { + ACE.ERROR ("Option -" + (char)c + " requires an argument"); + return -1; + } + + return 0; + } + + /** + * Load the Class for the specified LockHandler and create a mapping + * from its type to the Class instance. Used to parse the command + * line pair of (class name):(type). + * + *@param nameAndType (class name):(type) pair from the command line + *@return -1 on failure, 0 on success + */ + protected int newHandlerFactory (String nameAndType) + { + int colon = nameAndType.lastIndexOf (':'); + + if (colon < 1) { + ACE.ERROR ("Invalid -f <class name>:<type num> for handler: " + + nameAndType); + return -1; + } + + int type = 0; + try { + type = Integer.parseInt (nameAndType.substring (colon + 1)); + } catch (NumberFormatException e) { + ACE.ERROR ("Invalid token type: " + e.getMessage ()); + return -1; + } + + String name = nameAndType.substring (0, colon); + + Class factory; + try { + factory = Class.forName (name); + } catch (ClassNotFoundException e) { + ACE.ERROR (e.getMessage ()); + return -1; + } + + addHandlerFactory (type, factory); + ACE.DEBUG ("New handler " + name + " with type " + type); + + return 0; + } + + /** + * Create a mapping between a client ID and a LockHandler. This is + * only used by TokenRequestHandlers in order to keep track of which + * locks a client touches. That way, if/when a client disconnects, + * all its locks can be abandoned successfully. + * + *@param clientID client identification (key in the mapping) + *@param handler LockHandler to map to (value in the mapping) + * + */ + void addClientLockHandler (String clientID, + LockHandler handler) + { + Object obj = clientHandlerMap_.get (clientID); + if (obj == null) { + // Probably won't have more than 10 locks per client ID, and the Vector + // should resize automatically even if someone does. + Vector handlerList = new Vector (10); + handlerList.addElement (handler); + clientHandlerMap_.put (clientID, handlerList); + } else { + Vector handlerList = (Vector)obj; + int alreadyThereIndex = handlerList.indexOf (handler); + if (alreadyThereIndex == -1) + handlerList.addElement (handler); + } + } + + /** + * Called by TokenRequestHandlers to remove a specified client ID + * from the client ID to LockHandler mapping. + */ + void removeClient (String clientID) + { + clientHandlerMap_.remove (clientID); + } + + /** + * Called by TokenRequestHandlers to obtain a list of all LockHandlers + * accessed by a particular client. Useful for abandoning the locks. + */ + Enumeration getClientLockHandlers (String clientID) + { + Object obj = clientHandlerMap_.get (clientID); + if (obj == null) + return null; + else + return ((Vector)obj).elements (); + } + + // These should be replaced by weak hash maps when available + + // Map consisting of (token name) to (LockHandler instance) pairs + private Hashtable lockHandlerMap_; + + // Map consisting of (Integer token type) to (Class instance for + // corresponding LockHandler class) + private Hashtable handlerFactoryMap_; + + // Map consisting of (client ID) to (Vector of LockHandler) pairs + private Hashtable clientHandlerMap_; +} + diff --git a/java/JACE/netsvcs/Token/TokenReply.java b/java/JACE/netsvcs/Token/TokenReply.java new file mode 100644 index 00000000000..35f50901610 --- /dev/null +++ b/java/JACE/netsvcs/Token/TokenReply.java @@ -0,0 +1,171 @@ +package JACE.netsvcs.Token; + +import java.io.*; +import JACE.ASX.*; +import JACE.OS.*; + +/** + * Reply from a lock operation, and constants involved in it. + * This is compatible with the C++ ACE version. The user probably + * never deals directly with the constant errno values in Java ACE since + * the proxy (RemoteLock) should hide those details. + */ +public class TokenReply +{ + + /** indicates success */ + public static final int NO_ERRORS = 0; + + /** indicates a timeout */ + public static final int ETIME = 62; + + /** indicates the operation was interrupted */ + public static final int EINTR = 4; + + /** deadlock indication errno (JACE currently doesn't implement a + * deadlock detection system, but C++ ACE does and JACE proxies + * act appropriately). + */ + public static final int EDEADLK = 45; + + /** indicates the operation would block, used in tryAcquire */ + public static final int EWOULDBLOCK = 11; + + /** indicates a token name or client ID was too long */ + public static final int ENAMETOOLONG = 78; + + /** indicates an operation type was not supported */ + public static final int ENOTSUP = 48; + + /** indicates that this client was not the owner of the lock, + * so couldn't perform the desired operation */ + public static final int EACCES = 13; + + /** indicates an IO error occured during transmission of the request */ + public static final int EIO = 5; + + /** indicates a generic failure to complete the request */ + public static final int EFAULT = 14; + + /** indicates an operation was requested on an unknown type of token */ + public static final int EINVAL = 22; + + /** constant length of a valid token reply */ + private final static int LENGTH = 12; + + /** error code */ + private int errno_; + + /** argument (unused in JACE) */ + private int arg_; + + /** Dump the state of this TokenReply to a String */ + public String toString () + { + return "TokenReply(" + this.length() + ", " + this.errno_ + + ", " + this.arg_ + ")"; + } + + /** Default constructor (NO_ERRORS) */ + public TokenReply () + { + errno_ = NO_ERRORS; + arg_ = 0; + } + + /** Constructor which takes the error code and argument */ + public TokenReply (int errno, int arg) + { + errno_ = errno; + arg_ = arg; + } + + /** + * Accessor for the length of this TokenReply. + */ + public int length () + { + return LENGTH; + } + + /** Accessor for the error code of this TokenReply. */ + public int errno () + { + return errno_; + } + + /** + * Set the error code of this TokenReply. + */ + public void errno (int value) + { + errno_ = value; + } + + /** + * Accessor of the argument of this TokenReply. (Unused in JACE) + */ + public int arg () + { + return arg_; + } + + /** + * Set the argument of this TokenReply. (Unused in JACE) + */ + public void arg (int value) + { + arg_ = value; + } + + /** + * Read this TokenReply in from the given InputStream. + */ + public void streamInFrom (InputStream is) + throws IOException, EOFException + { + BufferedInputStream bis = new BufferedInputStream (is, LENGTH); + DataInputStream dis = new DataInputStream (bis); + + streamInFrom (dis); + } + + /** + * Read this TokenReply in from the given DataInputStream. + */ + public void streamInFrom (DataInputStream dis) + throws IOException, EOFException + { + int length = dis.readInt (); + if (length != LENGTH) + throw new IOException ("Invalid TokenReply length " + length); + + this.errno_ = dis.readInt (); + this.arg_ = dis.readInt (); + } + + /** + * Write this TokenReply out to the given OutputStream. + */ + public void streamOutTo (OutputStream os) + throws IOException + { + BufferedOutputStream bos = new BufferedOutputStream (os, LENGTH); + DataOutputStream dos = new DataOutputStream (bos); + + streamOutTo (dos); + } + + /** + * Write this TokenReply out to the given DataOutputStream. + */ + public void streamOutTo (DataOutputStream dos) + throws IOException + { + dos.writeInt (LENGTH); + dos.writeInt (this.errno_); + dos.writeInt (this.arg_); + + dos.flush (); + } +} diff --git a/java/JACE/netsvcs/Token/TokenRequest.java b/java/JACE/netsvcs/Token/TokenRequest.java new file mode 100644 index 00000000000..eb1113428c5 --- /dev/null +++ b/java/JACE/netsvcs/Token/TokenRequest.java @@ -0,0 +1,426 @@ +package JACE.netsvcs.Token; + +import java.io.*; +import JACE.ASX.*; +import JACE.OS.*; + +/** + * Request for an operation on a lock. This is compatible with the + * C++ ACE version. The US-ASCII character encoding is used for + * String to byte conversions (and vice versa). If + * that encoding isn't supported, it attempts to use the default + * encoding (but prints a message). Users probably never need to + * deal with this class directly. The notify field isn't used + * in JACE (or in C++ ACE as far as I can tell). + * + *@author Everett Anderson + */ +public class TokenRequest +{ + /** Maximum length for a token name */ + public final static int MAX_TOKEN_NAME_LEN = 40; + + /** Maximum length for a client ID */ + public final static int MAX_CLIENT_ID_LEN = 276; + + /** Length of the fixed size header */ + protected final static int HEADER_LEN = 40; + + /** Maximum length of any TokenRequest (total) */ + protected final static int MAX_LEN = 359; + + /** + * Dump this TokenRequest's state out to a String. + */ + public String toString() + { + return "TokenRequest(" + this.length() + ", " + + this.tokenType_ + ", " + + this.proxyType_ + ", " + + this.operationType_ + ", " + + this.requeuePosition_ + ", " + + this.notify_ + ", " + + this.useTimeout_ + ", " + + this.sec_ + ", " + + this.usec_ + ", " + + this.arg_ + ", " + + this.tokenName_ + ", " + + this.clientID_ + ")"; + } + + /** Default constructor. */ + public TokenRequest() + { + // Remember that the length is transmitted first + tokenType_ = 0; + proxyType_ = 0; + operationType_ = 0; + requeuePosition_ = 0; + notify_ = 0; + useTimeout_ = 0; + sec_ = 0; + usec_ = 0; + arg_ = 0; + tokenName_ = ""; + clientID_ = ""; + // Transmission is "<10 ints><token name>(null):<clientID>(null)" + + charEncoding_ = "US-ASCII"; + + buffer_ = new byte[MAX_LEN]; + } + + /** + * Create a request which doesn't use timeouts. + * + *@param tokenType type of token (usually a constant in LockTypes) + *@param proxyType type of proxy (usually a constant in LockTypes) + *@param operationType type of operation (usually a constant in + * LockOperations) + *@param tokenName name of the token to operate on + *@param clientID name of the client requesting an operation + * + *@see LockTypes + *@see LockOperations + */ + public TokenRequest(int tokenType, + int proxyType, + int operationType, + String tokenName, + String clientID) + { + this(); + + this.tokenType_ = tokenType; + this.proxyType_ = proxyType; + this.operationType_ = operationType; + this.tokenName_ = tokenName; + this.clientID_ = clientID; + } + + /** + * Create a request which uses the given absolute time timeout. + * + *@param tokenType type of token (usually a constant in LockTypes) + *@param proxyType type of proxy (usually a constant in LockTypes) + *@param operationType type of operation (usually a constant in + * LockOperations) + *@param tokenName name of the token to operate on + *@param clientID name of the client requesting an operation + *@param tv absolute time timeout to process the request by + * + *@see LockTypes + *@see LockOperations + */ + public TokenRequest(int tokenType, + int proxyType, + int operationType, + String tokenName, + String clientID, + TimeValue tv) + { + this(tokenType, + proxyType, + operationType, + tokenName, + clientID); + + this.useTimeout_ = 1; + this.sec_ = (int)tv.sec(); + this.usec_ = tv.nanos() / 1000; + } + + /** + * Return the length of this TokenRequest. + * <P> + * Details: + * <PRE> + * Fixed size header of length HEADER_LEN + * token name + * null + * : + * client ID + * null + * </PRE> + */ + public int length() + { + return (HEADER_LEN + + this.tokenName_.length() + + this.clientID_.length() + 3); + } + + /** Accessor for the token type. */ + public int tokenType() + { + return this.tokenType_; + } + /** Set the token type. */ + public void tokenType(int type) + { + this.tokenType_ = type; + } + /** Accessor for the proxy type. */ + public int proxyType() + { + return this.proxyType_; + } + /** Set the proxy type. */ + public void proxyType(int type) + { + this.proxyType_ = type; + } + /** Accessor for the operation type. */ + public int operationType() + { + return this.operationType_; + } + /** Set the operation type. */ + public void operationType(int type) + { + this.operationType_ = type; + } + /** Accessor for the requeue position. This only makes + * sense for a renew operation. */ + public int requeuePosition() + { + return this.requeuePosition_; + } + /** Set the requeue position. This only makes sense for + * a renew operation. */ + public void requeuePosition(int position) + { + this.requeuePosition_ = position; + } + /** Accessor for the flag to determine if a timeout should be used. */ + public boolean useTimeout() + { + return (this.useTimeout_ == 1 ? true : false); + } + /** Set the flag to enable or disable use of timeouts. + */ + public void useTimeout(boolean useIt) + { + this.useTimeout_ = (useIt == true ? 1 : 0); + } + + /** + * Set the absolute time timeout to the given TimeValue's value, and + * enable the useTimeout flag. + */ + public void useTimeout (TimeValue timeout) + { + this.useTimeout_ = 1; + this.sec_ = (int)timeout.sec (); + this.usec_ = timeout.nanos () / 1000; + } + + /** + * Accessor for the seconds component of the absolute time timeout. + */ + public int sec() + { + return this.sec_; + } + /** Set the seconds component of the timeout. */ + public void sec(int sec) + { + this.sec_ = sec; + } + /** Accessor for the usec component of the timeout. */ + public int usec() + { + return this.usec_; + } + /** Set the usec component of the timeout. */ + public void usec(int usec) + { + this.usec_ = usec; + } + /** Accessor for the arg value. (unused in JACE) */ + public int arg() + { + return this.arg_; + } + /** Set the arg value. (unused in JACE) */ + public void arg(int arg) + { + this.arg_ = arg; + } + + /** Accessor for the name of the token. */ + public String tokenName() + { + return this.tokenName_; + } + + /** Set the name of the token. */ + public void tokenName(String name) + { + this.tokenName_ = name; + } + + /** Accessor for the client identification string. */ + public String clientID() + { + return this.clientID_; + } + + /** Set the client identification string. */ + public void clientID(String ID) + { + this.clientID_ = ID; + } + + /** + * Construct a String from a specific subset of the byte array. + * The string begins at the start index and ends at either the + * end of the buffer or the first byte with value 0 (null). + * Tries to use the specified encoding, but uses the default + * platform encoding if it isn't available. + * + *@param start beginning index in the buffer + *@param bufferLength total length of the buffer + */ + protected String stringFromBuffer(int start, int bufferLength) + { + int end = start; + String result = null; + + while (end < bufferLength && this.buffer_[end] != 0) + end++; + + try { + result = new String(this.buffer_, + start, + end - start, + this.charEncoding_); + } catch (UnsupportedEncodingException e) { + ACE.ERROR(this.charEncoding_ + " is not supported!"); + result = new String (this.buffer_, start, end - start); + } + + return result; + } + + /** + * Read the request in from the given InputStream. + */ + public void streamInFrom(InputStream is) + throws IOException, EOFException + { + BufferedInputStream bis = new BufferedInputStream(is, + MAX_LEN); + DataInputStream dis = new DataInputStream(bis); + + streamInFrom (dis); + } + + /** + * Read the request in from the given DataInputStream. + */ + public void streamInFrom (DataInputStream dis) + throws IOException, EOFException + { + int length = dis.readInt(); + if (length > MAX_LEN || length < HEADER_LEN + 5) + throw new IOException ("Invalid TokenRequest length: " + length); + + this.tokenType_ = dis.readInt(); + this.proxyType_ = dis.readInt(); + this.operationType_ = dis.readInt(); + this.requeuePosition_ = dis.readInt(); + this.notify_ = dis.readInt(); + this.useTimeout_ = dis.readInt(); + this.sec_ = dis.readInt(); + this.usec_ = dis.readInt(); + this.arg_ = dis.readInt(); + + int total = dis.read(this.buffer_, 0, length - HEADER_LEN); + + this.tokenName_ = this.stringFromBuffer(0, total); + + this.clientID_ = this.stringFromBuffer(this.tokenName_.length() + 2, + total); + + if (this.tokenName_.length() > MAX_TOKEN_NAME_LEN || + this.clientID_.length() > MAX_CLIENT_ID_LEN) + throw new IOException("Exceeds maximum token name or client ID"); + } + + /** + * Write the request out to the given OutputStream. + */ + public void streamOutTo (OutputStream os) + throws IOException + { + BufferedOutputStream bos = new BufferedOutputStream(os); + DataOutputStream dos = new DataOutputStream(bos); + + streamOutTo (dos); + } + + /** + * Write the request out to the given DataOutputStream. Tries to use + * the specified encoding to the convert the token name and client ID + * to bytes, but uses the platform default encoding if necessary. + */ + public void streamOutTo (DataOutputStream dos) + throws IOException + { + dos.writeInt(this.length()); + dos.writeInt(this.tokenType_); + dos.writeInt(this.proxyType_); + dos.writeInt(this.operationType_); + dos.writeInt(this.requeuePosition_); + + dos.writeInt(this.notify_); + dos.writeInt(this.useTimeout_); + dos.writeInt(this.sec_); + dos.writeInt(this.usec_); + dos.writeInt(this.arg_); + + StringBuffer data = new StringBuffer(this.tokenName_.length() + + this.clientID_.length() + + 3); + + data.append(this.tokenName_); + data.append('\0'); + data.append(':'); + data.append(this.clientID_); + data.append('\0'); + + byte buf [] = null; + String dataString = data.toString (); + try { + buf = dataString.getBytes (this.charEncoding_); + } catch (UnsupportedEncodingException e) { + ACE.ERROR (charEncoding_ + " is unsupported, trying to use default"); + buf = dataString.getBytes (); + } + + dos.write(buf, 0, buf.length); + dos.flush(); + } + + private int tokenType_; + private int proxyType_; + private int operationType_; + private int requeuePosition_; + private int notify_; + private int useTimeout_; + private int sec_; + private int usec_; + private int arg_; + + private String tokenName_; + private String clientID_; + + private byte buffer_[]; + + /** + * Character encoding to use for converting the token name and + * client ID to/from bytes. + */ + protected String charEncoding_; +} diff --git a/java/JACE/netsvcs/Token/TokenRequestHandler.java b/java/JACE/netsvcs/Token/TokenRequestHandler.java new file mode 100644 index 00000000000..cb6d729f3bd --- /dev/null +++ b/java/JACE/netsvcs/Token/TokenRequestHandler.java @@ -0,0 +1,180 @@ +package JACE.netsvcs.Token; + +import java.io.*; +import java.net.SocketException; +import java.util.*; +import JACE.Connection.*; +import JACE.OS.*; +import JACE.netsvcs.Handler; + +/** + * Created by TokenAcceptor to handle token requests. Delegates to + * the appropriate LockHandler. This is fairly robust, and can handle + * multiple clients and locks (meaning requests can come in to this + * handle with varying client IDs and token names and still be processed + * and released appropriately.) Compatible with the C++ ACE token service. + * + *@author Everett Anderson + */ +class TokenRequestHandler extends Handler +{ + /** + * Default constructor. + */ + public TokenRequestHandler() { + this.clients_ = new Vector (10); + } + + /** + * Creates a new TokenRequest instance. + */ + public Object newRequest () + { + return new TokenRequest (); + } + + /** + * Sends an error message to a client with the TokenReply.EIO + * errno before abandoning the connection. This is used when an IO + * error occured while receiving the request. + * + *@param lastRequest request object to get the arg from + */ + protected void sendAbortMessage (TokenRequest lastRequest) + { + TokenReply reply = new TokenReply (TokenReply.EIO, + lastRequest.arg ()); + try { + reply.streamOutTo (this.peer ().dataOutputStream ()); + } catch (Exception e) { + // Doesn't matter if there is an error here, we've abandoned + // the connection. + } + } + + /** + * Safely shuts down this handler, making sure to release any locks + * that were touched by clients from this TokenRequestHandler. + * + *@return -1 on failure, 0 on success + */ + public synchronized int close () + { + // For every client X that has used this handler + // for every LockHandler that X has used + // release the lock until it fails because X isn't the owner + // remove the client entries + // Call Handler.close () + if (!done ()) { + + TokenAcceptor parent = (TokenAcceptor) parent (); + Enumeration clientEnum = clients_.elements (); + + while (clientEnum.hasMoreElements ()) { + String clientID = (String)clientEnum.nextElement (); + + Enumeration handlers = parent.getClientLockHandlers (clientID); + if (handlers == null) + continue; + + int num = 0; + + while (handlers.hasMoreElements ()) { + LockHandler handler = (LockHandler)handlers.nextElement (); + + handler.abandonLock (clientID); + + num++; + } + + parent.removeClient (clientID); + } + + return super.close (); + } + + return 0; + } + + /** + * Read in the given TokenRequest and delegates to the appropriate + * LockHandler. + * + *@see JACE.netsvcs.Handler + *@param requestObject TokenRequest object to use + */ + public void processRequest (Object requestObject) + throws SocketException, EOFException, IOException + { + TokenRequest request = (TokenRequest)requestObject; + TokenAcceptor parent = (TokenAcceptor) parent (); + + try { + request.streamInFrom (this.peer ().dataInputStream ()); + + if (!request.tokenName ().equals (lastTokenName_)) { + // Switched tokens: + // + // Either find a handler that's already been made (which would + // mean this token has been accessed before), or create a new + // one with a new token + handler_ = parent.getLockHandler(request.tokenName(), + request.tokenType()); + + if (handler_ == null) { + // The client asked for an operation on a type of token + // that we don't know about. + ACE.ERROR ("Unknown lock type: " + request.tokenType ()); + TokenReply error = new TokenReply (TokenReply.EINVAL, + request.arg ()); + error.streamOutTo(this.peer ().dataOutputStream ()); + return; + } + + // Add this LockHandler to the list of those accessed by + // this clientID + parent.addClientLockHandler (request.clientID (), + handler_); + } + + if (!request.clientID ().equals (lastClientID_)) { + // Switched clients + + if (!clients_.contains (request.clientID ())) + clients_.addElement (request.clientID ()); + + parent.addClientLockHandler (request.clientID (), + handler_); + } + + lastClientID_ = request.clientID (); + lastTokenName_ = request.tokenName (); + + TokenReply reply = handler_.handleRequest(this, request); + + reply.streamOutTo(this.peer ().dataOutputStream ()); + + } catch (NullPointerException e) { + sendAbortMessage (request); + throw e; + } catch (IOException e) { + sendAbortMessage (request); + throw e; + } + } + + // List of clientIDs that have been processed by this instance + // of TokenRequestHandler. This is useful when abandoning the + // locks of any clients that have been using this socket. + private Vector clients_; + + // Name of the last token accessed + private String lastTokenName_ = null; + + // Last client ID which accessed a token from this handler + private String lastClientID_ = null; + + // Current LockHandler + private LockHandler handler_ = null; +} + diff --git a/java/JACE/netsvcs/Token/package.html b/java/JACE/netsvcs/Token/package.html new file mode 100644 index 00000000000..80777aecc0a --- /dev/null +++ b/java/JACE/netsvcs/Token/package.html @@ -0,0 +1,16 @@ +<!-- $Id$ --> +<HTML> +<BODY> +Token Service for remote mutexes and reader/writer locks. +<P> +New types of lock can be easily added on the command line to the service +without changing existing code by implementing a LockHandler for the new +type. +<P> +A simple test client is available in the tests directory under +netsvcs\Token. + +@see JACE.netsvcs.Token.LockHandler +@see <a href="http://www.cs.wustl.edu/~schmidt/ACE-netsvcs.html">ACE Network Services</a> +</BODY> +</HTML> |