Control.java

/*
** Module   : Control.java
** Abstract : tracks control flow between client and server
**
** Copyright (c) 2006-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ------------------Description-------------------
** 001 NVS 20060208   @24362 Initial version.
** 002 NVS 20060209   @24466 Added delayed interruption processing.
** 003 NVS 20060210   @24570 Added server side processing.
** 004 NVS 20060213   @24571 Added unconditional push and pop operations.
** 005 NVS 20060220   @24620 Protected against premature calls.
** 006 NVS 20060222   @24624 Protected against duplicate exceptions.
** 007 GES 20060727   @28207 Performance enhancements. This code is
**                           called for every synchronous message send
**                           and receive so it needs to be very thin!
** 008 GES 20061019   @30537 Major rewrite to simplify and fix many
**                           issues.  Synchronization was fixed (it was
**                           mixed use between static and non-static
**                           "synchronized" methods, which is wrong
**                           because statics use the Class object's 
**                           monitor and instances use the instance
**                           object's monitor.  Removed the message ID
**                           usage and the overcomplicated implementation
**                           where message IDs and thread objects were
**                           mixed in the stack.  Removed some unneeded
**                           methods. Rewrote interrupt processing to
**                           operate differently based on the success
**                           or failure of any remote interrupt 
**                           notification.  Added code to honor any
**                           pending thread interruption that was already
**                           delivered to a given thread. Added 
**                           documentation.  Fixed a re-initialization
**                           issue that appears at any re-logon.
** 009 GES 20070410   @32914 Provided a global lock to eliminate a 
**                           possible (though unlikely) race condition
**                           when handling remote interrupt requests on
**                           the server side.
** 010 ECF 20071106   @35877 Refactored net package.  Replaced Queue with
**                           Session. Implemented generics. Other minor
**                           cleanup.
** 011 CA  20090831   @43795 Fixed interruption processing when both peers
**                           are in remote mode.
** 012 CA  20090901   @43810 Now the InterruptHandler implementation is 
**                           retrieved from the SessionManager. Major 
**                           changes to interrupt handling, to fix the
**                           interruption mechanism when a remote server 
**                           request is active. Also, now virtual controls are
**                           multiplexed as the virtual sessions are.
** 013 CA  20090902   @43820 Fixed a problem with setRightSideControl, when 
**                           the passed instance is the same as the Control
**                           instance which receives it (regression introduced
**                           by H012).
** 014 CA  20091021   @44180 Fixed some server-to-server interruption issues, 
**                           exposed by the changes in SessionManager.endSession
**                           found @44176. The issues are related to client 
**                           abend while a virtual session is executing.
** 015 CA  20091106   @44319 Added critical section protection against thread
**                           interruption. This ensures that the Conversation 
**                           thread can't be interrupted while a critical 
**                           section is being executed.
** 016 CA  20140513          Added a weight for the context-local var, to ensure predetermined 
**                           order during context reset.
** 017 CA  20170502          Thread.interrupted() in initControl is called only if this is the 
**                           default Control instance (for the client or current context, 
**                           on server-side).
** 018 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
** 019 ES  20240513          Added getCurrentContext method to get context local information for
**                           running thread.
**                           Added stopAfterInterrupt method for setting context local with the control 
**                           of a Conversation thread. And and performing a interrupt caused a stop-after 
**                           timeout.
**     ES  20240620          Added serverInterrupt method to insert Ctrl-C into TypeAhead queue.
** 020 ES  20250221          Added interrupt(Control ct) method to be able to give custom control object 
**                           for interruption.
**         20250224          Remove setting up the serverInstance ContextLocal variable to a custom Control
**                           object.
**         20250407          Added getTargetThreadStatus and targetThreadStatus to get the current state of 
**                           target thread.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
** 
** Additional terms under GNU Affero GPL version 3 section 7:
** 
**   Under Section 7 of the GNU Affero GPL version 3, the following additional
**   terms apply to the works covered under the License.  These additional terms
**   are non-permissive additional terms allowed under Section 7 of the GNU
**   Affero GPL version 3 and may not be removed by you.
** 
**   0. Attribution Requirement.
** 
**     You must preserve all legal notices or author attributions in the covered
**     work or Appropriate Legal Notices displayed by works containing the covered
**     work.  You may not remove from the covered work any author or developer
**     credit already included within the covered work.
** 
**   1. No License To Use Trademarks.
** 
**     This license does not grant any license or rights to use the trademarks
**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
**     of Golden Code Development Corporation. You are not authorized to use the
**     name Golden Code, FWD, or the names of any author or contributor, for
**     publicity purposes without written authorization.
** 
**   2. No Misrepresentation of Affiliation.
** 
**     You may not represent yourself as Golden Code Development Corporation or FWD.
** 
**     You may not represent yourself for publicity purposes as associated with
**     Golden Code Development Corporation, FWD, or any author or contributor to
**     the covered work, without written authorization.
** 
**   3. No Misrepresentation of Source or Origin.
** 
**     You may not represent the covered work as solely your work.  All modified
**     versions of the covered work must be marked in a reasonable way to make it
**     clear that the modified work is not originating from Golden Code Development
**     Corporation or FWD.  All modified versions must contain the notices of
**     attribution required in this license.
*/

