//------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ namespace System.ServiceModel.Channels { using System.Collections.Generic; using System.Diagnostics; using System.Runtime; using System.Threading; partial class PeerNodeImplementation { // A simple state manager for the PeerNode. Unlike the state managers used for channels and other // classes, a PeerNode's Open/Close is counted, a PeerNode is re-openable, and Abort only // takes effect if the outstanding number of Opens is 1. // The PeerNode defers to this object for all state related operations. // // Whenever a call is made that may change the state of the object (openCount transitions between 0 and 1), // an operation is queued. When an operation is removed from the queue, if the target state is still the // same as the operation (e.g. openCount > 0 and operation == Open) and the object is not already in that // state, the operation is performed by calling back into the PeerNode // // Because each operation is pulled form the queue one at a time, the open and close of the // PeerNode is serialized class SimpleStateManager { internal enum State { NotOpened, Opening, Opened, Closing }; State currentState = State.NotOpened; object thisLock = new object(); Queue queue = new Queue(); bool queueRunning; int openCount; PeerNodeImplementation peerNode; public SimpleStateManager(PeerNodeImplementation peerNode) { this.peerNode = peerNode; } object ThisLock { get { return thisLock; } } public void Abort() { lock (ThisLock) { bool runAbort = false; if (openCount <= 1 && currentState != State.NotOpened) { runAbort = true; } if (openCount > 0) { --openCount; } if (runAbort) { try { peerNode.OnAbort(); } finally { currentState = State.NotOpened; } } } } public IAsyncResult BeginClose(TimeSpan timeout, AsyncCallback callback, object state) { CloseOperation op = null; lock (ThisLock) { if (openCount > 0) { --openCount; } if (openCount > 0) { return new CompletedAsyncResult(callback, state); } else { op = new CloseOperation(this, peerNode, timeout, callback, state); queue.Enqueue(op); RunQueue(); } } return op; } public IAsyncResult BeginOpen(TimeSpan timeout, AsyncCallback callback, object state, bool waitForOnline) { bool completedSynchronously = false; OpenOperation op = null; lock (ThisLock) { openCount++; if (openCount > 1 && currentState == State.Opened) { completedSynchronously = true; } else { op = new OpenOperation(this, peerNode, timeout, callback, state, waitForOnline); queue.Enqueue(op); RunQueue(); } } if (completedSynchronously) { return new CompletedAsyncResult(callback, state); } return op; } public void Close(TimeSpan timeout) { EndClose(BeginClose(timeout, null, null)); } public static void EndOpen(IAsyncResult result) { // result can be either an OpenOperation or a CompletedAsyncResult if (result is CompletedAsyncResult) CompletedAsyncResult.End(result); else OpenOperation.End(result); } public static void EndClose(IAsyncResult result) { // result can be either an CloseOperation or a CompletedAsyncResult if (result is CompletedAsyncResult) CompletedAsyncResult.End(result); else CloseOperation.End(result); } // Process IP Address change event from IP helper public void OnIPAddressesChanged(object sender, EventArgs e) { IPAddressChangeOperation op = null; lock (ThisLock) { op = new IPAddressChangeOperation(peerNode); queue.Enqueue(op); RunQueue(); } } public void Open(TimeSpan timeout, bool waitForOnline) { EndOpen(BeginOpen(timeout, null, null, waitForOnline)); } // Start running operations from the queue (must be called within lock) void RunQueue() { if (queueRunning) return; queueRunning = true; ActionItem.Schedule(new Action(RunQueueCallback), null); } void RunQueueCallback(object state) { IOperation op; // remove an operation from the queue lock (ThisLock) { Fx.Assert(queue.Count > 0, "queue should not be empty"); op = queue.Dequeue(); } try { // execute the operation op.Run(); } finally { lock (ThisLock) { // if there are still pending operations, schedule another thread if (queue.Count > 0) { try { ActionItem.Schedule(new Action(RunQueueCallback), null); } catch (Exception e) { if (Fx.IsFatal(e)) throw; DiagnosticUtility.TraceHandledException(e, TraceEventType.Information); } } else { queueRunning = false; } } } } interface IOperation { void Run(); } class CloseOperation : OperationBase { PeerNodeImplementation peerNode; public CloseOperation(SimpleStateManager stateManager, PeerNodeImplementation peerNode, TimeSpan timeout, AsyncCallback callback, object state) : base(stateManager, timeout, callback, state) { this.peerNode = peerNode; } protected override void Run() { Exception lclException = null; try { lock (ThisLock) { if (stateManager.openCount > 0) { // the current target state is no longer Closed invokeOperation = false; } else if (stateManager.currentState == State.NotOpened) { // the state is already Closed invokeOperation = false; } else if (timeoutHelper.RemainingTime() <= TimeSpan.Zero) { // Time out has already happened complete will be taken care of in the // OperationBase class invokeOperation = false; throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new TimeoutException()); } else { // the PeerNode needs to be closed if (!(stateManager.currentState != State.Opening && stateManager.currentState != State.Closing)) { throw Fx.AssertAndThrow("Open and close are serialized by queue We should not be either in Closing or Opening state at this point"); } if (stateManager.currentState != State.NotOpened) { stateManager.currentState = State.Closing; invokeOperation = true; } } } } catch (Exception e) { if (Fx.IsFatal(e)) throw; DiagnosticUtility.TraceHandledException(e, TraceEventType.Information); lclException = e; } if (invokeOperation) { try { peerNode.OnClose(timeoutHelper.RemainingTime()); } catch (Exception e) { if (Fx.IsFatal(e)) throw; DiagnosticUtility.TraceHandledException(e, TraceEventType.Information); lclException = e; } lock (ThisLock) { stateManager.currentState = State.NotOpened; } } Complete(lclException); } } class OpenOperation : OperationBase { PeerNodeImplementation peerNode; bool waitForOnline; public OpenOperation(SimpleStateManager stateManager, PeerNodeImplementation peerNode, TimeSpan timeout, AsyncCallback callback, object state, bool waitForOnline) : base(stateManager, timeout, callback, state) { this.peerNode = peerNode; this.waitForOnline = waitForOnline; } protected override void Run() { Exception lclException = null; try { lock (ThisLock) { if (stateManager.openCount < 1) { // the current target state is no longer Opened invokeOperation = false; } else if (stateManager.currentState == State.Opened) { // the state is already Opened invokeOperation = false; } else if (timeoutHelper.RemainingTime() <= TimeSpan.Zero) { // Time out has already happened complete will be taken care of in the // OperationBase class invokeOperation = false; throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new TimeoutException()); } else { // the PeerNode needs to be opened if (!(stateManager.currentState != State.Opening && stateManager.currentState != State.Closing)) { throw Fx.AssertAndThrow("Open and close are serialized by queue We should not be either in Closing or Opening state at this point"); } if (stateManager.currentState != State.Opened) { stateManager.currentState = State.Opening; invokeOperation = true; } } } } catch (Exception e) { if (Fx.IsFatal(e)) throw; DiagnosticUtility.TraceHandledException(e, TraceEventType.Information); lclException = e; } if (invokeOperation) { try { peerNode.OnOpen(timeoutHelper.RemainingTime(), waitForOnline); lock (ThisLock) { stateManager.currentState = State.Opened; } } catch (Exception e) { if (Fx.IsFatal(e)) throw; lock (ThisLock) { stateManager.currentState = State.NotOpened; // since Open is throwing, we roll back the openCount because a matching Close is not // expected stateManager.openCount--; } lclException = e; DiagnosticUtility.TraceHandledException(e, TraceEventType.Information); } } Complete(lclException); } } // Base class for Open and Cose abstract class OperationBase : AsyncResult, IOperation { protected SimpleStateManager stateManager; protected TimeoutHelper timeoutHelper; AsyncCallback callback; protected bool invokeOperation; // Double-checked locking pattern requires volatile for read/write synchronization volatile bool completed; public OperationBase(SimpleStateManager stateManager, TimeSpan timeout, AsyncCallback callback, object state) : base(callback, state) { this.stateManager = stateManager; timeoutHelper = new TimeoutHelper(timeout); this.callback = callback; invokeOperation = false; completed = false; } void AsyncComplete(object o) { try { base.Complete(false, (Exception)o); } catch (Exception e) { if (Fx.IsFatal(e)) throw; throw DiagnosticUtility.ExceptionUtility.ThrowHelperCallback(SR.GetString(SR.AsyncCallbackException), e); } } protected abstract void Run(); void IOperation.Run() { Run(); } protected void Complete(Exception exception) { if (completed) { return; } lock (ThisLock) { if (completed) { return; } completed = true; } try { if (callback != null) { // complete the AsyncResult on a separate thread so that the queue can progress. // this prevents a deadlock when the callback attempts to call Close. // this may cause the callbacks to be called in a differnet order in which they completed, but that // is ok because each callback is associated with a different object (channel or listener factory) ActionItem.Schedule(new Action(AsyncComplete), exception); } else { AsyncComplete(exception); } } catch (Exception e) { if (Fx.IsFatal(e)) throw; throw DiagnosticUtility.ExceptionUtility.ThrowHelperCallback(SR.GetString(SR.MessagePropagationException), e); } } protected object ThisLock { get { return stateManager.thisLock; } } static public void End(IAsyncResult result) { AsyncResult.End(result); } } // To serialize IP address change processing class IPAddressChangeOperation : IOperation { PeerNodeImplementation peerNode; public IPAddressChangeOperation(PeerNodeImplementation peerNode) { this.peerNode = peerNode; } void IOperation.Run() { peerNode.OnIPAddressChange(); } } } } }