Instance Management and Transactions

As hinted previously, the transactional configuration of the service is intimately related to the service instance lifecycle, and it drastically changes the programming model. All transactional services must store their state in a resource manager or managers. Those resource managers could be volatile or durable, shared between the instances or per instance, and could support multiple services, all according to your design of both the service and its resources.

Per-Call Transactional Services

With a per-call service, once the call returns, the instance is destroyed. Therefore, the resource manager used to store the state between calls must be outside the scope of the instance. Because there could be many instances of the same service type accessing the same resource manager, every operation must contain some parameters that allow the service instance to find its state in the resource manager and bind against it. The best approach is to have each operation contain some key as a parameter identifying the state. I call that parameter the state identifier. The client must provide the state identifier with every call to the per-call service. Typical state identifiers are account numbers, order numbers, and so on. For example, the client creates a new transactional order-processing object, and on every method call the client must provide the order number as a parameter, in addition to other parameters.

Example 7-15 shows a template for implementing a transactional per-call service.

Example 7-15. Implementing a transactional service

[DataContract]
class Param
{...}

[ServiceContract]
interface IMyContract
{
   [OperationContract]
   [TransactionFlow(...)]
   void MyMethod(Param stateIdentifier);
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
class MyService : IMyContract,IDisposable
{
   [OperationBehavior(TransactionScopeRequired = true)]
   public void MyMethod(Param stateIdentifier)
   {
      GetState(stateIdentifier);
      DoWork(  );
      SaveState(stateIdentifier);
   }
   void GetState(Param stateIdentifier)
   {...}
   void DoWork(  )
   {...}
   void SaveState(Param stateIdentifier)
   {...}
   public void Dispose(  )
   {...}
}

The MyMethod( ) signature contains a state identifier parameter of the type Param (a pseudotype invented for this example), used to get the state from a resource manager with the GetState( ) helper method. The service instance then performs its work using the DoWork( ) helper method and saves its state back to the resource manager using the SaveState( ) method, specifying its identifier.

Note that not all of the service instance's state can be saved by value to the resource manager. If the state contains references to other objects, GetState( ) should create those objects, and SaveState( ) (or Dispose( )) should dispose of them.

Because the service instance goes through the trouble of retrieving its state and saving it on every method call, transactional programming is natural for per-call services. The behavioral requirements for a state-aware transactional object and a per-call object are the same: both retrieve and save their state at the method boundaries. Compare Example 7-15 with Example 4-3. The only difference is that the state store used by the service in Example 7-15 should be transactional.

A far as a per-call service call is concerned, transactional programming is almost incidental. Every call on the service gets a new instance, and that call may or may not be in the same transaction as the previous call (see Figure 7-8).

Per-call service and transactions

Figure 7-8. Per-call service and transactions

Regardless of transactions, in every call the service gets its state from a resource manager and then saves it back, so the methods are always guaranteed to operate either on consistent state from the previous transaction or on the temporary yet well-isolated state of the current transaction in progress. A per-call service must vote and complete its transaction in every method call. In fact, a per-call service must always use auto-completion (i.e., have TransactionAutoComplete set to its default value, true).

From the client's perspective, the same service proxy can participate in multiple transactions or in the same transaction. For example, in the following code snippet, every call will be in a different transaction:

MyContractClient proxy = new MyContractClient(  );

using(TransactionScope scope = new TransactionScope(  ))
{
   proxy.MyMethod(...);
   scope.Complete(  );
}
using(TransactionScope scope = new TransactionScope(  ))
{
   proxy.MyMethod(...);
   scope.Complete(  );
}

proxy.Close(  );

Or, the client can use the same proxy multiple times in the same transaction, and even close the proxy independently of any transactions:

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

Tip

The call to Dispose( ) on a per-call service has no ambient transaction.

With a per-call service, any resource manager can be used to store the service state. For example, you might use a database, or you might use volatile resource managers accessed as static member variables, as shown in Example 7-16.

Example 7-16. Per-call service using a VRM

[ServiceContract]
interface ICounterManager
{
   [OperationContract]
   [TransactionFlow(...)]
   void Increment(string stateIdentifier);
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
class MyService : ICounterManager
{
   static TransactionalDictionary<string,int> m_StateStore =
                                        new TransactionalDictionary<string,int>(  );