package com.goldencode.p2j.net;

import java.util.*;
import java.util.logging.*;

import com.goldencode.p2j.security.*;
import com.goldencode.p2j.ui.client.TypeAhead;
import com.goldencode.p2j.util.*;
import com.goldencode.p2j.util.logging.*;

/**
 * Tracks the state of the "conversation" between the two sides of the 
 * networking session and provides a local and remote delivery mechanism
 * for notification of interruptions.  An interruption that occurs during
 * local processing is delivered by marking the given thread as interrupted.
 * Any code that checks this flag (e.g. <code>Object.wait()</code>) will
 * process accordingly (e.g. throw an <code>InterruptedException</code>).
 * Any interruption that occurs during remote processing is delivered
 * to the other end of the session asynchronous to the "conversation". In 
 * other words, this is an "out of band" notification attempt.  As long as
 * processing is still local to the other side, the interruption will be  
 * raised there, as a local exception.  Any failure to deliver the
 * interruption on the remote side (e.g. the remote side called back to this
 * local side before the interruption could be delivered) will cause the
 * interruption to be honored at the moment that this side next shifts into
 * local processing mode.
 * <p>
 * This class maintains a stack of thread objects so that the current thread
 * of execution on the local side is known.  This is only used when processing
 * is local.
 * <p>
 * Initially the stack is empty.  The first call to the {@link #init} method
 * implicitly pushes the first thread into the stack.
 * <p>
 * The current point of execution for the conversation is tracked between the
 * remote side and the local side.  Local execution is active in the
 * following cases:
 * <p>
 * <ol>
 *    <li> Immediately after initialization by <code>init()</code>.
 *    <li> Before any call to {@link #pushRemoteCall} and after the  
 *         corresponding call to {@link #popRemoteCall}.  These calls
 *         form a "bracket" around remote processing.
 *    <li> During a remote processing bracket, if the remote side recursively
 *         calls back into the local side, there will be a bracketed pair of
 *         notifications.  After any call to {@link #pushUnconditionally} 
 *         and before the correspondng call to {@link #popUnconditionally},
 *         processing is local.
 *    <li> Because a call to the other side of the conversation can occur
 *         at any time, there is no built-in limit to recursion.  For this
 *         reason, it is perfectly possible that the local side will
 *         recursively call back to the remote side before completing an
 *         "unconditional" bracket.  Likewise the remote side may call into
 *         the local side before returning from a remote processing bracket. 
 *         Thus the state of local processing is directly determined by the
 *         last call to any one of the above 4 bracketing calls.
 * </ol>
 * <p>
 * To notify this class that an interruption needs to be delivered, use
 * {@link #interrupt}.
 */
public final class Control
{
   /** Logger */
   private static final CentralLogger LOG =
      CentralLogger.get(Control.class.getName());

   /** The generating side for asynchronous interruptions if non-null. */
   private static Control clientInstance = null;

   /** The receiving side of asynchronous interruptions if non-null. */
   private static ContextLocal<Control> serverInstance = null;

   static
   {
      if (SessionManager.get().isLeaf())
      {
         clientInstance = new Control();
      }
      else
      {
         serverInstance = new ContextLocal<Control>()
         {
            @Override
            public WeightFactor getWeight()
            {
               return WeightFactor.LAST;
            }
            
            protected Control initialValue()
            {
               return (new Control());
            }
         };      
      }
   }

