Singleton Service



Singleton Service

The singleton service is the ultimate sharable service. When a service is configured as a singleton, all clients independently get connected to the same single well-known instance, regardless of which endpoint of the service they connect to. The singleton service lives forever and is only disposed of once the host shuts down. The singleton is created exactly once, when the host is created.

Using a singleton does not require the clients to maintain a session with the singleton instance, or to use a binding that supports a transport-level session. If the contract the client consumes has a session, then during the call the singleton will have the same session ID as the client (binding permitting), but closing the client proxy will only terminate the session, not the singleton instance. In addition, the session will never expire. If the singleton service supports contracts without a session, those contracts will not be per-call: they too will be connected to the same instance. By its very nature, the singleton is shared, and each client should simply create its own proxies to it.

You configure a singleton service by setting the InstanceContextMode property to InstanceContextMode.Single:

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
class MySingleton : ...
{...}

Figure demonstrates a singleton service with two contracts, one that requires a session and one that does not. As you can see from the client call, the calls on the two endpoints were routed to the same instance, and closing the proxies did not terminate the singleton.

A singleton service and client

///////////////////////// Service code /////////////////////
[ServiceContract(SessionMode = SessionMode.Required)]
interface IMyContract
{
   [OperationContract]
   void MyMethod( );
}
[ServiceContract(SessionMode = SessionMode.NotAllowed)]
interface IMyOtherContract
{
   [OperationContract]
   void MyOtherMethod( );
}
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
class MySingleton : IMyContract,IMyOtherContract,IDisposable
{
   int m_Counter = 0;

   public MySingleton( )
   {
      Trace.WriteLine("MySingleton.MySingleton( )");
   }
   public void MyMethod( )
   {
      m_Counter++;
      Trace.WriteLine("Counter = " + m_Counter);
   }
   public void MyOtherMethod( )
   {
      m_Counter++;
      Trace.WriteLine("Counter = " + m_Counter);
   }
   public void Dispose( )
   {
      Trace.WriteLine("Singleton.Dispose( )");
   }
}
///////////////////////// Client code /////////////////////
MyContractClient proxy1 = new MyContractClient( );
proxy1.MyMethod( );
proxy1.Close( );

MyOtherContractClient proxy2 = new MyOtherContractClient( );
proxy2.MyOtherMethod( );
proxy2.Close( );

//Output
MySingleton.MySingleton( )
Counter = 1
Counter = 2

Initializing a Singleton

Sometimes you may not want to create and initialize the singleton using just the default constructor. Perhaps initializing that state requires some custom steps or specific knowledge not available to the clients or that the clients should not be bothered with. To support such scenarios, WCF allows you to directly create the singleton instance beforehand using normal CLR instantiation, initialize it, and then open the host with that instance in mind as the singleton service. The ServiceHost class offers a dedicated constructor that accepts an object:

public class ServiceHost : ServiceHostBase,...
{
   public ServiceHost(object singletonInstance,
                      params Uri[] baseAddresses);
   public virtual object SingletonInstance
   {get;}
   //More members
}

Note that the object must be configured as a singleton. For example, consider the code in Figure. The class MySingleton will be first initialized and then hosted as a singleton.

Initializing and hosting a singleton

//Service code
[ServiceContract]
interface IMyContract
{
   [OperationContract]
   void MyMethod( );
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
class MySingleton : IMyContract
{
   int m_Counter = 0;

   public int Counter
   {
      get
      {
         return m_Counter;
      }
      set
      {
         m_Counter = value;
      }
   }
   public void MyMethod( )
   {
      m_Counter++;
      Trace.WriteLine("Counter = " + Counter);
   }
}
//Host code
MySingleton singleton = new MySingleton( );
singleton.Counter = 42;

ServiceHost host = new ServiceHost(singleton);
host.Open( );
//Do some blocking calls then
host.Close( );

//Client code
MyContractClient proxy = new MyContractClient( );
proxy.MyMethod( );
proxy.Close( );

//Outoput:
Counter = 43

If you do initialize and host a singleton this way, you may also want to be able to access it directly on the host side. WCF enables downstream objects to reach back into the singleton directly using the SingletonInstance property of ServiceHost. Any party on the call chain leading down from an operation call on the singleton can always access the host via the operation context's read-only Host property:

public sealed class OperationContext : ...
{
   public ServiceHostBase Host
   {get;}
   //More members
}

Once you have the singleton reference, you can interact with it directly:

ServiceHost host = OperationContext.Current.Host as ServiceHost;
Debug.Assert(host != null);
MySingleton singleton = host.SingletonInstance as MySingleton;
Debug.Assert(singleton != null);
singleton.Counter = 388;

If no singleton instance was provided to the host, SingletonInstance returns null.

Streamlining with ServiceHost<T>

The ServiceHost<T> class presented in Chapter 1 can be extended to offer type-safe singleton initialization and access:

public class ServiceHost<T> : ServiceHost
{
   public ServiceHost(T singleton,params Uri[] baseAddresses)
                                           : base(singleton,baseAddresses)
   {}
   public virtual T Singleton
   {
      get
      {
         if(SingletonInstance == null)
         {
            return default(T);
         }
         return (T)SingletonInstance;
      }
   }
   //More members
}

The type parameter provides type-safe binding for the object used for construction:

MySingleton singleton = new MySingleton( );
singleton.Counter = 42;

ServiceHost<MySingleton> host = new ServiceHost<MySingleton>(singleton);
host.Open( );

and the object returned from the Singleton property.

ServiceHost<MySingleton> host = OperationContext.Current.Host
                                                      as ServiceHost<MySingleton>;
Debug.Assert(host != null);
host.Singleton.Counter = 388;

In a similar manner, InProcFactory<T> presented in Chapter 1 is also extended to initialize a singleton instance.


Choosing a Singleton

The singleton service is the sworn enemy of scalability. The reason is the singleton state synchronization. Having a singleton implies the singleton has some valuable state that you wish to share across multiple clients. The problem is that when multiple clients connect to the singleton, they may all do so concurrently, and the incoming client calls will be on multiple worker threads. The singleton must synchronize access to its state to avoid state corruption. This in turn means that only one client at a time can access the singleton. This may degrade throughput, responsiveness, and availability to the point that the singleton is unusable in a decent-size system. For example, if an operation on a singleton takes one-tenth of a second, then the singleton can only service 10 clients a second. If there are more (say 20 or 100), the system's performance will be inadequate.

In general, use a singleton object if it maps well to a natural singleton in the application domain. A natural singleton is a resource that is by its very nature single and unique. Examples for natural singletons are a global logbook that all services should log their activities to, a single communication port, or a single mechanical motor. Avoid using a singleton if there is even the slightest chance that the business logic will allow more than one such service in the future, such as adding another motor or a second communication port. The reason is clear: if your clients all depend on implicitly being connected to the well-known instance, and more than one service instance is available, the clients would suddenly need to have a way to bind to the correct instance. This can have severe implications on the application's programming model. Because of these limitations, I recommend that you avoid singletons in the general case and find ways to share the state of the singleton instead of the singleton instance itself. That said, there are cases when using a singleton is acceptable as mentioned above.