Section 9.4. Instance Management


9.4. Instance Management

The contract session mode and the service instance mode have a paramount effect on the behavior of the queued calls, the way the calls are played back to the service, and implications on the overall program workflow and allowed assumptions.

9.4.1. Per-Call Queued Services

In the case of a per-call service, the client has no way of knowing that the calls will eventually end up played to a queued per-call service. All that the client sees is the session mode on the contract. If the SessionMode on the contract is either SessionMode.Allowed or SessionMode.NotAllowed, the queued call (over the NetMsmqBinding) is considered to be sessionless. All calls to such sessionless endpoints are placed in separate MSMQ messages, where each call on the proxy yields a single separate MSMQ message.

9.4.1.1. Nontransactional clients

When a client without an ambient transaction calls a sessionless queued endpoint (as in Example 9-11), the MSMQ messages generated for each call are posted to the queue immediately after each call. If the client has an exception, the messages posted up to that point are not rejected and are delivered to the service.

Example 9-11. Nontransactional client of a sessionless queued endpoint

 using(TransactionScope scope =                              new TransactionScope(TransactionScopeOption.Suppress)) {    MyContractClient proxy = new MyContractClient( );    proxy.MyMethod( );//Message posts to queue here    proxy.MyMethod( );//Message posts to queue here    proxy.Close( ); } 

9.4.1.2. Transactional clients

With a transactional client (that is, a client code with an ambient transaction) of a sessionless queued endpoint (as in Example 9-12), all messages (corresponding to each call) are posted to the queue when the client's transaction commits. If the client transaction aborts, all messages are rejected from the queue and all calls are canceled.

Example 9-12. Transactional client of a queued sessionless endpoint

 using(TransactionScope scope = new TransactionScope( )) {    MyContractClient proxy = new MyContractClient( );    proxy.MyMethod( );//Message written to queue    proxy.MyMethod( );//Message written to queue    proxy.Close( );    scope.Complete( ); }//Messages committed to queue here 

There is no relationship between the proxy and the ambient transaction. If the client uses a transaction scope (as in Example 9-12), the client can close the proxy inside or outside the scope, and may continue to use the proxy even after the transaction or in a new transaction. The client may also close the proxy before or after the call to Complete( ).

9.4.1.3. Per-call processing

On the host side, the queued calls are dispatched separately to the service, and each call is played to a separate service instance. This is the case even if the service instance mode is per-session. I therefore recommend in the interest of readability that when using a sessionless queued contract, you should always explicitly configure the service as per-call and the contract for disallowing sessions:

 [ServiceContract(SessionMode = SessionMode.NotAllowed)] interface IMyContract {...} [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] class MyService : IMyContract {...} 

After each call the service instance is disposed of, just as with a connected per-call service. The per-call service may or may not be transactional, and if it is transactional and the playback transaction is aborted, only that particular call is rolled back to the queue for a retry. As you will see later on, due to the concurrent playback and due to WCF failure-handling behavior, calls to a per-call queued service can execute and complete in whichever order, and the client cannot make any assumptions about call ordering. Note that even calls dispatched by a transactional client may fail or succeed independently. Never assume order of calls with a per-call queued service.

9.4.2. Sessionful Queued Services

To develop and configure a sessionful queued service, the service contract must be configured with SessionMode.Required:

 [ServiceContract(SessionMode = SessionMode.Required)] interface IMyContract {...} class MyService : IMyContract {...} 

When the client queues up calls against a sessionful queued endpoint, all calls made throughout the session are grouped into a single MSMQ message. Once that single message is dispatched and played to the service, WCF creates a new dedicated service instance to handle all the calls in the message. All calls in the message are played back in the original order to that instance. After the last call, the instance is disposed of automatically, since there is no meaning for the client's call to close the proxy. On both the client and the service side, WCF will provide the client and the service with a unique session ID. However, the client session ID will be uncorrelated to that of the service. To approximate the session semantic, all calls on the same instance on the host side will share the same session ID.

9.4.2.1. Clients and transactions

In the case of a sessionful queued endpoint, the client must have an ambient transaction in order to call the proxy, and a nontransactional client is disallowed, resulting in an InvalidOperationException:

 [ServiceContract(SessionMode = SessionMode.Required)] interface IMyContract {    [OperationContract(IsOneWay = true)]    void MyMethod( ); } using(TransactionScope scope =                              new TransactionScope(TransactionScopeOption.Suppress)) {    MyContractClient proxy = new MyContractClient( );    proxy.MyMethod( );//Throws InvalidOperationException    proxy.MyMethod( );    proxy.Close( ); } 

In the case of a transactional client of a sessionful queued endpoint, WCF posts a single message to the queue when the transaction commits, and that single message is rejected from the queue if the transaction aborts:

 using(TransactionScope scope = new TransactionScope( )) {    MyContractClient proxy = new MyContractClient( );    proxy.MyMethod( );    proxy.MyMethod( );    proxy.Close( );//Finish composing message, writes to queue    scope.Complete( ); }//Single message committed to queue here 

It is important to note that the single message prepared by the proxy must be posted to the queue within the same client transactionthe client must end the session inside the transaction. If the client does not close the proxy before the transaction is complete, the transaction will always abort:

 MyContractClient proxy = new MyContractClient( ); using(TransactionScope scope = new TransactionScope( )) {    proxy.MyMethod( );    proxy.MyMethod( );    scope.Complete( ); }//Transaction aborts proxy.Close( ); 

An interesting side effect of this behavior is that there is no point in storing a proxy to a queued sessionful endpoint in a member variable, because that proxy can only be used once in a single transaction, and cannot be reused across client transactions. The client can only use the proxy inside a single transaction and then close it.

Not only does the client have to close the proxy before the transaction ends, but when using a transaction scope, the client must close the proxy before completing the transaction. The reason is that closing the proxy to a queue's sessionful endpoint requires accessing the current ambient transaction, which is not possible after calling Complete( ). TRying to do so results with InvalidOperationException:

 MyContractClient proxy = new MyContractClient( ); using(TransactionScope scope = new TransactionScope( )) {    proxy.MyMethod( );    proxy.MyMethod( );    scope.Complete( );    proxy.Close( ); //Transaction aborts } 

A corollary of this requirement is that you cannot easily stack using statements, because those may result in calling Dispose( ) in the wrong order, first on the scope, and then on the proxy:

 using(MyContractClient proxy = new MyContractClient( )) using(TransactionScope scope = new TransactionScope( )) {    proxy.MyMethod( );    proxy.MyMethod( );    scope.Complete( ); }//Transaction aborts 

9.4.2.2. Services and transactions

A sessionful queued service must be configured to use transactions in all operations by setting TRansactionScopeRequired to TRue. Failing to do so will abort all playback transactions. In addition, the service must provide for transactional affinity to the instance by setting transactionAutoComplete to false in all but the last operation in the session. Due to a design flaw of WCF, the service cannot rely on setting transactionAutoCompleteOnSessionClose to true, and must have the last method call in the session complete the transaction, either automatically or manually. Example 9-13 is a template for implementing a queued sessionful service, assuming MyMethod3( ) is the last operation call in the session.

Example 9-13. Implementing a sessionful queued service

 [ServiceContract(SessionMode = SessionMode.Required)] interface IMyContract {    [OperationContract(IsOneWay = true)]    void MyMethod1( );    [OperationContract(IsOneWay = true)]    void MyMethod2( );    [OperationContract(IsOneWay = true)]    void MyMethod3( ); } class MyService : IMyContract {    [OperationBehavior(TransactionScopeRequired = true,                       TransactionAutoComplete = false)]    public void MyMethod1( )    {...}    [OperationBehavior(TransactionScopeRequired = true,                       TransactionAutoComplete = false)]    public void MyMethod2( )    {...}    [OperationBehavior(TransactionScopeRequired = true)]    public void MyMethod3( )    {...} } 

Obviously, baking into your service code the assumption that a particular method will be the last in the session is often impractical. You can somewhat alleviate this using demarcating operations, and designate the method that terminates the session. Using the same definition as in Example 9-13, you could write:

 [ServiceContract(SessionMode = SessionMode.Required)] interface IMyContract {    [OperationContract(IsOneWay = true,IsTerminating = false)]    void MyMethod1( );    [OperationContract(IsOneWay = true,IsInitiating = false,IsTerminating = false)]    void MyMethod2( );    [OperationContract(IsOneWay = true,IsInitiating = false,IsTerminating = true)]    void MyMethod3( ); } 

But even that is not a sure cure if the client never calls the terminating operation, in which case the session will abort its transaction. Yet another option is to add an explicit CompleteTransaction( ) operation to the contract whose sole purpose is to complete the transaction and end the session. You need to explicitly document the need to call this method at the end of the session:

 [ServiceContract(SessionMode = SessionMode.Required)] interface IMyContract {    [OperationContract(IsOneWay = true)]    void MyMethod1( );    [OperationContract(IsOneWay = true)]    void MyMethod2( );    [OperationContract(IsOneWay = true)]    void CompleteTransaction( ); } class MyService : IMyContract {    [OperationBehavior(TransactionScopeRequired = true,                       TransactionAutoComplete = false)]    public void MyMethod1( )    {...}    [OperationBehavior(TransactionScopeRequired = true,                       TransactionAutoComplete = false)]    public void MyMethod2( )    {...}    [OperationBehavior(TransactionScopeRequired = true)]    public void CompleteTransaction( )    {...} } 

That said, avoid a queued sessionful serviceit is brittle and introduces coupling.

9.4.3. Singleton Service

A queued transactional singleton service can never have a session and can only implement sessionless contracts. Configuring the SessionMode to either SessionMode.Allowed or SessionMode.NotAllowed has the same result: a sessionless interaction. As a result, I recommend always explicitly configuring the contracts of a queued singleton as sessionless:

 [ServiceContract(SessionMode = SessionMode.NotAllowed)] interface IMyContract {...} [ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)] class MyService : IMyContract {...} 

A nontransactional queued singleton service behaves like a regular WCF singleton as far as instancing. Regardless of client and proxies, individual calls on the proxies are packaged into separate MSMQ messages and dispatched separately to the singleton, similar to a per-call service. However, unlike the per-call service, all these calls will be played back to the same single instance.

When it comes to a transactional queued singleton, by default it behaves like a per-call service because after every call that completes the transaction, WCF will release the singleton instance. The only difference between a true per-call service and a singleton is that WCF will only allow at most a single instance of the singleton regardless of the number of queued messages. While you could apply the techniques described in Chapter 7 for a state-aware transactional singleton, you can also restore the singleton semantic by setting the ReleaseServiceInstanceOnTransactionComplete property to false. Recall from Chapter 7 that this also constrains the singleton to have at least one operation with TRansactionScopeRequired set to true. Example 9-14 shows a template for implementing a transactional queued singleton.

Example 9-14. Transactional queued singleton

 [ServiceContract(SessionMode = SessionMode.NotAllowed)] interface IMyContract {    [OperationContract(IsOneWay = true)]    void MyMethod( ); } [ServiceBehavior(InstanceContextMode=InstanceContextMode.Single,                  ReleaseServiceInstanceOnTransactionComplete = false)] class MySingleton : IMyContract,IDisposable {    [OperationBehavior(TransactionScopeRequired = true)]    public void MyMethod( )    {...}    //More members } 

A transactional singleton cannot implement a sessionful contract unless all clients only make a single call in each session, which of course renders the whole idea of a session pointless. The reason for this is that only a per-session service can turn off auto-completion on multiple methods in the same session. Avoid a sessionful transactional queued singleton.


9.4.3.1. Calls and order

Because the calls are packaged into individual MSMQ messages, the calls may be played in any order to the singleton due to retries and transactions. In addition, calls could also complete in any order, and even calls dispatched by a transactional client may fail or succeed independently. Never assume order of calls with a singleton.




Programming WCF Services
Programming WCF Services
ISBN: 0596526997
EAN: 2147483647
Year: 2004
Pages: 148
Authors: Juval Lowy

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net