   /** The currently ongoing operations (requests or replies). */
   private final Stack<Operation> operations = new Stack<Operation>();
   
   /** List of remote outstanding reply ids. */
   private final Set<Integer> remotePendingReplies = new HashSet<Integer>();
   
   /** 
    * The operation being processed on the other peer. This is used only
    * for interruption processing. 
    */
   private Operation remoteOperation = null;

   /** Initialized flag. */
   private boolean ready = false;
   
   /** The connection for this P2J session. */
   private Session session = null;
   
   /** Which side owns control now: local (true) or remote (false). */
   private Boolean local = false;
   
   /** <code>true</code> if an interruption signal has occurred. */
   private boolean interrupted = false;
   
   /** <code>true</code> if currently processing an asynchronous signal. */
   private boolean async = false;
   
   /** The control stack. */
   private Stack<Thread> control = new Stack<Thread>();
   
   /** Global lock owner or <code>null</code> if not locked. */
   private Thread lock = null;
   
   /** 
     * The Control instance for the session on the left-side of this Control.
     * If this is not null, then the {@link #rightSideControl} must be null.
     */
   private Control leftSideControl = null;
   
   /** 
    * The Control instance for the session on the right-side of this Control.
    * If this is not null, then the {@link #leftSideControl} must be null.
    */
   private Control rightSideControl = null;

   /**
    * Constructor with restricted access (disallows external construction).
    */
   private Control() 
   {
   }

   /**
    * Initializes the control stack.
    *
    * @param    session
    *           The connection for this context.
    */
   public static synchronized void init(Session session)
   {
      Control ct = locate(0);

      ct.initControl(session);
   }
   
   /**
    * Initializes the control stack.
    *
    * @param    session
    *           The connection for this context.
    */
   synchronized void initControl(Session session)
   {
      // avoid duplicate initialization
      if (this.ready && this.session == session)
         return;
      
      // this may occur more than once (if we are in re-logon mode)
      this.local = true;
      this.control.clear();
      this.control.push(Thread.currentThread());
      this.session = session;
      if (this == locate(0))
      {
         Thread.interrupted();
      }
      this.ready = true;
   }
   
   /**
    * Clears the pending interrupt flag. Used whenever an
    * <code>InterruptedException</code> is caught to avoid duplicate
    * exceptions.
    */
   public static void clearInterrupted()
   {
      Control ct = locate(0);
      
      // since this is a static method, the method-level synch would be on
      // the CLASS, but we must synch on the INSTANCE before testing or
      // modifying its state
      synchronized (ct)
      {
         ct.interrupted = false;
      }
   }
   
   /**
    * Gives the context local information of a currently running thread.
    * 
    * @return  Context local information.
    */
   public static Control getCurrentContext()
   {    
      return serverInstance.get();
   }
   
   /**
    * Sets the context local to a control of a Conversation thread.
    * Interrupts a thread if it is local execution on server or 
    * sends a INTERRUPT_SESSION message on client side.
    * 
    * @param  control
    *         Control related to a Conversation thread.
    */
   public static void stopAfterInterrupt(Control control)
   {
      if (control.local)
      {
         Control.interrupt(control);
      }
      else
      {
         control.sentRemoteInterrupt();
      }
   }
   
   /**
    * Method sends a async request to client side with
    * SERVER_INTERRUPT message.
    * 
    */
   public void sentRemoteInterrupt()
   {
      RoutingKey key = new RoutingKey(RoutingKey.SERVER_INTERRUPT);
      Message message = new Message(key, new Object[] {});
      
      try
      {
         // mark this control instance as "in the middle of"
         // processing an asynchronous notification
         async = true;
         message.setType(MessageTypes.REQUEST_ASYNCH);
         
         session.forward(message);
      }
      
      catch (Exception exc)
      {
         // check if the cause is a InterruptedException. in this case, 
         // do not log it as a warning
         Level logLvl = Level.WARNING;
         String msg = "Error interrupting virtual session peer.";
         
         Throwable cause = exc;
         while (cause != null)
         {
            if (cause instanceof InterruptedException)
            {
               logLvl = Level.FINE;
               break;
            }
            if (cause instanceof SilentUnwindException)
            {
               msg = "Control.interruptImpl(boolean)";
               logLvl = Level.FINE;
               break;
            }

            cause = cause.getCause();
         }

         if (LOG.isLoggable(logLvl))
         {
            LOG.log(logLvl, msg, exc);
         }
      }
      
      finally
      {
         // clear the asynchronous notification mode flag
         async = false;
      }
   }
   