   [OperationBehavior(TransactionScopeRequired = true)]
   public void Increment(string stateIdentifier)
   {
      if(m_StateStore.ContainsKey(stateIdentifier) == false)
      {
         m_StateStore[stateIdentifier] = 0;
      }
      m_StateStore[stateIdentifier]++;
   }
}

The transaction lifecycle

When the per-call service is the root of a transaction (that is, when it is configured for the Client/Service transaction mode and there is no client transaction, or when it is configured for the Service transaction mode), the transaction ends once the service instance is deactivated. WCF completes and ends the transaction as soon as the method returns, even before Dispose( ) is called. When the client is the root of the transaction (or whenever the client's transaction flows to the service and the service joins it), the transaction ends when the client's transaction ends.

Per-Session Transactional Services

While it is possible to develop transactional sessionful services with great ease using my volatile resource managers, WCF was designed without them in mind, simply because these technologies evolved more or less concurrently. Consequently, the WCF architects did not trust developers to properly manage the state of their sessionful service in the face of transactions—something that is rather cumbersome and difficult, as you will see, if all you have at your disposal is raw .NET and WCF. The WCF architects made the extremely conservative decision to treat a sessionful transactional service as a per-call service by default in order to enforce a proper state-aware programming model. In fact, the default transaction configuration of WCF will turn any service, regardless of its instancing mode, into a per-call service. This, of course, negates the very need for a per-session service in the first place. That said, WCF does allow you to maintain the session semantic with a transactional service, using several distinct programming models. A per-session transactional service instance can be accessed by multiple transactions, or the instance can establish an affinity to a particular transaction, in which case, until it completes, only that transaction is allowed to access it. However, as you will see, unless you use volatile resource managers this support harbors a disproportional cost in programming model complexity and constraints.

Releasing the service instance

The lifecycle of any transactional service is controlled by the ServiceBehavior attribute's Boolean property, ReleaseServiceInstanceOnTransactionComplete:

[AttributeUsage(AttributeTargets.Class)]
public sealed class ServiceBehaviorAttribute : Attribute,...
{
   public bool ReleaseServiceInstanceOnTransactionComplete
   {get;set;}
   //More members
}

When ReleaseServiceInstanceOnTransactionComplete is set to true (the default value), it disposes of the service instance once the instance completes the transaction. WCF uses context deactivation (discussed in Chapter 4) to terminate the sessionful service instance and its in-memory state, while maintaining the transport session and the instance context.

Note that the release takes place once the instance completes the transaction, not necessarily when the transaction really completes (which could be much later). When ReleaseServiceInstanceOnTransactionComplete is true, the instance has two ways of completing the transaction and being released: at the method boundary if the method has TransactionAutoComplete set to true, or when any method that has TransactionAutoComplete set to false calls SetTransactionComplete( ).

ReleaseServiceInstanceOnTransactionComplete has two interesting interactions with other service and operation behavior properties. First, it cannot be set (to either true or false) unless at least one operation on the service has TransactionScopeRequired set to true. This is validated at the service load time by the set accessor of the ReleaseServiceInstanceOnTransactionComplete property.

For example, this is a valid configuration:

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

   [OperationBehavior(...)]
   public void MyOtherMethod(  )
   {...}
}

What this constraint means is that even though the default of ReleaseServiceInstanceOnTransactionComplete is true, the following two definitions are not semantically equivalent, because the second one will throw an exception at the service load time:

class MyService : IMyContract
{
   public void MyMethod(  )
   {...}
}

//Invalid definition:
[ServiceBehavior(ReleaseServiceInstanceOnTransactionComplete = true)]
class MyService : IMyContract
{
   public void MyMethod(  )
   {...}
}

The second constraint involved in using ReleaseServiceInstanceOnTransactionComplete relates to concurrent multithreaded access to the service instance.

Concurrency management is the subject of the next chapter. For now, all you need to know is that the ConcurrencyMode property of the ServiceBehavior attribute controls concurrent access to the service instance:

public enum ConcurrencyMode
{
   Single,
   Reentrant,
   Multiple
}

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

The default value of ConcurrencyMode is ConcurrencyMode.Single.

At the service load time, WCF will verify that, if TransactionScopeRequired is set to true for at least one operation on the service when ReleaseServiceInstanceOnTransactionComplete is true (by default or explicitly), the service concurrency mode is ConcurrencyMode.Single.

For example, given this contract:

[ServiceContract]
interface IMyContract
{
   [OperationContract]
   [TransactionFlow(...)]
   void MyMethod(  );

   [OperationContract]
   [TransactionFlow(...)]
   void MyOtherMethod(  );
}

the following two definitions are equivalent and valid:

class MyService : IMyContract
{
   [OperationBehavior(TransactionScopeRequired = true)]
   public void MyMethod(  )
   {...}

   public void MyOtherMethod(  )
   {...}
}

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

   public void MyOtherMethod(  )
   {...}
}

The following definition is also valid, since no method requires a transaction scope even though ReleaseServiceInstanceOnTransactionComplete is true:

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

   public void MyOtherMethod(  )
   {...}
}

In contrast, the following definition is invalid, because at least one method requires a transaction scope, ReleaseServiceInstanceOnTransactionComplete is true, and yet the concurrency mode is not ConcurrencyMode.Single:

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

   public void MyOtherMethod(  )
   {...}
}

Tip

The concurrency constraint applies to all instancing modes.

The ReleaseServiceInstanceOnTransactionComplete property can enable a transactional session interaction between the client and the service. With its default value of true, once the service instance completes the transaction (either declaratively or explicitly), the return of the method will deactivate the service instance as if it were a per-call service.

For example, the service in Example 7-17 behaves just like a per-call service.

Example 7-17. Per-session yet per-call transactional service

[ServiceContract(SessionMode = SessionMode.Required)]
interface IMyContract
{
   [OperationContract]
   [TransactionFlow(...)]
   void MyMethod(  );
}
class MyService : IMyContract
{
   [OperationBehavior(TransactionScopeRequired = true)]
   public void MyMethod(  )
   {...}
}

Every time the client calls MyMethod( ), the client will get a new service instance. The new client call may come in on a new transaction as well, and the service instance has no affinity to any transaction. The relationship between the service instances and the transactions is just as in Figure 7-8. The service needs to proactively manage its state just as it did in Example 7-15, as demonstrated in Example 7-18.

Example 7-18. Proactive state management by default with a per-session transactional service

[DataContract]
class Param
{...}

[ServiceContract(SessionMode = SessionMode.Required)]
interface IMyContract
{
   [OperationContract]
   [TransactionFlow(...)]
   void MyMethod(Param stateIdentifier);
}
class MyService : IMyContract
{
   [OperationBehavior(TransactionScopeRequired = true)]
   public void MyMethod(Param stateIdentifier)
   {
      GetState(stateIdentifier);
      DoWork(  );
      SaveState(stateIdentifier);
   }
   void GetState(Param stateIdentifier)
   {...}
   void DoWork(  )
   {...}
   void SaveState(Param stateIdentifier)
   {...}
}

The transactional per-session service can also, of course, use VRMs, as was done in Example 7-16.

Disabling releasing the service instance

Obviously, a configuration such as that in Example 7-17 or Example 7-18 adds no value to configuring the service as sessionful. The client must still pass a state identifier, and the service is de facto a per-class service. To behave as a per-session service, the service can set ReleaseServiceInstanceOnTransactionComplete to false, as in Example 7-19.

Example 7-19. Per-session transactional service

[ServiceContract(SessionMode = SessionMode.Required)]
interface IMyContract
{
   [OperationContract]
   [TransactionFlow(...)]
   void MyMethod(  );
}
[ServiceBehavior(ReleaseServiceInstanceOnTransactionComplete = false)]
class MyService : IMyContract
{
   [OperationBehavior(TransactionScopeRequired = true)]
   public void MyMethod(  )
   {...}
}

