Chapter 2: A Channel State Machine

2.1A Common State Machine

Objects that deal with communication, e.g. sockets, usually present a state machine whose state transitions relate to allocating network resources, making or accepting connections, closing connections and aborting communication. The channel state machine provides a uniform model of the states of a communication object that abstracts the underlying implementation of that object. The interface provides a set of states, state transition methods, and state transition events. All channels, channel factories and channel listeners implement the channel state machine.

 

2.2ICommunicationObject

ICommunicationObject is a CLR interface that describes the channel state machine contract:

public interface ICommunicationObject : IDisposable

{

    CommunicationState State { get; }

 

    event EventHandler Closed;

    event EventHandler Closing;

    event EventHandler Faulted;

    event EventHandler Opened;

    event EventHandler Opening;

 

    void Abort();

 

    void Close();

    void Close(TimeSpan timeout);

    IAsyncResult BeginClose(AsyncCallback callback, object state);

    IAsyncResult BeginClose(TimeSpan timeout,

                            AsyncCallback callback, object state);

    void EndClose(IAsyncResult result);

 

    void Open();

    void Open(TimeSpan timeout);

    IAsyncResult BeginOpen(AsyncCallback callback, object state);

    IAsyncResult BeginOpen(TimeSpan timeout,

                           AsyncCallback callback, object state);

    void EndOpen(IAsyncResult result);

}

The events Closed, Closing, Faulted, Opened and Opening signal an external observer after a state transition occurs.

The methods Abort, Close, and Open (and their async equivalents) cause state transitions.

The state property returns the current state as defined by CommunicationState:

public enum CommunicationState

{

    Created,

    Opening,

    Opened,

    Closing,

    Closed,

    Faulted

}

An ICommunicationObject starts out in the Created state where it’s various properties can be configured. Once in the Opened state, the object is usable for sending and/or receiving messages but its properties are considered immutable. If an unrecoverable error occurs, the object transitions to the Faulted state where it can be inspected for information about the error and ultimately closed. When in the Closed state the object has essentially reached the end of the state machine. In general, once an object transitions from one state to the next, it does not go back to a previous state.

 

2.2.1States and Transition

Figure X shows the ICommunicationObject states and state transitions. State transitions can be caused by calling one of the three methods: Abort, Open, or Close. They could also be caused by calling other implementation-specific methods. Transitioning to the Faulted state could happen as a result of errors while opening or after having opened the communication object.

Every ICommunicationObject starts out in the Created state. In this state, an application can configure the object by setting its properties (e.g. setting the address of a channel listener by calling its SetUri method). Once an object is in a state other than Created, it is considered immutable. For example, calling SetUri on a channel listener in the Opened state should throw an InvalidOperationException.

 

 

 

2.3CommunicationObject   

Indigo provides an abstract base class named CommunicationObject that implements ICommunicationObject and the channel state machine. Figure X is a modified state diagram that’s specific to CommunicationObject. In addition to the ICommunicationObject state machine, figure X shows the timing when additional CommunicationObject methods are invoked.

 

 

2.3.1ICommunicationObject Events

CommunicationObject exposes the five events defined by ICommunicationObject. These events are designed for code using the communication object to be notified of state transitions. As shown in figure X, each event is fired once after the object’s state transitions to the state named by the event. All five events are of the EventHandler type which is defined as:

 

public delegate void EventHandler(object sender, EventArgs e);

 

In the CommunicationObject implementation, the sender is either the CommunicationObject itself or whatever was passed in as the sender to the CommunicationObject constructor (if that constructor overload was used). The EventArgs parameter, e, is always EventArgs.Empty.

 

2.3.2Derived Object Callbacks

In addition to the five events, CommunicationObject declares eight OnXxx protected virtual methods designed to allow a derived object to be called back before and after state transitions occur.

The Open() and Close() methods have three such callbacks associated with each of them, namely OnOpening(), OnOpen() , OnOpened() and OnClose(), OnClosing(), OnClosed().

Similarly, the Abort() method has a corresponding OnAbort() callback and Fault() has a corresponding OnFault(). Figure X shows when each of these callbacks is called and the state of the CommunicationObject at that time.

While OnOpen(), OnClose() and OnAbort() have no default implementation, the other callbacks do have a default implementation which is necessary for state machine correctness. If you override these methods be sure to either delegate to the base implementation or replace it.

OnOpening(), OnClosing() and OnFaulted() fire the corresponding Opening, Closing and Faulted() events. OnOpened() and OnClosed() set the object state to Opened and Closed respectively then fire the corresponding Opened and Closed events.

2.3.3State Transition Methods

CommunicationObject provides implementations of Abort, Close and Open. It also provides a Fault method which causes a state transition to the Faulted state. Figure X shows the ICommunicationObject state machine with each transition labeled by the method that causes it (unlabeled transitions happen inside the implementation of the method that caused the last labeled transition).

 

Note: All CommunicationObject implementation of communication state gets/sets are thread synchronized.

 

2.3.3.1     Constructor

CommunicationObject provides three constructors all of which leave the object in the Created state. The constructors are defined as:

The first constructor is a default constructor that delegates to the constructor overload that takes an object:

 

protected CommunicationObject() : this(new object()) { … }

 