   /**
    * Trigger an interrupt cause by a stop-after timeout, by inserting
    * a Ctrl-C into TypeAhead queue.
    */
   public static void triggerServerInterrupt()
   {
      TypeAhead typeAhead = TypeAhead.instance();
    
      if (typeAhead != null)
      {
         typeAhead.triggerServerInterrupt();
      }
   }
   
   /**
    * Interrupts the execution of the conversation as the result of an
    * asynchronous interruption notification (usually by an end-user).
    * <p>
    * Since this call is made on a different thread, the target thread has to
    * be interrupted.
    * <p>
    * If by the time this call is made control is with the remote side AND
    * the original interrupt request was generated on this node, a remote
    * call is made to interrupt the conversation there.
    *
    * @return   <code>true</code> if the interrupt request was successfully
    *           delivered.
    */
   public static boolean interrupt()
   {
      Control ct = locate(0);
      
      return interrupt(ct);
   }
   
   /**
    * Gets the state of the target thread.
    * 
    * @return   See above.
    */
   public static Thread.State getTargetThreadStatus()
   {
      Control ct = locate(0);
   
      return ct.targetThreadStatus();
   }
   
   
   /**
    * Gets the state of the target thread.
    * 
    * @return   See above.
    */
   public Thread.State targetThreadStatus()
   {
      Thread t = getTargetThread();
      return t.getState();
   }
   
   /**
    * Interrupts the execution of the conversation as the result of an
    * asynchronous interruption notification (usually by an end-user or
    * a stop-after time-out).
    * <p>
    * Since this call is made on a different thread, the target thread has to
    * be interrupted.
    * <p>
    * If by the time this call is made control is with the remote side AND
    * the original interrupt request was generated on this node, a remote
    * call is made to interrupt the conversation there.
    * @param    ct
    *           Custom Control object for which the interruption is performed. 
    * @return   <code>true</code> if the interrupt request was successfully
    *           delivered.
    */
   static boolean interrupt(Control ct)
   {  
      boolean delivered = false;
      
      synchronized (ct)
      {
         // this block must be synchronized on the "ct" object, to be sure
         // the rightSideControl can't be nullified between the not-null test 
         // and its usage
         if (ct.rightSideControl != null)
         {
            // first, we dispatch the interrupt to the right-side 
            // session (i.e. local_server -to-> remote_server, if the session
            // wasn't shutdown yet); if this doesn't process it, try to handle 
            // it locally. 
            delivered = ct.rightSideControl.session.isRunning() &&
                  ct.rightSideControl.interruptImpl(true);
         }
      }
   
      // if the right-side didn't accept the interruption request, we must
      // handle it here, on the "central" node.
      return delivered || ct.interruptImpl(false);
   }

