Custom lifetime

The Lifetime sample demonstrates how to write a Windows Communication Foundation (WCF) extension to provide custom lifetime services for shared WCF service instances.

Note

The setup procedure and build instructions for this sample are located at the end of this article.

Shared instancing

WCF offers several instancing modes for your service instances. The shared instancing mode covered in this article provides a way to share a service instance between multiple channels. Clients can contact a factory method in the service and create a new channel to start communication. The following code snippet shows how a client application creates a new channel to an existing service instance:

// Create a header for the shared instance id
MessageHeader shareableInstanceContextHeader = MessageHeader.CreateHeader(
        CustomHeader.HeaderName,
        CustomHeader.HeaderNamespace,
        Guid.NewGuid().ToString());

// Create the channel factory
ChannelFactory<IEchoService> channelFactory =
    new ChannelFactory<IEchoService>("echoservice");

// Create the first channel
IEchoService proxy = channelFactory.CreateChannel();

// Call an operation to create shared service instance
using (new OperationContextScope((IClientChannel)proxy))
{
    OperationContext.Current.OutgoingMessageHeaders.Add(shareableInstanceContextHeader);
    Console.WriteLine("Service returned: " + proxy.Echo("Apple"));
}

((IChannel)proxy).Close();

// Create the second channel
IEchoService proxy2 = channelFactory.CreateChannel();

// Call an operation using the same header that will reuse the shared service instance
using (new OperationContextScope((IClientChannel)proxy2))
{
    OperationContext.Current.OutgoingMessageHeaders.Add(shareableInstanceContextHeader);
    Console.WriteLine("Service returned: " + proxy2.Echo("Apple"));
}

Unlike other instancing modes, the shared instancing mode has a unique way of releasing the service instances. By default, when all the channels are closed for an InstanceContext, the WCF service runtime checks to see if the service InstanceContextMode is configured to PerCall or PerSession, and if so releases the instance and claims the resources. If a custom IInstanceContextProvider is being used, WCF invokes the IsIdle method of the provider implementation before releasing the instance. If IsIdle returns true the instance is released, otherwise the IInstanceContextProvider implementation is responsible for notifying the Dispatcher of the idle state by using a callback method. This is done by calling the NotifyIdle method of the provider.

This sample demonstrates how you can delay releasing the InstanceContext with an idle timeout of 20 seconds.

Extending the InstanceContext

In WCF, InstanceContext is the link between the service instance and the Dispatcher. WCF allows you to extend this runtime component by adding new state or behavior by using its extensible object pattern. The extensible object pattern is used in WCF to either extend existing runtime classes with new functionality or to add new state features to an object. There are three interfaces in the extensible object pattern: IExtensibleObject<T>, IExtension<T>, and IExtensionCollection<T>.

The IExtensibleObject<T> interface is implemented by objects to allow extensions that customize their functionality.

The IExtension<T> interface is implemented by objects that can be extensions of classes of type T.

And finally, the IExtensionCollection<T> interface is a collection of IExtension<T> implementations that allows for retrieving an implementation of IExtension<T> by their type.

Therefore, in order to extend the InstanceContext, you must implement the IExtension<T> interface. In this sample project, the CustomLeaseExtension class contains this implementation.

class CustomLeaseExtension : IExtension<InstanceContext>
{
}

The IExtension<T> interface has two methods Attach and Detach. As their names imply, these two methods are called when the runtime attaches and detaches the extension to an instance of the InstanceContext class. In this sample, the Attach method is used to keep track of the InstanceContext object that belongs to the current instance of the extension.

InstanceContext owner;

public void Attach(InstanceContext owner)
{
    this.owner = owner;
}

In addition, you must add the necessary implementation to the extension to provide the extended lifetime support. Therefore, the ICustomLease interface is declared with the desired methods and is implemented in the CustomLeaseExtension class.

interface ICustomLease
{
    bool IsIdle { get; }
    InstanceContextIdleCallback Callback { get; set; }
}

class CustomLeaseExtension : IExtension<InstanceContext>, ICustomLease
{
}

When WCF invokes the IsIdle method in the IInstanceContextProvider implementation, this call is routed to the IsIdle method of the CustomLeaseExtension. Then, the CustomLeaseExtension checks its private state to see whether the InstanceContext is idle. If it is idle, it returns true. Otherwise, it starts a timer for a specified amount of extended lifetime.

public bool IsIdle
{
  get
  {
    lock (thisLock)
    {
      if (isIdle)
      {
        return true;
      }
      else
      {
        StartTimer();
        return false;
      }
    }
  }
}

In the timer's Elapsed event, the callback function in the Dispatcher is called in order to start another clean-up cycle.

