Section 8.2. Service Concurrency Mode


8.2. Service Concurrency Mode

Concurrent access to the service instance is governed by the ConcurrencyMode property of the ServiceBehavior attribute:

 public enum ConcurrencyMode {    Single,    Reentrant,    Multiple } [AttributeUsage(AttributeTargets.Class)] public sealed class ServiceBehaviorAttribute : ... {    public ConcurrencyMode ConcurrencyMode    {get;set;}    //More members } 

The value of the ConcurrencyMode enum controls if and when concurrent calls are allowed on the service instance.

8.2.1. ConcurrencyMode.Single

When the service is set with ConcurrencyMode.Single, WCF will provide automatic synchronization to the service instance and disallow concurrent calls by associating the service instance with a synchronization lock. Every call coming into the service must first try to acquire the lock. If the lock is unowned, the caller will lock the lock and be allowed in. Once the operation returns, WCF will unlock the lock and thus allow another caller in. The important thing is that only one caller at a time is ever allowed. If there are multiple concurrent callers while the lock is locked, the callers are all placed in a queue, and are served out of the queue in order. If the call times out while blocked, WCF will remove the caller from the queue and the client will get a TimeoutException. ConcurrencyMode.Single is the WCF default setting, so these definitions are equivalent:

 [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] class MyService : IMyContract {...} [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall,                  ConcurrencyMode = ConcurrencyMode.Single)] class MyService : IMyContract {...} 

Because the default concurrency mode is synchronized access, the susceptible instancing modes of per-session and singleton are also synchronized by default:

 [ServiceContract(SessionMode = SessionMode.Required)] interface IMyContract {...} //These stateful services are thread-safe class MyService1 : IMyContract {...} [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)] class MyService2 : IMyContract {...} [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)] class MySingleton : IMyContract {...} 

In the case of a sessionful or singleton service, keep the duration of the operation execution short and avoid blocking clients for long. Because the service instance is synchronized, if the operation takes a while to complete, you risk timing out pending callers.


8.2.1.1. Synchronized access and transactions

As explained in Chapter 7, WCF will verify at the service load time that if at least one operation on the service has transactionScopeRequired set to true and ReleaseServiceInstanceOnTransactionComplete is TRue, the service concurrency mode must be ConcurrencyMode.Single. This is done deliberately to ensure that the service instance can be recycled at the end of the transaction without having another thread accessing the disposed instance.

8.2.2. ConcurrencyMode.Multiple

When the service is set with ConcurrencyMode.Mutiple, WCF will stay out of the way and will not synchronize access in any way to the service instance. ConcurrencyMode.Mutiple simply means that the service instance is not associated with any synchronization lock, so concurrent calls are allowed on the service instance. Put differently, when a service instance is configured with ConcurrencyMode.Mutiple, WCF will not queue up the client messages and dispatch them to the service instance as soon as they arrive.

A large number of concurrent client calls will not result in a matching number of concurrently executing calls on the service. The maximum number of concurrent calls dispatched to the service is the product of the configured maximum concurrent calls' throttled value. As mentioned in Chapter 4, the default max concurrent calls value is 16.


Obviously, this is of great concern to sessionful and singleton services, but also sometimes to a per-call service, as you will see later on. Such services must manually synchronize access to their state. The common way of doing that is to use .NET locks such as Monitor or a WaitHandle-derived class. Manual synchronization is not for the faint of heart and is covered in great depth in Chapter 8 of my book Programming .NET Components (O'Reilly). Manual synchronization does enable the service developer to optimize the throughput of client calls on the service instance, because you can lock the service instance just when and where synchronization is required, thus allowing other clients calls on the same service instance in between the synchronized sections. Such a manually synchronized service is shown in Example 8-1.

Example 8-1. Manual synchronization using fragmented locking

 [ServiceContract(SessionMode = SessionMode.Required)] interface IMyContract {    void MyMethod( ); } [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)] class MyService : IMyContract {    int[] m_Numbers;    List<string> m_Names;    public void MyMethod( )    {       lock(m_Numbers)       {        ...       }       /* Don't access members here */       lock(m_Names)       {        ...       }    } } 