   /**
    * Interrupts the execution of the conversation as the result of an
    * asynchronous interruption notification (usually by an end-user).
    * <p>
    * Since this call is made on a different thread, the target thread has to
    * be interrupted.
    * <p>
    * If by the time this call is made control is with the remote side AND
    * the original interrupt request was generated on this node, a remote
    * call is made to interrupt the conversation there.
    *
    * @param    rightSide
    *           <code>true</code> if the interruption is delivered to the 
    *           right-side relative to the control for the current context. 
    *
    * @return   <code>true</code> if the interrupt request was successfully
    *           delivered.
    */
   synchronized boolean interruptImpl(boolean rightSide)
   {
      boolean delivered = false;
  
      if (ready)
      {
         interrupted = true;
   
         if (local)
         {
            // interrupt local execution
            Thread t = getTargetThread();
            
            if (t != null)
            {
               CriticalSectionManager.interrupt(t);

               delivered = true;
               interrupted = false;
            }
         }
         else
         {
            // the server side doesn't generate interruptions on the
            // client
            if (clientInstance == null && !rightSide)
            {
               // if nothing in the stack or the remote operation is not set,
               // it means this interruption didn't come from the remote peer
               if (operations.isEmpty() || remoteOperation == null)
               {
                  return false;
               }
               
               Operation current = operations.peek();
               
               Operation remote = remoteOperation;
               boolean interrupt = false;

               if (!remote.request && 
                   current.request && 
                   remote.operationId == current.operationId)
               {
                  // case a: CL request == false, 
                  //         SRV request == true, 
                  //         CL (reply) id == SRV (request) id
                  interrupt = true;
               }

               if (remote.request)
               {
                  Operation test = new Operation(false, remote.operationId);
                  
                  // case b: CL request == true
                  //         SRV request can be true or false
                  //         CL request id is not in the list of 
                  //         outstanding SRV reply ids
                  interrupt = !operations.contains(test);
               }
               
               if (remote.request   && 
                   !current.request && 
                   remote.operationId == current.operationId)
               {
                  // case c: CL request == true
                  //         SRV request == false
                  //         CL (request) id == SRV (reply) id
                  interrupt = false;
               }
               
               if (current.request)
               {
                  // case d: CL request can be true or false
                  //         SRV request == true
                  //         SRV request id is not in the list of 
                  //         outstanding CL reply ids
                  interrupt = !remotePendingReplies
                                               .contains(current.operationId);
               }

               this.interrupted = interrupt;

               return interrupt;
            }
   
            // interrupt the other side of the remote connection 
            RoutingKey key = new RoutingKey(RoutingKey.INTERRUPT_SESSION);
            Operation current = operations.peek();
            
            // collect all the unprocessed replies to send them
            List<Integer> repliesList = new ArrayList<Integer>();
            for (int i = 0; i < operations.size(); i++)
            {
               Operation operation = operations.get(i);
               if (!operation.request)
               {
                  repliesList.add(operation.operationId);
               }
            }
            
            int[] replies = Utils.integerCollectionToPrimitive(repliesList);
            
            Message message =
               new Message(key,
                    new Object[] { current.request,
                                   current.operationId,
                                   replies });
            
            try
            {
               // mark this control instance as "in the middle of"
               // processing an asynchronous notification
               async = true;
               
               Boolean del = (Boolean) session.transact(message, 0); 
               delivered = (del != null) ? del.booleanValue() : false;
               
               if (delivered)
               {
                  // no further action locally
                  interrupted = false;
               }
            }
            
            catch (Exception exc)
            {
               // check if the cause is a InterruptedException. in this case, 
               // do not log it as a warning
               Level logLvl = Level.WARNING;
               String msg = "Error interrupting virtual session peer.";
               
               Throwable cause = exc;
               while (cause != null)
               {
                  if (cause instanceof InterruptedException)
                  {
                     logLvl = Level.FINE;
                     break;
                  }
                  if (cause instanceof SilentUnwindException)
                  {
                     msg = "Control.interruptImpl(boolean)";
                     logLvl = Level.FINE;
                     break;
                  }
      
                  cause = cause.getCause();
               }
      
               if (LOG.isLoggable(logLvl))
               {
                  LOG.log(logLvl, msg, exc);
               }
            }
            
            finally
            {
               // clear the asynchronous notification mode flag
               async = false;
            }
         }
      }
      
      return delivered;
   }
   
   /**
    * Get the session for the specified context.
    * 
    * @param    contextID
    *           The context to which the interruption must be applied.
    *
    * @return  the P2J session.
    */
   static Session getSession(int contextID)
   {
      return locate(contextID).session;
   }

   /**
    * Return the {@link #ready} state of this control.
    * 
    * @return  <code>true</code> if this control is {@link #ready}.
    */
   boolean isReady()
   {
      return ready;
   }

   /**
    * Save the state of the peer. This means saving the operation type 
    * (request or reply), the operation ID and the peer's outstanding replies.
    * 
    * @param   request
    *          The remote operation type. <code>true</code> for request.
    * @param   operationId
    *          The remote operation ID.
    * @param   pendingReplies
    *          The list of remote outstanding replies.
    */
   synchronized void setRemoteOperation(boolean request, 
                                        int     operationId, 
                                        int[]   pendingReplies)
   {
      remoteOperation = new Operation(request, operationId);
      remotePendingReplies.clear();
      for (int i = 0; i < pendingReplies.length; i++)
      {
         remotePendingReplies.add(pendingReplies[i]);
      }
   }
   
   /**
    * Clear the data about the remote operation.
    */
   synchronized void clearRemoteOperation()
   {
      remoteOperation = null;
      remotePendingReplies.clear();
   }
   