void idleTimer_Elapsed(object sender, ElapsedEventArgs args)
{
    lock (thisLock)
    {
        StopTimer();
        isIdle = true;
        Utility.WriteMessageToConsole(
            ResourceHelper.GetString("MsgLeaseExpired"));
        callback(owner);
    }
}

There's no way to renew the running timer when a new message arrives for the instance being moved to the idle state.

The sample implements IInstanceContextProvider to intercept the calls to the IsIdle method and route them to the CustomLeaseExtension. The IInstanceContextProvider implementation is contained in CustomLifetimeLease class. The IsIdle method is invoked when WCF is about to release the service instance. However, there is only one instance of a particular ISharedSessionInstance implementation in the ServiceBehavior's IInstanceContextProvider collection. This means there's no way of knowing if the InstanceContext is closed at the time WCF checks the IsIdle method. Therefore, this sample uses thread locking to serialize requests to the IsIdle method.

Important

Using thread locking is not a recommended approach because serialization can severely affect the performance of your application.

A private member field is used in the CustomLifetimeLease class to track the idle state and is returned by the IsIdle method. Each time the IsIdle method is called, the isIdle field is returned and reset to false. It is essential to set this value to false in order to make sure the Dispatcher calls the NotifyIdle method.

public bool IsIdle(InstanceContext instanceContext)
{
    get
    {
        lock (thisLock)
        {
            //...
            bool idleCopy = isIdle;
            isIdle = false;
            return idleCopy;
        }
    }
}

If the IInstanceContextProvider.IsIdle method returns false, the Dispatcher registers a callback function by using the NotifyIdle method. This method receives a reference to the InstanceContext being released. Therefore, the sample code can query the ICustomLease type extension and check the ICustomLease.IsIdle property in the extended state.

public void NotifyIdle(InstanceContextIdleCallback callback,
            InstanceContext instanceContext)
{
    lock (thisLock)
    {
       ICustomLease customLease =
           instanceContext.Extensions.Find<ICustomLease>();
       customLease.Callback = callback;
       isIdle = customLease.IsIdle;
       if (isIdle)
       {
             callback(instanceContext);
       }
    }
}

Before the ICustomLease.IsIdle property is checked, the Callback property needs to be set as this is essential for CustomLeaseExtension to notify the Dispatcher when it becomes idle. If ICustomLease.IsIdle returns true, the isIdle private member is simply set in CustomLifetimeLease to true and calls the callback method. Because the code holds a lock, other threads can't change the value of this private member. And the next time Dispatcher calls the IInstanceContextProvider.IsIdle, it returns true and lets Dispatcher release the instance.

Now that the groundwork for the custom extension is completed, it has to be hooked up to the service model. To hook up the CustomLeaseExtension implementation to the InstanceContext, WCF provides the IInstanceContextInitializer interface to perform the bootstrapping of InstanceContext. In the sample, the CustomLeaseInitializer class implements this interface and adds an instance of CustomLeaseExtension to the Extensions collection from the only method initialization. This method is called by Dispatcher while initializing the InstanceContext.

public void InitializeInstanceContext(InstanceContext instanceContext,
    System.ServiceModel.Channels.Message message, IContextChannel channel)

    //...

    IExtension<InstanceContext> customLeaseExtension =
        new CustomLeaseExtension(timeout, headerId);
    instanceContext.Extensions.Add(customLeaseExtension);
}

Finally the IInstanceContextProvider implementation is hooked up to the service model by using the IServiceBehavior implementation. This implementation is placed in the CustomLeaseTimeAttribute class and it also derives from the Attribute base class to expose this behavior as an attribute.

public void ApplyDispatchBehavior(ServiceDescription description,
           ServiceHostBase serviceHostBase)
{
    CustomLifetimeLease customLease = new CustomLifetimeLease(timeout);

    foreach (ChannelDispatcherBase cdb in serviceHostBase.ChannelDispatchers)
    {
        ChannelDispatcher cd = cdb as ChannelDispatcher;

        if (cd != null)
        {
            foreach (EndpointDispatcher ed in cd.Endpoints)
            {
                ed.DispatchRuntime.InstanceContextProvider = customLease;
            }
        }
    }
}

This behavior can be added to a sample service class by annotating it with the CustomLeaseTime attribute.

[CustomLeaseTime(Timeout = 20000)]
public class EchoService : IEchoService
{
  //…
}

When you run the sample, the operation requests and responses are displayed in both the service and client console windows. Press ENTER in each console window to shut down the service and client.

To set up, build, and run the sample

  1. Ensure that you've performed the One-Time Setup Procedure for the Windows Communication Foundation Samples.

  2. To build the C# or Visual Basic .NET edition of the solution, follow the instructions in Building the Windows Communication Foundation Samples.

  3. To run the sample in a single- or cross-machine configuration, follow the instructions in Running the Windows Communication Foundation Samples.