The service in Example 8-1 is configured for concurrent access. Since the critical sections of the operations that require synchronization are any member variable access, the service uses a Monitor (encapsulated in the lock statement) to lock the object before accessing it. Local variables require no synchronization because they are visible only to the thread that created them on its own call stack. The problem with the technique shown in Example 8-1 is that it is deadlock- and error-prone. It only provides for thread-safe access if every other operation on the service is as disciplined to always lock the members before accessing them. But even if all operations lock all members, you may still risk deadlocksif one operation on Thread A locks member M1 trying to access member M2 while another operation executing concurrently on Thread B locks member M2 while trying to access member M1, you will end up with a deadlock.

WCF resolves service calls deadlock by eventually timing out the call and throwing a TimeoutException. Avoid using a long timeout as it decreases WCF's ability to resolve deadlocks in a timely manner.


I therefore recommend avoiding fragmented locking. It is better to lock the entire service instance instead:

 public void MyMethod( ) {    lock(this)    {       ...    }    /* Don't access members here */    lock(this)    {       ...    } } 

The problem with this approach is that it is still fragmented and thus error-proneif at some point in the future someone adds a method call in the unsynchronized code section that does access the members, it will not be a synchronized access. It is better still to lock the entire body of the method:

 public void MyMethod( ) {    lock(this)    {       ...    } } 

You can even instruct .NET to automate injecting the call to lock the instance using the MethodImpl attribute with the MethodImplOptions.Synchronized flag:

 [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)] class MyService : IMyContract {    int[] m_Numbers;    List<string> m_Names;    [MethodImpl(MethodImplOptions.Synchronized)]    public void MyMethod( )    {       ...    } } 

You will need to repeat the assignment of the MethodImpl attribute on all the service operations implementations.

The problem now is that while the code is thread-safe, you gain little from the use of ConcurrencyMode.Multiple because the net effect will be similar to using ConcurrencyMode.Single in terms of synchronization, yet you have increased the overall code complexity and reliance on developers' discipline. There are cases where just such a configuration is required, howeverin particular when callbacks are involved, as you will see later on.

8.2.2.1. Unsynchronized access and transactions

When the service is configured for ConcurrencyMode.Multiple, if at least one operation has transactionScopeRequired set to true, then ReleaseServiceInstanceOnTransactionComplete must be set to false. For example, this is a valid definition because no method has transactionScopeRequired set to true even though ReleaseServiceInstanceOnTransactionComplete defaults to true:

 [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)] class MyService : IMyContract {    public void MyMethod( )    {...}    public void MyOtherMethod( )    {...} } 

The following, on the other hand, is an invalid definition because at least one method has transactionScopeRequired set to true:

 //Invalid configuration: [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)] class MyService : IMyContract {    [OperationBehavior(TransactionScopeRequired = true)]    public void MyMethod( )    {...}    public void MyOtherMethod( )    {...} } 

A transactional unsynchronized service must set ReleaseServiceInstanceOnTransactionComplete explicitly to false:

 [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple,                  ReleaseServiceInstanceOnTransactionComplete = false)] class MyService : IMyContract {    [OperationBehavior(TransactionScopeRequired = true)]    public void MyMethod( )    {...}    public void MyOtherMethod( )    {...} } 

The rationale behind this constraint is that only a sessionful or a singleton service could possibly benefit from unsynchronized access, so in the case of transactional access, WCF wants to enforce the semantic of the configured instancing mode. In addition, this will avoid having a caller accessing the instance, completing the transaction and releasing the instance all while the instance is still being used by another caller.

8.2.3. ConcurrencyMode.Reentrant

The ConcurrencyMode.Reentrant value is a refinement of ConcurrencyMode.Single. Similar to ConcurrencyMode.Single, ConcurrencyMode.Reentrant associates the service instance with a synchronization lock, so concurrent calls on the same instance are never allowed. However, if the reentrant service calls out to another service or a callback, and that call chain (or causality) somehow winds its way back to the service instance as shown in Figure 8-1, then that call is allowed to reenter the service instance.

Figure 8-1. Call reentrancy