   /**
    * Push a new operation to the stack, saving its type and its ID.
    * 
    * @param   request
    *          <code>true</code> if this is a requests, <code>false</code> for
    *          a reply 
    * @param   operationId
    *          the ID of the current operation being executed.
    */
   private void pushOperation(boolean request, int operationId)
   {
      operations.push(new Operation(request, operationId));
   }
   
   /**
    * Pop the operation stack. Invoked after the request or reply is finished
    * processing and the control is switched to the other side.
    */
   private void popOperation()
   {
      operations.pop();
   }
   
   /**
    * Obtain the global lock on the session's instance. This will block until
    * the lock is obtained. Once obtained, no use of the session's instance
    * can be made from other threads until the lock is released with
    * {@link #unlock}.
    */
   synchronized void lock()
   {
      lockCore();
      
      if (leftSideControl != null)
      {
         leftSideControl.lock();
      }
   }
   
   /**
    * Release the global lock on the session's instance. This will allow all
    * other threads to use the session's instance as soon as this method
    * returns.
    */
   synchronized void unlock()
   {
      unlockCore();
      
      if (leftSideControl != null)
      {
         leftSideControl.unlock();
      }
   }
   
   /**
    * Switches control to the remote side.  If an interrupt is pending
    * ({@link #interrupt} has been issued but not yet honored), then this
    * method calls <code>handleInterrupted()</code>.
    * <p>
    * This method will block until it can obtain the global lock on this
    * instance.  Before returning, that lock will be released.
    * <p>
    * If this instance is in asynchronous notification mode, then this method
    * is a NOP.  Otherwise the state of the instance would change during the
    * asynchronous notification, which would be improper since such 
    * notifications are by nature supposed to be "out of band". Thus state
    * changes for the normal communication channels must be unaffected.
    *
    * @param   request
    *          the ID of the current request being executed.
    */
   synchronized void pushRemoteCall(int request)
   {
      lockCore();
      
      try
      {
         if (!ready || !local || async)
         {
            // we must push here too, to avoid unbalancing the stack; reason:
            // popRemoteCall will call popOperation in its finally clause
            pushOperation(true, request);
            return;
         }
         
         // CASE 1: if the local interrupt processing interrupted the thread
         // but the processing on that thread did not honor the thread's
         // interrupted status for any reason, we honor it here
         // CASE 2: if the remote interrupt processing did not succeed for any
         // reason we honor any pending interrupt here (e.g. the interrupt
         // request was forwarded at the moment when both nodes were in
         // "remote" mode and our node is about to be set back into "local"
         // mode), we detect this case using the interrupted member; this case
         // can also occur if the local interrupt processing was unable to
         // deliver the interruption
         
         if (Thread.interrupted() || interrupted)
         {
            interrupted = false;
            handleInterruptedImpl(new InterruptedException());
         }
         
   
         local = false;
         
         
         // we switched in "remote" mode, push only if an interruption doesn't
         // stop us
         pushOperation(true, request);
      }
      
      finally
      {
         unlockCore();

         if (leftSideControl != null)
         {
            leftSideControl.pushRemoteCall(request);
         }
      }
   }

   /**
    * Switches control back to the local side.  If an interrupt is pending
    * ({@link #interrupt} has been issued but not yet honored), then this
    * method calls <code>handleInterrupted()</code>.
    * <p>
    * This method will block until it can obtain the global lock on this
    * instance.  Before returning, that lock will be released.
    * <p>
    * If this instance is in asynchronous notification mode, then this method
    * is a NOP.  Otherwise the state of the instance would change during the
    * asynchronous notification, which would be improper since such 
    * notifications are by nature supposed to be "out of band". Thus state
    * changes for the normal communication channels must be unaffected.
    */
   synchronized void popRemoteCall()
   throws InterruptedException
   {
      lockCore();
      
      try
      {
         if (!ready || local || async)
            return;
   
         local = true;
   
         // CASE 1: if the local interrupt processing interrupted the thread
         // but the processing on that thread did not honor the thread's
         // interrupted status for any reason, we honor it here
         // CASE 2: if the remote interrupt processing did not succeed for any
         // reason we honor any pending interrupt here (e.g. the interrupt
         // request was forwarded at the moment when both nodes were in
         // "remote" mode and our node is about to be set back into "local"
         // mode), we detect this case using the interrupted member
         if (Thread.interrupted() || interrupted)
         {
            interrupted = false;
            handleInterruptedImpl(new InterruptedException());
         }
      }
      
      finally
      {
         popOperation();

         unlockCore();

         if (leftSideControl != null)
         {
            leftSideControl.popRemoteCall();
         }
      }
   }