The constructor that takes an object uses that parameter as the object to be locked when synchronizing access to communication object state:

 

protected CommunicationObject(object mutex) { … }

 

Finally, a third constructor takes an additional parameter which is used as the sender argument when ICommunicationObject events are fired.

 

protected CommunicationObject(object mutex, object eventSender) { … }

 

The previous two constructors set the sender to this.

2.3.3.2     Open Method

Precondition: State is Created.

Postcondition: State is Opened or Faulted. May throw an exception.

The Open() method will try to open the communication object and set the state to Opened. If it encounters an error, it will set the state to Faulted.

The method first checks that the current state is Created. If the current state is Opening or Opened it throws an InvalidOperationException. If the current state is Closing or Closed, it throws a CommunicationObjectAbortedException if the object has been aborted and ObjectDisposedException otherwise. If the current state is Faulted, it throws a CommunicationObjectFaultedException.

It then sets the state to Opening and calls OnOpening() (which raises the Opening event), OnOpen() and OnOpened() in that order. OnOpened() sets the state to Opened and raises the Opened event. If any of these throws an exception, Open() will call Fault() and will let the exception bubble up.

 

Override the OnOpen method to implement custom open logic..

 

2.3.3.3     Close Method

Precondition: None.

Postcondition: State is Closed. May throw an exception.

The Close() method can be called at any state. It will try to close the object normally. If an error is encountered, it will abort the object. The method will do nothing if the current state is Closing or Closed. Otherwise it will set the state to Closing. If the original state was Created, Opening or Faulted, it calls Abort() (see below). If the original state was Opened, it calls OnClosing() (which raises the Closing event), OnClose() and OnClosed() in that order. If any of these throws an exception, Close() will call Abort() and let the exception bubble up. OnClosed() sets the state to Closed and raises the Closed event.

Override the OnClose method to implement custom close logic. All graceful closing logic that may block for a long time (e.g. waiting for the other side to respond) should be implemented in OnClose() because it takes a timeout parameter and because it is not called as part of Abort().

2.3.3.4     Fault

Precondition: None.

Postcondition: State is Faulted. May throw an exception.

The Fault() method will do nothing if the current state is Faulted or Closed. Otherwise it will set the state to Faulted and call OnFaulted() which raises the Faulted event. If OnFaulted throws an exception it will bubble up.

2.3.3.5     Abort

Precondition: None.
Postcondition: State is Closed. May throw an exception.

The Abort() method will do nothing if the current state is Closed or if the object has been aborted before (e.g. possibly by having Abort() executing on another thread). Otherwise it sets the state to Closing and calls OnClosing() (which raises the Closing event), OnAbort(), and OnClosed() in that order (does not call OnClose because the object is being aborted not closed). OnClosed() sets the state to Closed and raises the Closed event. If any of these throw an exception, it will bubble up. Implementations of OnClosing(), OnClosed() and OnAbort() should not block on I/O.

 

Override the OnAbort method to implement custom abort logic.

 

 

2.3.4ThrowIfXxx Methods

CommunicationObject has three protected methods that can be used to throw if the object is in a specific state.

ThrowIfDisposed throws if the state is Closing, Closed or Faulted.

ThrowIfDisposedOrImmutable throws if the state is not Created.

ThrowIfDisposedOrNotOpen throws if the state is not Opened.

The exceptions thrown depend on the state. Table X shows the different states and the corresponding exception type thrown by calling a ThrowIfXxx that throws on that state.

 

State

Has Abort been called?

Exception

Created

N/A

InvalidOperationException

Opening

N/A

InvalidOperationException

Opened

N/A

InvalidOperationException

Closing

Yes

CommunicationObjectAbortedException

Closing

No

ObjectDisposedException

Closed

Yes

CommunicationObjectAbortedException

Closed

No

ObjectDisposedException

Faulted

N/A

CommunicationObjectFaultedException

 

2.3.5Timeouts

Several of the methods we discussed take timeout parameters. These are Close, Open (certain overloads and async versions), OnClose and OnOpen. These methods are designed to allow for lengthy operations (e.g. blocking on I/O while gracefully closing down a connection) so the timeout parameter indicates how long such operations can take before being interrupted. Implementations of any of these methods should use the supplied timeout value to ensure it returns to the caller within that timeout. Implementations of other methods that do not take a timeout are not designed for lengthy operations and should not block on I/O.

The exception are the Open() and Close() overloads that don’t take a timeout. These use a default timeout value supplied by the derived class. CommunicationObject exposes two protected abstract properties named DefaultCloseTimeout and DefaultOpenTimeout defined as:

 

protected abstract TimeSpan DefaultCloseTimeout { get; }

protected abstract TimeSpan DefaultOpenTimeout { get; }

 

A derived class implements these properties to provide the default timeout for the Open() and Close() overloads that do not take a timeout value. Then the Open() and Close() implementations simply delegate to the overload that takes a timeout passing it the default timeout value, e.g.

 

public void Open()

{

   this.Open(this.DefaultOpenTimeout);

}

 

2.4Summary

ICommunicationObject is the fundamental contract that all communication-related objects implement in WCF. This contract defines a state machine that includes specific states, methods to transition between those states, and semantics for how the transitions should occur. CommunicationObject is a useful implementation of this contract that custom channels can derive from.