ConcurrencyMode.Reentrant implementation is very simplewhen the reentrant service calls out over WCF, WCF silently releases the synchronization lock associated with the instance. ConcurrencyMode.Reentrant is designed to avoid the potential deadlock of reentrancy. If the service were to maintain the lock while calling out, then if the causality tried to enter the same instance, a deadlock would occur.

Reentrancy support is instrumental in a number of scenarios:

  • A singleton service calling out risks a deadlock if any of the downstream services it calls try to call back into the singleton.

  • In the same app domain, if the client stores a proxy reference in some globally available static variable, then some of the downstream services called by the referenced service use the proxy reference to call back to the original service.

  • Callbacks on non-one-way operations must be allowed to reenter the calling service.

  • If the callout the service performs is of long duration, even without reentrancy, you may want to optimize throughput by allowing other clients to use the same service instance.

A service configured with ConcurrencyMode.Multiple is by definition also reentrant, because no lock is held during the callout. However, unlike a reentrant service, which is inherently thread-safe, a service with ConcurrencyMode.Multiple must provide for its own synchronization, such as by locking the instance during every call, as explained previously. It is up to the developer of such a service to decide if it wants to release the lock of the instance before calling out to avoid reentrancy deadlock.


8.2.3.1. Designing for reentrancy

It is very important to realize the liability associated with reentrancy. First, when the reentrant service calls out, it must leave the service state in a workable consistent state, because others could be allowed into the service instance while the service calls out. A consistent thread-safe state means that the reentrant service has no more interactions with its own members or any other local object or static variable, and that when the callout returns, the reentrant service could simply return control to its client. For example, suppose the reentrant service modified the state of some linked list, and leaves it in an inconsistent state (such as missing a head node) because it needs the value of the new head from another service. The reentrant service then calls to the other service, but now it leaves other clients vulnerable, because if they call into the reentrant service and access the linked list, they will encounter an error.

Much the same way, when the reentrant service returns from its callout, it must refresh all local method state. For example, if the service has a local variable that contains a copy of the state of a member variable, that local variable may have the wrong value now because during the callout another party could have entered the reentrant service and modified the member variable.

8.2.3.2. Reentrancy and transactions

A reentrant service faces exactly the same design constraints regarding transactions as a service configured with ConcurrencyMode.Multiple; namely, if at least one operation has TRansactionScopeRequired set to true, then ReleaseServiceInstanceOnTransactionComplete must be set to false.

8.2.3.3. Callbacks and reentrancy

Callbacks are the main reason reentrancy is available. As explained in Chapter 5, if a WCF service wants to invoke a duplex callback to its calling client, the service requires reentrancy (or no synchronization at all via ConcurrencyMode.Multiple). The reason is that processing the reply message from the client once the callback returns requires ownership of the instance lock, and so a deadlock would occur if a service with ConcurrencyMode.Single were allowed to call back to its clients. To allow callbacks, the service must be configured with either ConcurrencyMode.Multiple or preferably ConcurrencyMode.Reentrant. This is required even of a per-call service, which otherwise has no need for anything but ConcurrencyMode.Single. Note that the service may still invoke callbacks to other clients or call other services. It is the callback to the calling client that is disallowed.

If a service configured with ConcurrencyMode.Single TRies to invoke a duplex callback, WCF will throw an InvalidOperationException. Example 8-2 demonstrates a service configured for reentrancy. During the operation execution, the service calls back to its client. Control will only return to the service once the callback returns, and the service's own thread will need to reacquire the lock.

Example 8-2. Configure for reentrancy to allow callbacks

 [ServiceContract(CallbackContract = typeof(IMyContractCallback))] interface IMyContract {    [OperationContract]    void DoSomething( ); } interface IMyContractCallback {    [OperationContract]    void OnCallback( ); } [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant)] class MyService : IMyContract {    public void DoSomething( )    {       IMyContractCallback callback = OperationContext.Current.                                          GetCallbackChannel<IMyContractCallback>( );       callback.OnCallback( );    } } 

As also mentioned in Chapter 5, the only case where a service configured with ConcurrencyMode.Single can call back to its clients is when the callback contract operation is configured as one-way because there will not be any reply message to contend for the lock.




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