When ReleaseServiceInstanceOnTransactionComplete is false, the instance will not be disposed of when transactions complete, as shown in Figure 7-9.

Sessionful transactional instance and transactions

Figure 7-9. Sessionful transactional instance and transactions

The interaction in Figure 7-9 might, for example, be the result of the following client code, where all calls went to the same service instance:

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

using(TransactionScope scope = new TransactionScope(  ))
{
   proxy.MyMethod(  );
   proxy.MyMethod(  );
   scope.Complete(  );
}
proxy.Close(  );

State-aware per-session services

When ReleaseServiceInstanceOnTransactionComplete is false, WCF will stay out of the way and will let the service developer worry about managing the state of the service instance in the face of transactions. Obviously, you have to somehow monitor transactions and roll back any changes made to the state of the instance if a transaction aborts. The per-session service still must equate method boundaries with transaction boundaries, because every method may be in a different transaction, and a transaction may end between method calls in the same session. There are two possible programming models. The first is to be state-aware, but use the session ID as a state identifier. With this model, at the beginning of every method the service gets its state from a resource manager using the session ID as a key, and at the end of every method the service instance saves the state back to the resource manager, as shown in Example 7-20.

Example 7-20. State-aware, transactional per-session service

[ServiceBehavior(ReleaseServiceInstanceOnTransactionComplete = false)]
class MyService : IMyContract,IDisposable
{
   readonly string m_StateIdentifier;

   public MyService(  )
   {
      InitializeState(  );
      m_StateIdentifier = OperationContext.Current.SessionId;
      SaveState(  );
   }
   [OperationBehavior(TransactionScopeRequired = true)]
   public void MyMethod(  )
   {
      GetState(  );
      DoWork(  );
      SaveState(  );
   }
   public void Dispose(  )
   {
      RemoveState(  );
   }

   //Helper methods

   void InitializeState(  )
   {...}
   void GetState(  )
   {
      //Use m_StateIdentifier to get state
      ...
   }
   void DoWork(  )
   {...}
   void SaveState(  )
   {
      //Use m_StateIdentifier to save state
      ...
   }
   void RemoveState(  )
   {
      //Use m_StateIdentifier to remove the state from the RM
      ...
   }
}

In Example 7-20, the constructor first initializes the state of the object and then saves the state to a resource manager, so that any method can retrieve it. Note that the per-session object maintains the illusion of a stateful, sessionful interaction with its client. The client does not need to pass an explicit state identifier, but the service must be disciplined and retrieve and save the state in every operation call. When the session ends, the service purges its state from the resource manager in the Dispose( ) method.

Stateful per-session services

The second, more modern programming model is to use volatile resource managers for the service members, as shown in Example 7-21.

Example 7-21. Using volatile resource managers to achieve a stateful per-session transactional service

[ServiceBehavior(ReleaseServiceInstanceOnTransactionComplete = false)]
class MyService : IMyContract
{
   Transactional<string> m_Text = new Transactional<string>("Some initial value");

   TransactionalArray<int> m_Numbers = new TransactionalArray<int>(3);

   [OperationBehavior(TransactionScopeRequired = true)]
   public void MyMethod(  )
   {
      m_Text.Value = "This value will roll back if the transaction aborts";

      //These will roll back if the transaction aborts
      m_Numbers[0] = 11;
      m_Numbers[1] = 22;
      m_Numbers[2] = 33;
   }
}

Example 7-21 uses my Transactional<T> and TransactionalArray<T> volatile resource managers. The per-session service can safely set ReleaseServiceInstanceOnTransactionComplete to false and yet freely access its members. The use of the volatile resource managers enables a stateful programming model, and the service instance simply accesses its state as if no transactions were involved. The volatile resource managers auto-enlist in the transaction and isolate that transaction from all other transactions. Any changes made to the state will commit or roll back with the transaction.

Transaction lifecycle

When the per-session service is the root of the transaction, the transaction ends once the service completes the transaction, which is when the method returns. When the client is the root of the transaction (or when a transaction flows to the service), the transaction ends when the client's transaction ends. If the per-session service provides an IDisposable implementation, the Dispose( ) method will not have any transaction, regardless of the root.

Concurrent transactions

Because a per-session service can engage the same service instance in multiple client calls, it can also sustain multiple concurrent transactions. Given the service definition of Example 7-19, Example 7-22 shows some client code that launches concurrent transactions on the same instance. scope2 will use a new transaction separate from that of scope1, and yet access the same service instance in the same session.

Example 7-22. Launching concurrent transactions

using(TransactionScope scope1 = new TransactionScope(  ))
{
   MyContractClient proxy = new MyContractClient(  );
   proxy.MyMethod(  );

   using(TransactionScope scope2 =
                          new TransactionScope(TransactionScopeOption.RequiresNew))
   {
      proxy.MyMethod(  );
      scope2.Complete(  );
   }
   proxy.MyMethod(  );

   proxy.Close(  );
   scope1.Complete(  );
}

The resulting transactions of Example 7-22 are depicted in Figure 7-10.

Concurrent transactions

Figure 7-10. Concurrent transactions

Warning

Code such as that in Example 7-22 will almost certainly result in a transactional deadlock over the underlying resources the service accesses. The first transaction will obtain the resource lock, and the second transaction will wait to own that lock while the first transaction waits for the second to complete.

Completing on session end

WCF offers yet another programming model for transactional per-session services, which is completely independent of ReleaseServiceInstanceOnTransactionComplete. This model is available for the case when the entire session fits into a single transaction, and the service equates session boundaries with transaction boundaries. The idea is that the service should not complete the transaction inside the session, because that is what causes WCF to release the service instance. To avoid completing the transaction, a per-session service can set TransactionAutoComplete to false, as shown in Example 7-23.

Example 7-23. Setting TransactionAutoComplete to false