   /**
    * Unconditionally pushes the current thread onto the stack. Used to
    * prepare for asynchronous interrupt requests (e.g. on the server side
    * of a conversation, where the processing must be interrupted on an
    * asynchronous basis).
    * <p>
    * This method will block until it can obtain the global lock on this
    * instance.  Before returning, that lock will be released.
    * <p>
    * MUST NOT be called from any thread used for an asynchronous 
    * notification, such as the one generated by {@link #interrupt} to
    * trigger a remote interruption.
    *
    * @param   request
    *          the ID of the request being processed.
    */
   synchronized void pushUnconditionally(int request)
   {
      lockCore();
      
      try
      {
         control.push(Thread.currentThread());
         local = true;
         
         // if the remote interrupt processing did not succeed for any reason
         // we honor any pending interrupt here (e.g. the interrupt request
         // was forwarded at the moment when both nodes were in "remote" mode
         // and our node is about to be set back into "local" mode) we detect
         // this case using the interrupted member
         if (interrupted)
         {
            interrupted = false;
            handleInterruptedImpl(new InterruptedException());
         }
      }
      
      finally
      {
         // we just switched in local mode; this is done regardless if an
         // interruption is pending or not - because we do call
         // popUnconditionally if an interruption was pending
         pushOperation(false, request);

         unlockCore();

         if (leftSideControl != null)
         {
            leftSideControl.pushUnconditionally(request);
         }
      }
   }

   /**
    * Unconditionally pops the current thread off the stack.  Used to
    * cleanup the server side of a conversation, where the processing must
    * be interrupted on an asynchronous basis.
    * <p>
    * This method will block until it can obtain the global lock on this
    * instance.  Before returning, that lock will be released.
    * <p>
    * MUST NOT be called from any thread used for an asynchronous 
    * notification, such as the one generated by {@link #interrupt} to
    * trigger a remote interruption.
    */
   synchronized void popUnconditionally()
   {
      lockCore();
      
      try
      {
         control.pop();
         local = false;
         
         // CASE 1: if the local interrupt processing interrupted the thread
         // but the processing on that thread did not honor the thread's
         // interrupted status for any reason, we honor it here
         // CASE 2: the local interrupt processing was unable to deliver the
         // interruption, we detect this with the interrupted member
         if (Thread.interrupted() || interrupted)
         {
            interrupted = false;
            handleInterruptedImpl(new InterruptedException());
         }
      }
      
      finally
      {
         // we are back in remote mode
         popOperation();

         unlockCore();

         if (leftSideControl != null)
         {
            leftSideControl.popUnconditionally();
         }
      }
   }

   /**
    * Gets the currently executing thread without popping it off the stack.
    *
    * @return   Target thread or <code>null</code> if control is on the
    *           remote side.
    */
   synchronized Thread getTargetThread()
   {
      Thread target = null;
      
      // when the queue is stopped by the Reader/Writer threads, there is a 
      // possibility for the control thread stack to be empty - this is the
      // case for the server-to-server queue, when the Reader thread closes
      // the queue after the Dispatcher thread finished processing an incoming 
      // interrupt request 
      if (ready && local && !control.isEmpty())
      {
         target = control.peek();
      }
      
      return target;
   }
   
   /**
    * Set the left-side control for this virtual control, in the connection
    * chain.
    * 
    * @param   control
    *          the left-side instance, which transferes control to the virtual
    *          session
    */
   synchronized void setLeftSideControl(Control control)
   {
      this.leftSideControl = control;
   }
   
   /**
    * Set the right-side instance for this virtual control.
    *  
    * @param   control
    *          the right-side instance, which transfered control to us
    */
   synchronized void setRightSideControl(Control control)
   {
      if (clientInstance == null && control != this)
      {
         // the right-side control is saved only if the request
         // is being performed via a different control.
         rightSideControl = control;
      }
   }
   
   /**
    * Get the right-side instance for this virtual control.
    *  
    * @return  the right-side instance, which transfered control to us
    */
   synchronized Control getRightSideControl()
   {
      return clientInstance == null ? rightSideControl : null;
   }

