Use read/write locking for downcall state engine
diff --git a/yoko-core/src/main/java/org/apache/yoko/orb/OB/CollocatedServer.java b/yoko-core/src/main/java/org/apache/yoko/orb/OB/CollocatedServer.java
index bd3ac2a..78c0e74 100644
--- a/yoko-core/src/main/java/org/apache/yoko/orb/OB/CollocatedServer.java
+++ b/yoko-core/src/main/java/org/apache/yoko/orb/OB/CollocatedServer.java
@@ -146,7 +146,7 @@
         // dispatch thread is used, we could avoid initializing the
         // state monitor when it is not needed.
         //
-        down.initStateMonitor();
+        down.allowWaiting();
 
         Upcall up;
 
diff --git a/yoko-core/src/main/java/org/apache/yoko/orb/OB/Downcall.java b/yoko-core/src/main/java/org/apache/yoko/orb/OB/Downcall.java
index 3714b04..68b0832 100644
--- a/yoko-core/src/main/java/org/apache/yoko/orb/OB/Downcall.java
+++ b/yoko-core/src/main/java/org/apache/yoko/orb/OB/Downcall.java
@@ -18,8 +18,14 @@
 package org.apache.yoko.orb.OB;
 
 import java.util.Vector;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
 
 import org.apache.yoko.orb.OCI.GiopVersion;