[ServiceContract(SessionMode = SessionMode.Required)]
interface IMyContract
{
   [OperationContract]
   [TransactionFlow(...)]
   void MyMethod1(  );

   [OperationContract]
   [TransactionFlow(...)]
   void MyMethod2(  );

   [OperationContract]
   [TransactionFlow(...)]
   void MyMethod3(  );
}
class MyService : IMyContract
{
   [OperationBehavior(TransactionScopeRequired = true,
                      TransactionAutoComplete = false)]
   public void MyMethod1(  )
   {...}

   [OperationBehavior(TransactionScopeRequired = true,
                      TransactionAutoComplete = false)]
   public void MyMethod2(  )
   {...}

   [OperationBehavior(TransactionScopeRequired = true,
                      TransactionAutoComplete = false)]
   public void MyMethod3(  )
   {...}
}

Note that only a per-session service with a contract set to SessionMode.Required can set TransactionAutoComplete to false, and that is verified at the service load time. The problem with Example 7-23 is that the transaction the service participates in will always abort because the service does not vote to commit it by completing it. If the service equates sessions with transactions, the service should vote once the session ends. For that purpose, the ServiceBehavior attribute provides the Boolean property TransactionAutoCompleteOnSessionClose, defined as:

[AttributeUsage(AttributeTargets.Class)]
public sealed class ServiceBehaviorAttribute : Attribute,...
{
   public bool TransactionAutoCompleteOnSessionClose
   {get;set;}
   //More members
}

The default of TransactionAutoCompleteOnSessionClose is false. However, when set to true, it will auto-complete all uncompleted methods in the session. If no exceptions occurred during the session, when TransactionAutoCompleteOnSessionClose is true the service will vote to commit. For example, here is how to retrofit Example 7-23:

[ServiceBehavior(TransactionAutoCompleteOnSessionClose = true)]
class MyService : IMyContract
{...}

Figure 7-11 shows the resulting instance and its session.

Setting TransactionAutoCompleteOnSessionClose to true

Figure 7-11. Setting TransactionAutoCompleteOnSessionClose to true

During the session, the instance can maintain and access its state in normal member variables, and there is no need for state awareness or volatile resource managers.

Warning

When joining the client's transaction and relying on auto-completion on session close, the service must avoid lengthy processing in Dispose( ) or, in practical terms, avoid implementing IDisposable altogether. The reason is the race condition described here. Recall from Chapter 4 that Dispose( ) is called asynchronously at the end of the session. Auto-completion at session end takes place once the instance has been disposed of. If the client has control before the instance is disposed, the transaction will abort because the service has not yet completed it.

Note that using TransactionAutoCompleteOnSessionClose is risky, because it is always subjected to the transaction timeout. Sessions are by their very nature long-lived entities, while well-designed transactions are short-lived. This programming model is available for the case when the vote decision requires information that will be obtained by future calls throughout the session.

Because having TransactionAutoCompleteOnSessionClose set to true equates the session's end with the transaction's end, it is required that when the client's transaction is used, the client terminates the session within that transaction:

using(TransactionScope scope = new TransactionScope(  ))
{
   MyContractClient proxy = new MyContractClient(  );
   proxy.MyMethod(  );
   proxy.MyMethod(  );
   proxy.Close(  );

   scope.Complete(  );
}

Failing to do so will abort the transaction. As a side effect, the client cannot easily stack the using statements of the transaction scope and the proxy, because that may cause the proxy to be disposed of after the transaction:

//This always aborts:
using(MyContractClient proxy = new MyContractClient(  ))
using(TransactionScope scope = new TransactionScope(  ))
{
   proxy.MyMethod(  );
   proxy.MyMethod(  );

   scope.Complete(  );
}

In addition, because the proxy is basically good for only one-time use, there is little point in storing the proxy in member variables.

Transactional affinity

Setting TransactionAutoComplete to false has a unique effect that nothing else in WCF provides: it creates an affinity between the service instance context and the transaction, so that only that single transaction can ever access that service instance context. Unless context deactivation is used, this affinity is therefore to the instance as well. The affinity is established once the first transaction accesses the service instance, and once established it is fixed for the life of the instance (until the session ends). Transactional affinity is available only for per-session services, because only a per-session service can set TransactionAutoComplete to false. Affinity is crucial because the service is not state-aware—it uses normal members, and it must isolate access to them from any other transaction, in case the transaction to which it has an affinity aborts. Affinity thus offers a crude form of transaction-based locking. With transaction affinity, code such as that in Example 7-22 is guaranteed to deadlock (and eventually abort due to timing out) because the second transaction is blocked (independently of any resources the service accesses) waiting for the first transaction to finish, while the first transaction is blocked waiting for the second.

Hybrid state management

WCF also supports a hybrid of two of the sessionful programming models discussed earlier, combining both a state-aware and a regular sessionful transactional per-session service. The hybrid mode is designed to allow the service instance to maintain state in memory until it can complete the transaction, and then recycle that state using ReleaseServiceInstanceOnTransactionComplete as soon as possible, instead of delaying completing the transaction until the end of the session. Consider the service in Example 7-24, which implements the contract from Example 7-23.

Example 7-24. Hybrid per-session service

[ServiceBehavior(TransactionAutoCompleteOnSessionClose = true)]
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(  )
   {...}
}

The service uses the default of ReleaseServiceInstanceOnTransactionComplete (true), yet it has two methods (MyMethod1( ) and MyMethod2( )) that do not complete the transaction with TransactionAutoComplete set to false, resulting in an affinity to a particular transaction. The affinity isolates its members from any other transaction. The problem now is that the transaction will always abort, because the service does not complete it. To compensate for that, the service offers MyMethod3( ), which does complete the transaction. Because the service uses the default of ReleaseServiceInstanceOnTransactionComplete (true), after MyMethod3( ) is called, the transaction is completed and the instance is disposed of, as shown in Figure 7-12. Note that MyMethod3( ) could have instead used explicit voting via SetTransactionComplete( ). The important thing is that it completes the transaction. If the client does not call MyMethod3( ), purely as a contingency, the service in Example 7-24 relies on TransactionAutoCompleteOnSessionClose being set to true to complete and commit the transaction.