   /**
    * Obtains the lock for the given thread if the lock is unowned, or if
    * owned, checks if the given thread owns the lock. Must only be called
    * from within a synchronized block.
    *
    * @param    thread
    *           The potential owner.
    *
    * @return   <code>true</code> if the thread owns the lock (by obtaining
    *           it within this method or by having previously obtained it).
    *           <code>false</code> if some other thread owns the lock.
    */
   private boolean tryLock(Thread thread)
   {
      if (lock == null)
      {
         // obtain the lock
         lock = thread;
      }
      else if (lock == thread)
      {
         // we already own the lock, just return
      }
      else
      {
         // someone else owns the lock
         return false;
      }
      
      return true;
   }
   
   /**
    * Obtains the global lock, blocking until it is available. Must only be
    * called from within a synchronized block.
    */
   private void lockCore()
   {
      Thread thread = Thread.currentThread();
      
      while (!tryLock(thread))
      {
         try
         {
            wait();
         }
         
         catch (InterruptedException ie)
         {
            handleInterruptedImpl(new InterruptedException());
         }
      }
   }

   /**
    * Unlocks the global lock. Must only be called from within a synchronized
    * block.
    */
   private void unlockCore()
   {
      if (lock == Thread.currentThread())
      {
         lock = null;
         notify();
      }
   }
   
   /**
    * Trigger a notification when the thread is interrupted so that the
    * handler may take any action deemed appropriate like throwing a
    * customized exception.
    *
    * @param    exc
    *           The original <code>InterruptedException</code>.
    */
   static void handleInterrupted(InterruptedException exc)
   {
      Control ct = locate(0);
    
      synchronized (ct)
      {
         ct.handleInterruptedImpl(exc);
      }
   }
   
   /**
    * Trigger a notification when the thread is interrupted so that the
    * handler may take any action deemed appropriate like throwing a
    * customized exception.
    *
    * @param    exc
    *           The original <code>InterruptedException</code>.
    */
   private void handleInterruptedImpl(InterruptedException exc)
   {
      SessionManager.get().getInterruptHandler().handleInterrupted(exc);
   }

   /**
    * Gets the instance of this class for the current context.
    * <p>
    * When the passed context ID is 0, it means it must retrieve either the
    * control for the client instance or the registered control for the current
    * context, if {@link #clientInstance} is null.
    * 
    * @param    contextID
    *           The context for which the control instance must be located.
    * 
    * @return   The instance.
    */
   static synchronized Control locate(int contextID)
   {
      // since this is a static method, the method-level synch is on the 
      // CLASS not on the INSTANCE

      if (contextID == 0)
      {
         return (clientInstance != null ? clientInstance 
                                        : serverInstance.get());
      }
      else
      {
         SessionManager sessMgr = SessionManager.get();

         Control ct = sessMgr.virtualControlByContext.get(contextID);

         if (ct == null)
         {
            ct = new Control();
            
            sessMgr.virtualControlByContext.put(contextID, ct);
         }

         return ct;
      }
   }

   /**
    * Set the server instance for the current context.
    * 
    * @param   control
    *          Object used as the server instance.
    */
   static synchronized void setContext(Control control)
   {
      serverInstance.set(control);
   }
   
   /**
    * Container for the current executed operation and its type. This may 
    * represent a reply being processed or a new request being invoked.
    */
   private static class Operation
   {
      /** 
       * Flag indicating if this operation is a request (when 
       * <code>true</code>) or a reply (when <code>false</code>). 
       */
      private boolean request;
      
      /** Field holding the operation ID. */
      private int operationId;

      /**
       * Constructor.
       * 
       * @param   request
       *          if <code>true</code>, this is a request
       * @param   operationId
       *          the operation id
       */
      private Operation(boolean request, int operationId)
      {
         this.request = request;
         this.operationId = operationId;
      }
      
      /**
       * Test if the passed object is the same as this operation. Equality
       * means both {@link #request} and {@link #operationId} must be the 
       * same.
       */
      public boolean equals(Object obj)
      {
         if (!(obj instanceof Operation))
         {
            return false;
         }
         
         Operation o = (Operation) obj;
         
         return o.request == this.request && 
                o.operationId == this.operationId;
      }
   }
}