+import org.apache.yoko.orb.util.AutoLock;
+import org.apache.yoko.orb.util.AutoReadWriteLock;
+import org.omg.CORBA.CompletionStatus;
+import org.omg.CORBA.NO_RESPONSE;
 import org.omg.IOP.ServiceContext;
 
 public class Downcall {
@@ -78,25 +84,13 @@
     //
     // The state of this invocation
     //
-    protected static final int DowncallStateUnsent = 0;
-
-    protected static final int DowncallStatePending = 1;
-
-    protected static final int DowncallStateNoException = 2;
-
-    protected static final int DowncallStateUserException = 3;
-
-    protected static final int DowncallStateSystemException = 4;
-
-    protected static final int DowncallStateFailureException = 5;
-
-    protected static final int DowncallStateForward = 6;
-
-    protected static final int DowncallStateForwardPerm = 7;
-
-    protected int state_;
-
-    protected java.lang.Object stateMonitor_;
+    protected enum State { UNSENT, PENDING, NO_EXCEPTION, USER_EXCEPTION, SYSTEM_EXCEPTION, FAILURE_EXCEPTION, FORWARD, FORWARD_PERM }
+    
+    protected final AutoReadWriteLock stateLock = new AutoReadWriteLock();
+    
+    protected State state = State.UNSENT;
+    
+    protected Condition stateWaitCondition;
 
     //
     // Holds the exception if state_ is DowncallStateUserException,
@@ -133,36 +127,38 @@
     // Raise an exception if necessary
     //
     void checkForException() throws LocationForward, FailureException {
-        switch (state_) {
-        case DowncallStateUserException:
-            //
-            // Do not raise UserException in Java
-            //
-            // if(ex_ != null) // Only raise if a user exception has been set
-            // throw ex_;
-            break;
+        try (AutoLock readLock = stateLock.getReadLock()) {
+            switch (state) {
+                case USER_EXCEPTION:
+                    //
+                    // Do not raise UserException in Java
+                    //
+                    // if(ex_ != null) // Only raise if a user exception has been set
+                    // throw ex_;
+                    break;
 
-        case DowncallStateSystemException:
-            Assert._OB_assert(ex_ != null);
-            // update the stack trace to have the caller's stack rather than the 
-            // receiver thread. 
-            ex_.fillInStackTrace();    
-            throw (org.omg.CORBA.SystemException) ex_;
+                case SYSTEM_EXCEPTION:
+                    Assert._OB_assert(ex_ != null);
+                    // update the stack trace to have the caller's stack rather than the 
+                    // receiver thread. 
+                    ex_.fillInStackTrace();    
+                    throw (org.omg.CORBA.SystemException) ex_;
 
-        case DowncallStateFailureException:
-            Assert._OB_assert(ex_ != null);
-            throw new FailureException((org.omg.CORBA.SystemException) ex_);
+                case FAILURE_EXCEPTION:
+                    Assert._OB_assert(ex_ != null);
+                    throw new FailureException((org.omg.CORBA.SystemException) ex_);
 
-        case DowncallStateForward:
-            Assert._OB_assert(forwardIOR_ != null);
-            throw new LocationForward(forwardIOR_, false);
+                case FORWARD:
+                    Assert._OB_assert(forwardIOR_ != null);
+                    throw new LocationForward(forwardIOR_, false);
 
-        case DowncallStateForwardPerm:
-            Assert._OB_assert(forwardIOR_ != null);
-            throw new LocationForward(forwardIOR_, true);
+                case FORWARD_PERM:
+                    Assert._OB_assert(forwardIOR_ != null);
+                    throw new LocationForward(forwardIOR_, true);
 
-        default:
-            break;
+                default:
+                    break;
+            }
         }
     }
 
@@ -194,8 +190,10 @@
         policies_ = policies;
         op_ = op;
         responseExpected_ = resp;
-        state_ = DowncallStateUnsent;
-        stateMonitor_ = null;
+        // since this.state is not volatile we must use a lock to guarantee consistency
+        try (AutoLock writeLock = stateLock.getWriteLock()) {
+            state = State.UNSENT;
+        }
         ex_ = null;
 
         //
@@ -206,58 +204,58 @@
         logger_.debug("Downcall created for operation " + op + " with id " + reqId_); 
     }
 
-    public ORBInstance orbInstance() {
+    public final ORBInstance orbInstance() {
         return orbInstance_;
     }
 
-    public Client client() {
+    public final Client client() {
         return client_;
     }
 
-    public org.apache.yoko.orb.OCI.ProfileInfo profileInfo() {
+    public final org.apache.yoko.orb.OCI.ProfileInfo profileInfo() {
         return profileInfo_;
     }
 
-    public RefCountPolicyList policies() {
+    public final RefCountPolicyList policies() {
         return policies_;
     }
 
-    public Exception excep() {
+    public final Exception excep() {
         return ex_;
     }
 
-    public int requestId() {
+    public final int requestId() {
         return reqId_;
     }
 
-    public String operation() {
+    public final String operation() {
         return op_;
     }
 
-    public boolean responseExpected() {
+    public final boolean responseExpected() {
         return responseExpected_;
     }
 
-    public org.apache.yoko.orb.CORBA.OutputStream output() {
+    public final org.apache.yoko.orb.CORBA.OutputStream output() {
         return out_;
     }
 
-    public org.apache.yoko.orb.CORBA.InputStream input() {
+    public final org.apache.yoko.orb.CORBA.InputStream input() {
         return in_;
     }
 
-    public org.omg.IOP.ServiceContext[] getRequestSCL() {
+    public final org.omg.IOP.ServiceContext[] getRequestSCL() {
         org.omg.IOP.ServiceContext[] scl = new org.omg.IOP.ServiceContext[requestSCL_
                 .size()];
         requestSCL_.copyInto(scl);
         return scl;
     }
 
-    public void addToRequestSCL(org.omg.IOP.ServiceContext sc) {
+    public final void addToRequestSCL(org.omg.IOP.ServiceContext sc) {
         requestSCL_.addElement(sc);
     }
 
-    public void setReplySCL(org.omg.IOP.ServiceContext[] scl) {
+    public final void setReplySCL(org.omg.IOP.ServiceContext[] scl) {
         // Don't create a new Vector
         Assert._OB_assert(replySCL_.size() == 0);
         replySCL_.setSize(scl.length);
@@ -270,17 +268,17 @@
         return preMarshalBase();
     }
 
-    public void marshalEx(org.omg.CORBA.SystemException ex)
+    public final void marshalEx(org.omg.CORBA.SystemException ex)
             throws LocationForward, FailureException {
         setFailureException(ex);
         checkForException();
         Assert._OB_assert(false);
     }
 
-    public void postMarshal() throws LocationForward, FailureException {
+    public final void postMarshal() throws LocationForward, FailureException {
     }
 
-    public void locate() throws LocationForward, FailureException {
+    public final void locate() throws LocationForward, FailureException {
         Assert._OB_assert(responseExpected_);
         Assert._OB_assert(op_.equals("_locate"));
 
@@ -302,7 +300,7 @@
         checkForException();
     }
 
-    public void request() throws LocationForward, FailureException {
+    public final void request() throws LocationForward, FailureException {
         Assert._OB_assert(responseExpected_);
 
         //
@@ -322,7 +320,7 @@
         checkForException();
     }
 
-    public void oneway() throws LocationForward, FailureException {
+    public final void oneway() throws LocationForward, FailureException {
         Assert._OB_assert(!responseExpected_);
 
         if (policies_.syncScope == org.omg.Messaging.SYNC_WITH_TRANSPORT.value) {
@@ -336,7 +334,7 @@
         }
     }
 
-    public void deferred() throws LocationForward, FailureException {
+    public final void deferred() throws LocationForward, FailureException {
         Assert._OB_assert(responseExpected_);
 
         boolean finished = emitter_.send(this, true);
@@ -344,7 +342,7 @@
             checkForException();
     }
 
-    public void response() throws LocationForward, FailureException {
+    public final void response() throws LocationForward, FailureException {
         Assert._OB_assert(responseExpected_);
 
         boolean finished = emitter_.receive(this, true);
@@ -352,23 +350,25 @@
         checkForException();
     }
 
-    public boolean poll() throws LocationForward, FailureException {
+    public final boolean poll() throws LocationForward, FailureException {
         Assert._OB_assert(responseExpected_);
 
         boolean finished = emitter_.receive(this, false);
         if (finished) {
-            checkForException();
-            return state_ != DowncallStatePending;
+            try (AutoLock lock = stateLock.getReadLock()) {
+                checkForException();
+                return state != State.PENDING;
+            }
         } else
             return false;
     }
 
-    public org.apache.yoko.orb.CORBA.InputStream preUnmarshal()
+    public final org.apache.yoko.orb.CORBA.InputStream preUnmarshal()
             throws LocationForward, FailureException {
         return in_;
     }
 
-    public void unmarshalEx(org.omg.CORBA.SystemException ex)
+    public final void unmarshalEx(org.omg.CORBA.SystemException ex)
             throws LocationForward, FailureException {
         setFailureException(ex);
         checkForException();
@@ -385,289 +385,212 @@
         // the user exception, so we only want to raise UNKNOWN if we
         // don't have the ID
         //
-        if (state_ == DowncallStateUserException && ex_ == null
-                && exId_ == null)
-            setSystemException(new org.omg.CORBA.UNKNOWN(org.apache.yoko.orb.OB.MinorCodes
-                    .describeUnknown(org.apache.yoko.orb.OB.MinorCodes.MinorUnknownUserException),
-                    org.apache.yoko.orb.OB.MinorCodes.MinorUnknownUserException,
-                    org.omg.CORBA.CompletionStatus.COMPLETED_YES));
-
-        checkForException();
+        try (AutoLock lock = stateLock.getReadLock()) {
+            if (state == State.USER_EXCEPTION && ex_ == null && exId_ == null)
+                setSystemException(new org.omg.CORBA.UNKNOWN(org.apache.yoko.orb.OB.MinorCodes
+                        .describeUnknown(org.apache.yoko.orb.OB.MinorCodes.MinorUnknownUserException),
+                        org.apache.yoko.orb.OB.MinorCodes.MinorUnknownUserException,
+                        org.omg.CORBA.CompletionStatus.COMPLETED_YES));
+            checkForException();
+        }
     }
 
-    public String unmarshalExceptionId() {
-        Assert._OB_assert(state_ == DowncallStateUserException);
-        int pos = in_._OB_pos();
-        String id = in_.read_string();
-        in_._OB_pos(pos);
-        return id;
+    public final String unmarshalExceptionId() {
+        try (AutoLock lock = stateLock.getReadLock()) {
+            Assert._OB_assert(state == State.USER_EXCEPTION);
+            int pos = in_._OB_pos();
+            String id = in_.read_string();
+            in_._OB_pos(pos);
+            return id;
+        }
     }
 
-    public boolean unsent() {
-        return state_ == DowncallStateUnsent;
+    public final boolean unsent() {
+        try (AutoLock lock = stateLock.getReadLock()) {
+            return state == State.UNSENT;
+        }
     }
 
-    public boolean pending() {
-        return state_ == DowncallStatePending;
+    public final boolean pending() {
+        try (AutoLock lock = stateLock.getReadLock()) {
+            return state == State.PENDING;
+        }
     }
 
-    public boolean noException() {
-        return state_ == DowncallStateNoException;
+    public final boolean noException() {
+        try (AutoLock lock = stateLock.getReadLock()) {
+            return state == State.NO_EXCEPTION;
+        }
     }
 
-    public boolean userException() {
-        return state_ == DowncallStateUserException;
+    public final boolean userException() {
+        try (AutoLock lock = stateLock.getReadLock()) {
+            return state == State.USER_EXCEPTION;
+        }
     }
 
-    public boolean failureException() {
-        return state_ == DowncallStateFailureException;
+    public final boolean failureException() {
+        try (AutoLock lock = stateLock.getReadLock()) {
+            return state == State.FAILURE_EXCEPTION;
+        }
     }
 
-    public boolean systemException() {
-        return state_ == DowncallStateSystemException;
+    public final boolean systemException() {
+        try (AutoLock lock = stateLock.getReadLock()) {
+            return state == State.SYSTEM_EXCEPTION;
+        }
     }
 
-    private void setPendingImpl() {
-        Assert._OB_assert(responseExpected_);
-        state_ = DowncallStatePending;
-    }
-
-    public void setPending() {
-        if (stateMonitor_ != null) {
-            synchronized (stateMonitor_) {
-                setPendingImpl();
-                stateMonitor_.notify();
-            }
-        } else
-            setPendingImpl();
-    }
-
-    private void setNoExceptionImpl(org.apache.yoko.orb.CORBA.InputStream in) {
-        state_ = DowncallStateNoException;
-        if (in == null) {
-            Assert._OB_assert(!responseExpected_);
-        } else {
+    public final void setPending() {
+        try (AutoLock lock = stateLock.getWriteLock()) {
             Assert._OB_assert(responseExpected_);
+            state = State.PENDING;
+            if (null != stateWaitCondition) stateWaitCondition.signalAll();
+        }
+    }
+
+    public final void setNoException(org.apache.yoko.orb.CORBA.InputStream in) {
+        try (AutoLock lock = stateLock.getWriteLock()) {
+            state = State.NO_EXCEPTION;
+            if (in == null) {
+                Assert._OB_assert(!responseExpected_);
+            } else {
+                Assert._OB_assert(responseExpected_);
+                in_ = in;
+                in_._OB_ORBInstance(orbInstance_);
+                CodeConverters codeConverters = client_.codeConverters();
+                in_._OB_codeConverters(codeConverters, GiopVersion.get(profileInfo_.major, profileInfo_.minor));
+            }
+            if (null != stateWaitCondition) stateWaitCondition.signalAll();
+        }
+    }
+
+    public final void setUserException(org.apache.yoko.orb.CORBA.InputStream in) {
+        try (AutoLock lock = stateLock.getWriteLock()) {
+            Assert._OB_assert(in != null);
+            Assert._OB_assert(responseExpected_);
+            state = State.USER_EXCEPTION;
             in_ = in;
             in_._OB_ORBInstance(orbInstance_);
             CodeConverters codeConverters = client_.codeConverters();
             in_._OB_codeConverters(codeConverters, GiopVersion.get(profileInfo_.major, profileInfo_.minor));
+            if (null != stateWaitCondition) stateWaitCondition.signalAll();
         }
     }
 
-    public void setNoException(org.apache.yoko.orb.CORBA.InputStream in) {
-        if (stateMonitor_ != null) {
-            synchronized (stateMonitor_) {
-                setNoExceptionImpl(in);
-                stateMonitor_.notify();
-            }
-        } else
-            setNoExceptionImpl(in);
-    }
-
-    private void setUserExceptionImpl(org.apache.yoko.orb.CORBA.InputStream in) {
-        Assert._OB_assert(in != null);
-        Assert._OB_assert(responseExpected_);
-        state_ = DowncallStateUserException;
-        in_ = in;
-        in_._OB_ORBInstance(orbInstance_);
-        CodeConverters codeConverters = client_.codeConverters();
-        in_._OB_codeConverters(codeConverters, GiopVersion.get(profileInfo_.major, profileInfo_.minor));
-    }
-
-    public void setUserException(org.apache.yoko.orb.CORBA.InputStream in) {
-        if (stateMonitor_ != null) {
-            synchronized (stateMonitor_) {
-                setUserExceptionImpl(in);
-                stateMonitor_.notify();
-            }
-        } else
-            setUserExceptionImpl(in);
-    }
-
-    private void setUserExceptionImpl(org.omg.CORBA.UserException ex,
-            String exId) {
-        Assert._OB_assert(responseExpected_);
-        Assert._OB_assert(ex_ == null);
-        state_ = DowncallStateUserException;
-        ex_ = ex;
-    }
-
     public void setUserException(org.omg.CORBA.UserException ex, String exId) {
-        if (stateMonitor_ != null) {
-            synchronized (stateMonitor_) {
-                setUserExceptionImpl(ex, exId);
-                stateMonitor_.notify();
-            }
-        } else
-            setUserExceptionImpl(ex, exId);
+        try (AutoLock lock = stateLock.getWriteLock()) {
+            Assert._OB_assert(responseExpected_);
+            Assert._OB_assert(ex_ == null);
+            state = State.USER_EXCEPTION;
+            ex_ = ex;
+            if (null != stateWaitCondition) stateWaitCondition.signalAll();
+        }
     }
 
-    private void setUserExceptionImpl(org.omg.CORBA.UserException ex) {
+    public final void setUserException(org.omg.CORBA.UserException ex) {
+        try (AutoLock lock = stateLock.getWriteLock()) {
+            Assert._OB_assert(responseExpected_);
+            Assert._OB_assert(ex_ == null);
+            state = State.USER_EXCEPTION;
+            ex_ = ex;
+            if (null != stateWaitCondition) stateWaitCondition.signalAll();
+        }
+    }
+
+    public final void setUserException(String exId) {
+        try (AutoLock lock = stateLock.getWriteLock()) {
+            Assert._OB_assert(responseExpected_);
+            Assert._OB_assert(ex_ == null);
+            state = State.USER_EXCEPTION;
+            exId_ = exId;
+            logger_.debug("Received user exception " + exId);
+            if (null != stateWaitCondition) stateWaitCondition.signalAll();
+        }
+    }
+
+    public final void setSystemException(org.omg.CORBA.SystemException ex) {
+        try (AutoLock lock = stateLock.getWriteLock()) {
+            Assert._OB_assert(responseExpected_);
+            Assert._OB_assert(ex_ == null);
+            state = State.SYSTEM_EXCEPTION;
+            ex_ = ex;
+            logger_.debug("Received system exception", ex);
+            if (null != stateWaitCondition) stateWaitCondition.signalAll();
+        }
+    }
+
+    public final void setFailureException(org.omg.CORBA.SystemException ex) {
+        try (AutoLock lock = stateLock.getWriteLock()) {
+            Assert._OB_assert(ex_ == null);
+            state = State.FAILURE_EXCEPTION;
+            ex_ = ex;
+            logger_.debug("Received failure exception", ex);
+            if (null != stateWaitCondition) stateWaitCondition.signalAll();
+        }
+    }
+
+    public final void setLocationForward(org.omg.IOP.IOR ior, boolean perm) {
+        try (AutoLock lock = stateLock.getWriteLock()) {
+            Assert._OB_assert(responseExpected_);
+            Assert._OB_assert(forwardIOR_ == null);
+            state = perm ? State.FORWARD_PERM : State.FORWARD;
+            forwardIOR_ = ior;
+            if (null != stateWaitCondition) stateWaitCondition.signalAll();
+        }
         Assert._OB_assert(responseExpected_);
-        Assert._OB_assert(ex_ == null);
-        state_ = DowncallStateUserException;
-        ex_ = ex;
-    }
-
-    public void setUserException(org.omg.CORBA.UserException ex) {
-        if (stateMonitor_ != null) {
-            synchronized (stateMonitor_) {
-                setUserExceptionImpl(ex);
-                stateMonitor_.notify();
-            }
-        } else
-            setUserExceptionImpl(ex);
     }
 
     //
-    // Java only
-    //
-    // Required for portable stubs, which do not make the UserException
-    // instance available to the ORB
-    //
-    private void setUserExceptionImpl(String exId) {
-        Assert._OB_assert(responseExpected_);
-        Assert._OB_assert(ex_ == null);
-        state_ = DowncallStateUserException;
-        exId_ = exId;
-        logger_.debug("Received user exception " + exId);
-    }
-
-    public void setUserException(String exId) {
-        if (stateMonitor_ != null) {
-            synchronized (stateMonitor_) {
-                setUserExceptionImpl(exId);
-                stateMonitor_.notify();
-            }
-        } else
-            setUserExceptionImpl(exId);
-    }
-
-    private void setSystemExceptionImpl(org.omg.CORBA.SystemException ex) {
-        Assert._OB_assert(responseExpected_);
-        Assert._OB_assert(ex_ == null);
-        state_ = DowncallStateSystemException;
-        ex_ = ex;
-        logger_.debug("Received system exception", ex);
-    }
-
-    public void setSystemException(org.omg.CORBA.SystemException ex) {
-        if (stateMonitor_ != null) {
-            synchronized (stateMonitor_) {
-                setSystemExceptionImpl(ex);
-                stateMonitor_.notify();
-            }
-        } else
-            setSystemExceptionImpl(ex);
-    }
-
-    private void setFailureExceptionImpl(org.omg.CORBA.SystemException ex) {
-        Assert._OB_assert(ex_ == null);
-        state_ = DowncallStateFailureException;
-        ex_ = ex;
-        logger_.debug("Received failure exception", ex);
-    }
-
-    public void setFailureException(org.omg.CORBA.SystemException ex) {
-        if (stateMonitor_ != null) {
-            synchronized (stateMonitor_) {
-                setFailureExceptionImpl(ex);
-                stateMonitor_.notify();
-            }
-        } else
-            setFailureExceptionImpl(ex);
-    }
-
-    private void setLocationForwardImpl(org.omg.IOP.IOR ior, boolean perm) {
-        Assert._OB_assert(responseExpected_);
-        Assert._OB_assert(forwardIOR_ == null);
-        if (perm)
-            state_ = DowncallStateForwardPerm;
-        else
-            state_ = DowncallStateForward;
-        forwardIOR_ = ior;
-    }
-
-    public void setLocationForward(org.omg.IOP.IOR ior, boolean perm) {
-        if (stateMonitor_ != null) {
-            synchronized (stateMonitor_) {
-                setLocationForwardImpl(ior, perm);
-                stateMonitor_.notify();
-            }
-        } else
-            setLocationForwardImpl(ior, perm);
-    }
-
-    //
-    // Initialize the state monitor. This operation must be called in
+    // Initialize the wait condition. This operation must be called in
     // order to be able to use one of the waitUntil...() operations
     // below
     //
-    public void initStateMonitor() {
-        Assert._OB_assert(stateMonitor_ == null);
-        stateMonitor_ = new java.lang.Object();
+    public final void allowWaiting() {
+        try (AutoLock lock = stateLock.getWriteLock()) {
+            Assert._OB_assert(stateWaitCondition == null);
+            stateWaitCondition = lock.newCondition();
+        }
     }
 
-    //
-    // This operation try waits for a specific state, using the
-    // timeout from this downcall's policies. If the timeout expires,
-    // a NO_RESPONSE exception is raised.
-    //
-    // - If the first parameter is set to false, the operation returns
-    // immediately with false if the desired state cannot be
-    // reached.
-    //
-    // - If the return value is true, it's safe to access or modify
-    // the downcall object. If the return value if false, accessing
-    // or modifying the downcall object is not allowed, for thread
-    // safety reasons. (Because the downcall object is not thread
-    // safe.)
-    //
-    public boolean waitUntilCompleted(boolean block) {
+    /**
+     * This operation try waits for a completed state, using the
+     * timeout from this downcall's policies.
+     *
+     * @param block whether to wait for the call to complete
+     * @return true if the call has completed
+     * @throws NO_RESPONSE if a timeout was set and has elapsed
+     */
+    public final boolean waitUntilCompleted(boolean block) {
         //
         // Get the timeout
         //
         int t = policies_.requestTimeout;
 
         //
-        // Yield if non-blocking or blocking with zero timeout
-        //
-        if (!block || (block && t == 0)) {
-            Thread.yield();
-        }
-
-        //
         // Wait for the desired state, taking the timeout and blocking
         // flag into account
         //
-        Assert._OB_assert(stateMonitor_ != null);
-        synchronized (stateMonitor_) {
-            while (state_ == DowncallStateUnsent
-                    || state_ == DowncallStatePending) {
-                if (!block) {
-                    return false;
-                }
+        try (AutoLock lock = stateLock.getWriteLock()) {
+            Assert._OB_assert(stateWaitCondition != null);
+            while (state == State.UNSENT || state == State.PENDING) {
+                if (!block) return false;
 
                 try {
-                    if (t < 0) {
-                        stateMonitor_.wait();
+                    if (t <= 0) {
+                        stateWaitCondition.await();
                     } else {
-                        int oldState = state_;
+                        State oldState = state;
 
-                        stateMonitor_.wait(t);
+                        stateWaitCondition.await(t, TimeUnit.MILLISECONDS);
 
-                        if (state_ == oldState) {
-                            throw new org.omg.CORBA.NO_RESPONSE(
-                                    "Timeout during receive",
-                                    0,
-                                    org.omg.CORBA.CompletionStatus.COMPLETED_MAYBE);
+                        if (state == oldState) {
+                            throw new NO_RESPONSE("Timeout during receive", 0, CompletionStatus.COMPLETED_MAYBE);
                         }
                     }
                 } catch (InterruptedException ex) {
                 }
             }
-
             //
             // The downcall has completed
             //
diff --git a/yoko-core/src/main/java/org/apache/yoko/orb/OB/GIOPConnectionThreaded.java b/yoko-core/src/main/java/org/apache/yoko/orb/OB/GIOPConnectionThreaded.java
index 269edf5..bc250df 100644
--- a/yoko-core/src/main/java/org/apache/yoko/orb/OB/GIOPConnectionThreaded.java
+++ b/yoko-core/src/main/java/org/apache/yoko/orb/OB/GIOPConnectionThreaded.java
@@ -539,7 +539,7 @@
             // make the downcall thread-safe
             //
             if (down.responseExpected()) {
-                down.initStateMonitor();
+                down.allowWaiting();
             }
 
             // 
diff --git a/yoko-core/src/main/java/org/apache/yoko/orb/OB/PIDowncall.java b/yoko-core/src/main/java/org/apache/yoko/orb/OB/PIDowncall.java
index ac094c5..6aa738b 100644
--- a/yoko-core/src/main/java/org/apache/yoko/orb/OB/PIDowncall.java
+++ b/yoko-core/src/main/java/org/apache/yoko/orb/OB/PIDowncall.java
@@ -17,6 +17,8 @@
 
 package org.apache.yoko.orb.OB;
 
+import org.apache.yoko.orb.util.AutoLock;
+
 public class PIDowncall extends Downcall {
     //
     // The IOR and the original IOR
@@ -48,74 +50,76 @@
     // ----------------------------------------------------------------------
 
     void checkForException() throws LocationForward, FailureException {
-        //
-        // If ex_ is set, but exId_ is not, then set it now
-        //
-        // TODO: Postpone this in Java?
-        //
-        if (ex_ != null && exId_ == null)
-            exId_ = Util.getExceptionId(ex_);
+        try (AutoLock lock = stateLock.getReadLock()) {
+            //
+            // If ex_ is set, but exId_ is not, then set it now
+            //
+            // TODO: Postpone this in Java?
+            //
+            if (ex_ != null && exId_ == null)
+                exId_ = Util.getExceptionId(ex_);
 
-        switch (state_) {
-        case DowncallStateUserException:
-            //
-            // For Java portable stubs, we'll have the repository ID
-            // but not the exception instance, so we pass UNKNOWN to
-            // the interceptors but DO NOT modify the Downcall state.
-            //
-            if (ex_ == null && exId_ != null) {
-                org.omg.CORBA.Any any = new org.apache.yoko.orb.CORBA.Any(
-                        orbInstance_);
-                org.omg.CORBA.UNKNOWN sys = new org.omg.CORBA.UNKNOWN(
-                        MinorCodes
+            switch (state) {
+                case USER_EXCEPTION:
+                    //
+                    // For Java portable stubs, we'll have the repository ID
+                    // but not the exception instance, so we pass UNKNOWN to
+                    // the interceptors but DO NOT modify the Downcall state.
+                    //
+                    if (ex_ == null && exId_ != null) {
+                        org.omg.CORBA.Any any = new org.apache.yoko.orb.CORBA.Any(
+                                orbInstance_);
+                        org.omg.CORBA.UNKNOWN sys = new org.omg.CORBA.UNKNOWN(
+                                MinorCodes
                                 .describeUnknown(org.apache.yoko.orb.OB.MinorCodes.MinorUnknownUserException)
                                 + ": " + exId_,
-                        org.apache.yoko.orb.OB.MinorCodes.MinorUnknownUserException,
-                        org.omg.CORBA.CompletionStatus.COMPLETED_YES);
-                org.omg.CORBA.UNKNOWNHelper.insert(any, sys);
-                org.omg.CORBA.UnknownUserException unk = new org.omg.CORBA.UnknownUserException(
-                        any);
-                piManager_.clientReceiveException(requestInfo_, false, unk,
-                        exId_);
+                                org.apache.yoko.orb.OB.MinorCodes.MinorUnknownUserException,
+                                org.omg.CORBA.CompletionStatus.COMPLETED_YES);
+                        org.omg.CORBA.UNKNOWNHelper.insert(any, sys);
+                        org.omg.CORBA.UnknownUserException unk = new org.omg.CORBA.UnknownUserException(
+                                any);
+                        piManager_.clientReceiveException(requestInfo_, false, unk,
+                                exId_);
+                    }
+                    //
+                    // Only invoke interceptor if a user exception has been
+                    // set
+                    //
+                    if (ex_ != null)
+                        piManager_.clientReceiveException(requestInfo_, false, ex_,
+                                exId_);
+                    break;
+
+                case SYSTEM_EXCEPTION:
+                    Assert._OB_assert(ex_ != null);
+                    piManager_.clientReceiveException(requestInfo_, true, ex_, exId_);
+                    break;
+
+                case FAILURE_EXCEPTION:
+                    try {
+                        Assert._OB_assert(ex_ != null);
+                        piManager_.clientReceiveException(requestInfo_, true, ex_,
+                                exId_);
+                    } catch (org.omg.CORBA.SystemException ex) {
+                        //
+                        // Ignore any exception translations for failure
+                        // exceptions
+                        //
+                    }
+                    break;
+
+                case FORWARD:
+                case FORWARD_PERM:
+                    Assert._OB_assert(forwardIOR_ != null);
+                    piManager_.clientReceiveLocationForward(requestInfo_, forwardIOR_);
+                    break;
+
+                default:
+                    break;
             }
-            //
-            // Only invoke interceptor if a user exception has been
-            // set
-            //
-            if (ex_ != null)
-                piManager_.clientReceiveException(requestInfo_, false, ex_,
-                        exId_);
-            break;
 
-        case DowncallStateSystemException:
-            Assert._OB_assert(ex_ != null);
-            piManager_.clientReceiveException(requestInfo_, true, ex_, exId_);
-            break;
-
-        case DowncallStateFailureException:
-            try {
-                Assert._OB_assert(ex_ != null);
-                piManager_.clientReceiveException(requestInfo_, true, ex_,
-                        exId_);
-            } catch (org.omg.CORBA.SystemException ex) {
-                //
-                // Ignore any exception translations for failure
-                // exceptions
-                //
-            }
-            break;
-
-        case DowncallStateForward:
-        case DowncallStateForwardPerm:
-            Assert._OB_assert(forwardIOR_ != null);
-            piManager_.clientReceiveLocationForward(requestInfo_, forwardIOR_);
-            break;
-
-        default:
-            break;
+            super.checkForException();
         }
-
-        super.checkForException();
     }
 
     // ----------------------------------------------------------------------
@@ -143,52 +147,45 @@
     }
 
     public void postUnmarshal() throws LocationForward, FailureException {
-        //
-        // If the result of this downcall is a user exception, but no user
-        // exception could be unmarshalled, then use the system exception
-        // UNKNOWN, but keep the original exception ID
-        //
-        // In Java, the portable stubs only provide the repository ID of
-        // the user exception, not the exception instance. We want to
-        // report UNKNOWN to the interceptors, but do not want to change
-        // the downcall status if we have the repository ID.
-        //
-        if (state_ == DowncallStateUserException && ex_ == null
-                && exId_ == null) {
-            String id = unmarshalExceptionId();
-            setSystemException(new org.omg.CORBA.UNKNOWN(org.apache.yoko.orb.OB.MinorCodes
-                    .describeUnknown(org.apache.yoko.orb.OB.MinorCodes.MinorUnknownUserException)
-                    + ": " + id, org.apache.yoko.orb.OB.MinorCodes.MinorUnknownUserException,
-                    org.omg.CORBA.CompletionStatus.COMPLETED_YES));
-            exId_ = id;
+        try (AutoLock lock = stateLock.getReadLock()) {
+            //
+            // If the result of this downcall is a user exception, but no user
+            // exception could be unmarshalled, then use the system exception
+            // UNKNOWN, but keep the original exception ID
+            //
+            // In Java, the portable stubs only provide the repository ID of
+            // the user exception, not the exception instance. We want to
+            // report UNKNOWN to the interceptors, but do not want to change
+            // the downcall status if we have the repository ID.
+            //
+            if (state == State.USER_EXCEPTION && ex_ == null && exId_ == null) {
+                String id = unmarshalExceptionId();
+                setSystemException(new org.omg.CORBA.UNKNOWN(org.apache.yoko.orb.OB.MinorCodes
+                        .describeUnknown(org.apache.yoko.orb.OB.MinorCodes.MinorUnknownUserException)
+                        + ": " + id, org.apache.yoko.orb.OB.MinorCodes.MinorUnknownUserException,
+                        org.omg.CORBA.CompletionStatus.COMPLETED_YES));
+                exId_ = id;
+            }
+    
+            super.postUnmarshal();
+    
+            //
+            // Java only - Downcall.checkForException() does not raise
+            // UserExceptions, so we return now and let the stub handle it
+            //
+            if (state == State.USER_EXCEPTION)
+                return;
+    
+            if (responseExpected_)
+                Assert._OB_assert(state == State.NO_EXCEPTION);
+            else
+                Assert._OB_assert(state == State.UNSENT || state == State.NO_EXCEPTION);
+            piManager_.clientReceiveReply(requestInfo_);
         }
-
-        super.postUnmarshal();
-
-        //
-        // Java only - Downcall.checkForException() does not raise
-        // UserExceptions, so we return now and let the stub handle it
-        //
-        if (state_ == DowncallStateUserException)
-            return;
-
-        if (responseExpected_)
-            Assert._OB_assert(state_ == DowncallStateNoException);
-        else
-            Assert._OB_assert(state_ == DowncallStateUnsent
-                    || state_ == DowncallStateNoException);
-        piManager_.clientReceiveReply(requestInfo_);
     }
 
     public void setUserException(org.omg.CORBA.UserException ex, String exId) {
         super.setUserException(ex, exId);
         exId_ = exId;
     }
-
-    public void setUserException(org.omg.CORBA.UserException ex) {
-        Assert._OB_assert(responseExpected_);
-        Assert._OB_assert(ex_ == null);
-        state_ = DowncallStateUserException;
-        ex_ = ex;
-    }
 }
diff --git a/yoko-core/src/main/java/org/apache/yoko/orb/OBCORBA/AutoReadWriteLock.java b/yoko-core/src/main/java/org/apache/yoko/orb/OBCORBA/AutoReadWriteLock.java
deleted file mode 100644
index 85ae80f..0000000
--- a/yoko-core/src/main/java/org/apache/yoko/orb/OBCORBA/AutoReadWriteLock.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package org.apache.yoko.orb.OBCORBA;
-
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-
-class AutoLock implements AutoCloseable{
-    final AtomicReference<Lock> lockRef;
-    
-    AutoLock(Lock lock) {
-        lockRef = new AtomicReference<>(lock);
-        lock.lock();
-    }
-    
-    @Override
-    public void close() {
-        Lock lock = lockRef.getAndSet(null);
-        if (lock != null) lock.unlock();
-    }
-}
-
-class AutoReadWriteLock {
-    private final ReadWriteLock lock = new ReentrantReadWriteLock();
-    
-    public AutoLock getReadLock() {
-        return new AutoLock(lock.readLock());
-    }
-
-    public AutoLock getWriteLock() {
-        return new AutoLock(lock.writeLock());
-    }
-}
\ No newline at end of file
diff --git a/yoko-core/src/main/java/org/apache/yoko/orb/OBCORBA/ORB_impl.java b/yoko-core/src/main/java/org/apache/yoko/orb/OBCORBA/ORB_impl.java
index c16544b..766f9aa 100644
--- a/yoko-core/src/main/java/org/apache/yoko/orb/OBCORBA/ORB_impl.java
+++ b/yoko-core/src/main/java/org/apache/yoko/orb/OBCORBA/ORB_impl.java
@@ -25,6 +25,8 @@
 import org.apache.yoko.orb.cmsf.CmsfClientInterceptor;
 import org.apache.yoko.orb.cmsf.CmsfIORInterceptor;
 import org.apache.yoko.orb.cmsf.CmsfServerInterceptor;
+import org.apache.yoko.orb.util.AutoLock;
+import org.apache.yoko.orb.util.AutoReadWriteLock;
 import org.apache.yoko.orb.util.GetSystemPropertyAction;
 import org.apache.yoko.util.osgi.ProviderLocator;
 import org.omg.CORBA.OBJECT_NOT_EXIST;
diff --git a/yoko-core/src/main/java/org/apache/yoko/orb/util/AutoLock.java b/yoko-core/src/main/java/org/apache/yoko/orb/util/AutoLock.java
new file mode 100644
index 0000000..c9dea93
--- /dev/null
+++ b/yoko-core/src/main/java/org/apache/yoko/orb/util/AutoLock.java
@@ -0,0 +1,24 @@
+package org.apache.yoko.orb.util;
+
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+
+public class AutoLock implements AutoCloseable{
+    final AtomicReference<Lock> lockRef;
+    
+    AutoLock(Lock lock) {
+        lockRef = new AtomicReference<>(lock);
+        lock.lock();
+    }
+    
+    @Override
+    public void close() {
+        Lock lock = lockRef.getAndSet(null);
+        if (lock != null) lock.unlock();
+    }
+
+    public Condition newCondition() {
+        return lockRef.get().newCondition();
+    }
+}
\ No newline at end of file
diff --git a/yoko-core/src/main/java/org/apache/yoko/orb/util/AutoReadWriteLock.java b/yoko-core/src/main/java/org/apache/yoko/orb/util/AutoReadWriteLock.java
new file mode 100644
index 0000000..f70c0a3
--- /dev/null
+++ b/yoko-core/src/main/java/org/apache/yoko/orb/util/AutoReadWriteLock.java
@@ -0,0 +1,16 @@
+package org.apache.yoko.orb.util;
+
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+public class AutoReadWriteLock {
+    private final ReadWriteLock lock = new ReentrantReadWriteLock();
+    
+    public AutoLock getReadLock() {
+        return new AutoLock(lock.readLock());
+    }
+
+    public AutoLock getWriteLock() {
+        return new AutoLock(lock.writeLock());
+    }
+}
\ No newline at end of file