Hybrid state management

Figure 7-12. Hybrid state management

The hybrid mode is inherently a brittle proposition. The first problem is that the service instance must complete the transaction before it times out, but since there is no telling when the client will call the completing method, you risk timing out before that. In addition, the service holds onto any locks on resource managers it may access for the duration of the session, and the longer the locks are held, the higher the likelihood is of other transactions timing out or deadlocking with this service's transaction. Finally, the service is at the mercy of the client, because the client must call the completing method to end the session. You can and should clearly document the need to call that operation at the end of the transaction:

[ServiceContract(SessionMode = SessionMode.Required)]
interface IMyContract
{
   [OperationContract]
   [TransactionFlow(...)]
   void MyMethod1(  );

   [OperationContract]
   [TransactionFlow(...)]
   void MyMethod2(  );

   [OperationContract]
   [TransactionFlow(...)]
   void CompleteTransaction(  );
}

Both equating sessions with transactions (while relying solely on TransactionAutoCompleteOnSessionClose) and using the hybrid mode are potential solutions for situations when the transaction execution and subsequent voting decision require information obtained throughout the session. Consider, for example, the following contract used for order processing:

[ServiceContract(SessionMode = SessionMode.Required)]
interface IOrderManager
{
   [OperationContract]
   [TransactionFlow(...)]
   void SetCustomerId(int customerId);

   [OperationContract]
   [TransactionFlow(...)]
   void AddItem(int itemId);

   [OperationContract]
   [TransactionFlow(...)]
   bool ProcessOrders(  );
}

The implementing service can only process the order once it has the customer ID and all of the ordered items. However, relying on transactional sessions in this way usually indicates poor design, because of the inferior throughput and scalability implications. Good transactions are inherently short while sessions are inherently long (up to 10 minutes by default), so they are inherently incompatible. The disproportional complexity of prolonging a single transaction across a session outweighs the perceived benefit of using a session. It is usually better to factor the contract so that it provides every operation with all the information it needs to complete and vote:

[ServiceContract(SessionMode = ...)]
interface IOrderManager
{
   [OperationContract]
   [TransactionFlow(...)]
   bool ProcessOrders(int customerId,int[] itemIds);
}

Done this way, you can either implement the service as per-call or maintain a sessionful programming model, avoid placing operation order constraints on the client, and use any VRMs as member variables and access other transactional resources. You clearly separate the contract from its implementation, both on the client and the service side.

Transactional Durable Services

Recall from Chapter 4 that a durable service retrieves its state from the configured store and then saves its state back into that store on every operation. The state store may or may not be a transactional resource manager. If the service is transactional, it should of course use only a transactional durable storage and enlist it in each operation's transaction. That way, if a transaction aborts, the state store will be rolled back to its pre-transaction state. However, WCF does not know whether a service is designed to propagate its transactions to the state store, and by default it will not enlist the storage in the transaction even if the storage is a transactional resource manager, such as SQL Server 2005/2008. To instruct WCF to propagate the transaction and enlist the underlying storage, set the SaveStateInOperationTransaction property of the DurableService attribute to true:

public sealed class DurableServiceAttribute : ...
{
   public bool SaveStateInOperationTransaction
   {get;set;}
}

SaveStateInOperationTransaction defaults to false, which means the state storage will not participate in the transaction. It is therefore important to always set SaveStateInOperationTransaction to true to ensure consistent state management in the presence of transactions. Since only a transactional service could benefit from having SaveStateInOperationTransaction set to true, if it is true then WCF will insist that all operations on the service either have TransactionScopeRequired set to true or have mandatory transaction flow. If the operation is configured with TransactionScopeRequired set to true, the ambient transaction of the operation will be the one used to enlist the storage. If the operation is configured for mandatory transaction flow, the client's transaction will be used to enlist the storage (regardless of whether the operation does or does not have an ambient transaction).

Instance ID management

As explained in Chapter 4, the DurableService behavior attribute enforces strict management of the instance ID passed over the context binding. The first operation to start the workflow will have no instance ID, in which case, WCF will create a new instance ID, use it to save the newly created instance state to the storage, and then send the instance ID back to the client. From that point on, until the end of the workflow, the client must pass the same instance ID to the service. If the client provides an instance ID that is not present in the storage, WCF will throw an exception. This presents a potential pitfall with transactional durable services: suppose the client starts a workflow and propagates its transaction to the service. The first operation creates the instance ID, executes successfully, and stores the state in the storage. However, what would happen if the transaction were then to abort, due to some other party (such as the client or another service involved in the transaction) voting to abort? The state storage would roll back the changes made to it, including the newly created instance state and the corresponding ID. The next call coming from the client will present the same ID created by the first call, except now the state storage will not have any record of that ID, so WCF will reject the call, throw an exception, and prevent any other call to the service with that ID from ever executing.

To avoid this pitfall, you need to add to the service contract an explicit first operation whose sole purpose is to guarantee that the first call successfully commits the instance ID to the state storage. For example, in the case of a calculator service, this would be your PowerOn( ) operation. You should explicitly block the client's transaction (by using the default value of TransactionFlowOption.NotAllowed), and avoid placing any code in that method body, thus precluding anything that could go wrong from aborting the transaction. You can enforce having the client call the initiating operation first using demarcating operations (discussed in Chapter 4).

A similar pitfall exists at the end of the workflow. By setting the CompletesInstance property of the DurableOperation attribute to true, you indicate to WCF that the workflow has ended and that WCF should purge the instance state from the storage. However, if the client's transaction aborts after the last operation in the service has executed successfully, the storage will roll back and keep the orphaned state indefinitely. To avoid bloating the state storage with zombie instances (the product of aborted transactions of the completing instance operations), you need to add to the service contract an explicit operation whose sole purpose is to complete the instance and to commit successfully, irrespective of whether the client's transaction commits. For example, in the case of a calculator service, this would be your PowerOff( ) operation. Again, block any client transaction from propagating to the service, and avoid placing any code in the completing method.

Example 7-25 shows a template for defining and implementing a transactional durable service, adhering to these guidelines.

Example 7-25. Transactional durable service

[ServiceContract]
interface IMyContract
{
   [OperationContract]
   void SaveState(  );

   [OperationContract(IsInitiating = false)]
   void ClearState(  );

   [OperationContract(IsInitiating = false)]
   [TransactionFlow(...)]
   void MyMethod1(  );

   [OperationContract(IsInitiating = false)]
   [TransactionFlow(...)]
   void MyMethod2(  );
}

[Serializable]
[DurableService(SaveStateInOperationTransaction = true)]
class MyService: IMyContract
{
   [OperationBehavior(TransactionScopeRequired = true)]
   public void SaveState(  )
   {}

   [DurableOperation(CompletesInstance = true)]
   [OperationBehavior(TransactionScopeRequired = true)]
   public void ClearState(  )
   {}

   [OperationBehavior(TransactionScopeRequired = true)]
   public void MyMethod1(  )
   {...}
   [OperationBehavior(TransactionScopeRequired = true)]
   public void MyMethod2(  )
   {...}
}

Transactional Behavior[5]

As far as the DurableService attribute is concerned, the word Durable in its name is a misnomer, since it does not necessarily indicate a durable behavior. All it means is that WCF will automatically deserialize the service state from a configured storage and then serialize it back again on every operation. Similarly, the persistence provider behavior (see Chapter 4) does not necessarily mean persistence, since any provider that derives from the prescribed abstract provider class will comply with WCF's expectation of the behavior.

The fact that the WCF durable service infrastructure is, in reality, a serialization infrastructure enabled me to leverage it into yet another technique for managing service state in the face of transactions, while relying underneath on a volatile resource manager, without having the service instance do anything about it. This further streamlines the transactional programming model of WCF and yields the benefit of the superior programming model of transactions for mere objects.

The first step was to define two transactional in-memory provider factories:

public abstract class MemoryProviderFactory : PersistenceProviderFactory
{...}

public class TransactionalMemoryProviderFactory : MemoryProviderFactory
{...}
public class TransactionalInstanceProviderFactory : MemoryProviderFactory
{...}

The TransactionalMemoryProviderFactory uses my TransactionalDictionary<ID,T> to store the service instances.

Tip

Unrelated to this section and transactions, you can configure the service to use the TransactionalMemoryProviderFactory with or without transactions by simply listing it in the persistence providers section of the service behaviors:

<behavior name = "TransactionalMemory">
   <persistenceProvider
      type = "ServiceModelEx.
                  TransactionalMemoryProviderFactory,
              ServiceModelEx"
   />
</behavior>

This will enable you to store the instances in memory, instead of in a file or SQL Server database. This is useful for quick testing and for stress testing, since it avoids the inherent I/O latency of a durable persistent storage.

The in-memory dictionary is shared among all clients and transport sessions, and as long as the host is running, TransactionalMemoryProviderFactory allows clients to connect and disconnect from the service. When using TransactionalMemoryProviderFactory you should designate a completing operation that removes the instance state from the store as discussed in Chapter 4, using the CompletesInstance property of the DurableOperation attribute.

TransactionalInstanceProviderFactory, on the other hand, matches each transport session with a dedicated instance of Transactional<T>. There is no need to call any completing operation since the service state will be cleaned up with garbage collection after the session is closed.

Next, I defined the TransactionalBehaviorAttribute, shown in Example 7-26.

Example 7-26. The TransactionalBehavior attribute

[AttributeUsage(AttributeTargets.Class)]
public class TransactionalBehaviorAttribute : Attribute,IServiceBehavior
{
   public bool TransactionRequiredAllOperations
   {get;set;}

   public bool AutoCompleteInstance
   {get;set;}

   public TransactionalBehaviorAttribute(  )
   {
      TransactionRequiredAllOperations = true;
      AutoCompleteInstance = true;
   }
   void IServiceBehavior.Validate(ServiceDescription description,
                                  ServiceHostBase host)
   {
      DurableServiceAttribute durable = new DurableServiceAttribute(  );
      durable.SaveStateInOperationTransaction = true;
      description.Behaviors.Add(durable);

      PersistenceProviderFactory factory;
      if(AutoCompleteInstance)
      {
         factory = new TransactionalInstanceProviderFactory(  );
      }
      else
      {
         factory = new TransactionalMemoryProviderFactory(  );
      }

      PersistenceProviderBehavior persistenceBehavior =
                                          new PersistenceProviderBehavior(factory);
      description.Behaviors.Add(persistenceBehavior);

      if(TransactionRequiredAllOperations)
      {
         foreach(ServiceEndpoint endpoint in description.Endpoints)
         {
            foreach(OperationDescription operation in endpoint.Contract.Operations)
            {
               OperationBehaviorAttribute operationBehavior =
                            operation.Behaviors.Find<OperationBehaviorAttribute>(  );
               operationBehavior.TransactionScopeRequired = true;
            }
         }
      }
   }
   void IServiceBehavior.AddBindingParameters(...)
   {}
   void IServiceBehavior.ApplyDispatchBehavior(...)
   {}
}

TransactionalBehavior is a service behavior attribute. It always performs these configurations for the service. First, it injects into the service description a DurableService attribute with SaveStateInOperationTransaction set to true. Second, it adds the use of either TransactionalMemoryProviderFactory or TransactionalInstanceProviderFactory for the persistent behavior according to the value of the AutoCompleteInstance property. If AutoCompleteInstance is set to true (the default) then TransactionalBehavior will use TransactionalInstanceProviderFactory. Finally, TransactionalBehavior provides the TransactionRequiredAllOperations property. When the property is set to true (the default) TransactionalBehavior will set TransactionScopeRequired to true on all the service operation behaviors, thus providing all operations with an ambient transaction. When it is explicitly set to false, the service developer can choose which operations will be transactional.

As a result, using the attribute like so:

[Serializable]
[TransactionalBehavior]
class MyService : IMyContract
{
   public void MyMethod(  )
   {...}
}

is equivalent to this service declaration and configuration:

[Serializable]
[DurableService(SaveStateInOperationTransaction = true)]
class MyService : IMyContract
{
   [OperationBehavior(TransactionScopeRequired = true)]
   public void MyMethod(  )
   {...}
}

<services>
   <service name = "MyService" behaviorConfiguration = "TransactionalBehavior">
      ...
   </service>
</services>
<behaviors>
   <serviceBehaviors>
      <behavior name = "TransactionalBehavior">
         <persistenceProvider
            type = "ServiceModelEx.TransactionalInstanceProviderFactory,
                    ServiceModelEx"
         />
      </behavior>
   </serviceBehaviors>
</behaviors>

When using the TransactionalBehavior attribute with the default values, the client need not manage or interact in any way with the instance ID as shown in Chapter 4. All the client needs to do is use the proxy over one of the context bindings, and let the binding manage the instance ID. For example, for this service definition:

[ServiceContract]
interface IMyContract
{
   [OperationContract]
   [TransactionFlow(TransactionFlowOption.Allowed)]
   void IncrementCounter(  );
}

[Serializable]
[TransactionalBehavior]
class MyService : IMyContract
{
   int m_Counter = 0;

   public void IncrementCounter(  )
   {
      m_Counter++;
      Trace.WriteLine("Counter = " + m_Counter);
   }
}

the following client code:

MyContractClient proxy = new MyContractClient(  );

using(TransactionScope scope = new TransactionScope(  ))
{
   proxy.IncrementCounter(  );
   scope.Complete(  );
}

//This transaction will abort since the scope is not completed
using(TransactionScope scope = new TransactionScope(  ))
{
   proxy.IncrementCounter(  );
}

using(TransactionScope scope = new TransactionScope(  ))
{
   proxy.IncrementCounter(  );
   scope.Complete(  );
}

proxy.Close(  );

yields this output:

Counter = 1
Counter = 2
Counter = 2

Note that the service was interacting with a normal integer as its member variable.

In-proc transactions

The TransactionalBehavior attribute substantially simplifies transactional programming and is a fundamental step toward the future, where memory itself will be transactional and it will be possible for every object to be transactional (for more on my vision for the future of the platform, please see Appendix A). TransactionalBehavior maintains the programming model of conventional, plain .NET, yet it provides the full benefits of transactions.

To allow the efficient use of TransactionalBehavior even in the most intimate execution scopes, ServiceModelEx contains the NetNamedPipeContextBinding class. As the binding's name implies, it is the IPC binding plus the context protocol (required by the DurableService attribute). Appendix B walks through implementing the NetNamedPipeContextBinding class.

Tip

Supporting TransactionalBehavior over IPC was my main motivation for developing the NetNamedPipeContextBinding.

To make the programming model of TransactionalBehavior even more accessible, the InProcFactory class from Chapter 1 actually uses NetNamedPipeContextBinding instead of the built-in NetNamedPipeBinding. InProcFactory also flows transactions over the binding. This enables the programming model of Example 7-27, without ever resorting to host management or client or service config files.

Example 7-27. Combining TransactionalBehavior with the InProcFactory

[ServiceContract]
interface IMyContract
{
   [OperationContract]
   [TransactionFlow(TransactionFlowOption.Allowed)]
   void IncrementCounter(  );
}

[Serializable]
[TransactionalBehavior]
class MyService : IMyContract
{
   int m_Counter = 0;

   public void IncrementCounter(  )
   {
      m_Counter++;
      Trace.WriteLine("Counter = " + m_Counter);
   }
}

//Client-code

IMyContract proxy = InProcFactory.CreateInstance<MyService,IMyContract>(  );

using(TransactionScope scope = new TransactionScope(  ))
{
   proxy.IncrementCounter(  );
   scope.Complete(  );
}

//This transaction will abort since the scope is not completed
using(TransactionScope scope = new TransactionScope(  ))
{
   proxy.IncrementCounter(  );
}

using(TransactionScope scope = new TransactionScope(  ))
{
   proxy.IncrementCounter(  );
   scope.Complete(  );
}

InProcFactory.CloseProxy(proxy);

//Traces:
Counter = 1
Counter = 2
Counter = 2

Transactional Singleton Service

By default, a transactional singleton behaves like a per-call service. The reason is that by default ReleaseServiceInstanceOnTransactionComplete is set to true, so after the singleton auto-completes a transaction, WCF disposes of the singleton, in the interest of state management and consistency. This, in turn, implies that the singleton must be state-aware and must proactively manage its state in every method call, in and out of a resource manager. The big difference compared to a per-call service is that WCF will enforce the semantic of the single instance, so at any point in time there will be at most a single instance running. WCF uses concurrency management and instance deactivation to enforce this rule. Recall that when ReleaseServiceInstanceOnTransactionComplete is true, the concurrency mode must be ConcurrencyMode.Single to disallow concurrent calls. WCF keeps the singleton context and merely deactivates the instance hosted in the context, as discussed in Chapter 4. What this means is that even though the singleton needs to be state-aware, it does not need the client to provide an explicit state identifier in every call. The singleton can use any type-level constant to identify its state in the state resource manager, as shown in Example 7-28.

Example 7-28. State-aware singleton

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
class MySingleton : IMyContract
{
   readonly static string m_StateIdentifier = typeof(MySingleton).GUID.ToString(  );

   [OperationBehavior(TransactionScopeRequired = true)]
   public void MyMethod(  )
   {
      GetState(  );
      DoWork(  );
      SaveState(  );
   }

   //Helper methods
   void GetState(  )
   {
      //Use m_StateIdentifier to get state
   }
   void DoWork(  )
   {}
   public void SaveState(  )
   {
      //Use m_StateIdentifier to save state
   }
   public void RemoveState(  )
   {
      //Use m_StateIdentifier to remove the state from the resource manager
   }
}
//Hosting code
MySingleton singleton  = new MySingleton(  );
singleton.SaveState(  ); //Create the initial state in the resource manager

ServiceHost host = new ServiceHost(singleton);
host.Open(  );

/* Some blocking calls */

host.Close(  );
singleton.RemoveState(  );

In this example, the singleton uses the unique GUID associated with every type as a state identifier. At the beginning of every method call the singleton reads its state, and at the end of each method call it saves the state back to the resource manager. However, the first call on the first instance must also be able to bind to the state, so you must prime the resource manager with the state before the first call ever arrives. To that end, before launching the host, you need to create the singleton, save its state to the resource manager, and then provide the singleton instance to ServiceHost (as explained in Chapter 4). After the host shuts down, make sure to remove the singleton state from the resource manager, as shown in Example 7-28. Note that you cannot create the initial state in the singleton constructor, because the constructor will be called for each operation on the singleton and will override the previous saved state.

While a state-aware singleton is certainly possible (as demonstrated in Example 7-28), the overall complexity involved makes it a technique to avoid. It is better to use a stateful transactional singleton, as presented next.

Stateful singleton service

By setting ReleaseServiceInstanceOnTransactionComplete to false, you regain the singleton semantic. The singleton will be created just once, when the host is launched, and the same single instance will be shared across all clients and transactions. The problem is, of course, how to manage the state of the singleton. The singleton has to have state; otherwise, there is no point in using a singleton in the first place. The best solution (as before, with the stateful per-session service) is to use volatile resource managers as member variables, as shown in Example 7-29.

Example 7-29. Achieving a stateful singleton transactional service

////////////////// Service Side //////////////////////////////////////
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
                 ReleaseServiceInstanceOnTransactionComplete = false)]
class MySingleton : IMyContract
{
   Transactional<int> m_Counter = new Transactional<int>(  );

   [OperationBehavior(TransactionScopeRequired = true)]
   public void MyMethod(  )
   {
      m_Counter.Value++;
      Trace.WriteLine("Counter: " + m_Counter.Value);
   }
}
////////////////// Client Side //////////////////////////////////////
using(TransactionScope scope1 = new TransactionScope(  ))
{
   MyContractClient proxy = new MyContractClient(  );
   proxy.MyMethod(  );
   proxy.Close(  );
   scope1.Complete(  );
}
using(TransactionScope scope2 = new TransactionScope(  ))
{
   MyContractClient proxy = new MyContractClient(  );
   proxy.MyMethod(  );
   proxy.Close(  );
}
using(TransactionScope scope3 = new TransactionScope(  ))
{
   MyContractClient proxy = new MyContractClient(  );
   proxy.MyMethod(  );
   proxy.Close(  );
   scope3.Complete(  );
}
////////////////// Output //////////////////////////////////////
Counter: 1
Counter: 2
Counter: 2

In Example 7-29, a client creates three transactional scopes, each with its own new proxy to the singleton. In each call, the singleton increments a counter it maintains as a Transactional<int> volatile resource manager. scope1 completes the transaction and commits the new value of the counter (1). In scope2, the client calls the singleton and temporarily increments the counter to 2. However, scope2 does not complete its transaction. The volatile resource manager therefore rejects the increment and reverts to its previous value of 1. The call in scope3 then increments the counter again from 1 to 2, as shown in the trace output.

Note that when setting ReleaseServiceInstanceOnTransactionComplete, the singleton must have at least one method with TransactionScopeRequired set to true.

In addition, the singleton must have TransactionAutoComplete set to true on every method, which of course precludes any transactional affinity and allows concurrent transactions. All calls and all transactions are routed to the same instance. For example, the following client code will result in the transaction diagram shown in Figure 7-13:

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

using(MyContractClient proxy = new MyContractClient(  ))
using(TransactionScope scope = new TransactionScope(  ))
{
   proxy.MyMethod(  );
   proxy.MyMethod(  );
   scope.Complete(  );
}
Stateful transactional singleton

Figure 7-13. Stateful transactional singleton

Instancing Modes and Transactions

To summarize the topic of instance management modes and transactions, Table 7-3 lists the possible configurations discussed so far and their resulting effects. Other combinations may technically be allowed, but I've omitted them because they are either nonsensical or plainly disallowed by WCF.

Table 7-3. Possible instancing modes, configurations, and transactions

Configured instancing mode

Auto- complete

Release on complete

Complete on session close

Resulting instancing mode

State mgmt.

Trans. affinity

Per-call

True

True/False

True/False

Per-call

State-aware

Call

Session

True

True

True/False

Per-call

State-aware

Call

Session

True

False

True/False

Session

VRM members

Call

Session

False

True/False

True

Session

Stateful

Instance context

Session

Hybrid

True

True/False

Hybrid

Hybrid

Instance context

Durable service

True

True/False

True/False

Per-call

Stateful

Call

Singleton

True

True

True/False

Per-call

State-aware

Call

Singleton

True

False

True/False

Singleton

VRM members

Call

With so many options, which mode should you choose? I find that the complexity of an explicit state-aware programming model with sessionful and singleton services outweighs any potential benefits, and this is certainly the case with the hybrid mode as well. Equating sessions with transactions is often impractical and indicates a bad design. For both sessionful and singleton services, I prefer the simplicity and elegance of volatile resource managers as member variables. You can also use a durable service on top of a transactional durable storage or the TransactionalBehavior attribute.

Table 7-4 lists these recommended configurations. None of the recommended options relies on transactional affinity or auto-completion on session close, but they all use auto-completion.

Table 7-4. Recommended instancing modes, configurations, and transactions

Configured instancing mode

Release on complete

Resulting instancing mode

State management

Per-call

True/False

Per-call

State-aware

Session

False

Session

VRM members

Durable service

True/False

Per-call

Stateful

Singleton

False

Singleton

VRM members



[5] I presented my approach for transactional behavior in the January 2009 issue of MSDN Magazine.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.35.193