What happens when InstanceContext.Release/GetServiceInstance() is called on a Singleton

InstanceContext has two methods, ReleaseServiceInstance and GetServiceInstance, that can be used by users to dynamically release and create new service instances. Service instance is the actual .Net instance of the contract on which operations are invoked. This post is to clarify the behavior of the API's when the service is hosted as a Singleton. There are two overloads in ServiceHost that lets users host a contract as singleton. If users use the overload that requires a Type then the runtime will create a instance of the class during the Open() process. If users use the Object overload then the runtime will use that instance to service all requests. The instance created in the former case is referred as "hidden" singleton and the one passed by the user in the latter case is called "well-known" singleton. The behavior of InstanceContext.Release/GetServiceInstance is different in both cases.

Technically whenever the user calls InstanceContext.ReleaseServiceInstance() the runtime will disassociate the instance from the InstanceContext and call dispose (If the instance inherits from IDisposable) on the instance. A new instance will be created when a new message arrives for that endpoint or the user explicitly calls InstanceContext.GetServiceInstance().

When ReleaseServiceInstance is called on a hidden singleton, the runtime disassociates the hidden instance from the InstanceContext but will not dispose it yet. When another message arrives for that endpoint or GetServiceInstance is called a new instance is created which will now act as the singleton instance for all future requests. This now acts as a normal service instance and so when the user calls Release/GetServiceInstance is called that instance will be disposed immediately and a new one created. The hidden singleton that was disassociated in the first call to ReleaseServiceInstance will be disposed only when the ServiceHost is closed. So even though the InstanceContextMode was set to Single, there can be 2 service instances active in the appdomain if the user calls ReleaseServiceInstance.

In case of well-known singleton, the runtime detects that its well-known and will ignore all calls to Release/GetServiceInstance i.e its just a no-op invoking those two API's. This well-known instance will never be disposed by WCF (not even during ServiceHost.Close() process) as this user owns the lifetime of the instance and the user owns the responsibility of disposing it.

Lets host a simple contract to see the behavior in both cases.

[ServiceContract]
public interface IService
{
[OperationContract]
void SayHello();

    [OperationContract]
void ReleaseInstance();
}

The class that implements this contract will give a unique ID to each instance created and will implement IDisposable so it can log when that instance is disposed by the runtime.

[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single, ConcurrencyMode=ConcurrencyMode.Single)]
public class MyService : IService, IDisposable
{
int sayHelloCount;
String id;
public MyService()
{
id = Guid.NewGuid().ToString();
Console.WriteLine("MyService Ctor called. Id = "+ id);
sayHelloCount = 0;
}

    #region IService Members

    public void SayHello()
{
sayHelloCount++;
Console.WriteLine("SayHello count : " + sayHelloCount);
}

    public void ReleaseInstance()
{
OperationContext.Current.InstanceContext.ReleaseServiceInstance();
OperationContext.Current.InstanceContext.GetServiceInstance();
}

    #endregion

    #region IDisposable Members

    public void Dispose()
{
Console.WriteLine("Destructor called for Id : " + id);
}

    #endregion
}

The SayHello() method will print the number of times it was invoked by all clients and the ReleaseInstance() when called will explicitly refresh the service instance.

Hidden Singleton Host:

Lets consider the case when the contract is hosted as a hidden singleton. The client will create call SayHello() thrice followed by ReleaseInstance() for a couple of times.

IService client = ChannelFactory<IService>.CreateChannel(new BasicHttpBinding(), new EndpointAddress("https://localhost:8080/MyService"));
client.SayHello();
client.SayHello();
client.SayHello();
client.ReleaseInstance();
client.SayHello();
client.SayHello();
client.SayHello();
client.ReleaseInstance();
client.SayHello();
client.SayHello();
client.SayHello();

The output when we run the above client is provided below.

MyService Ctor called. Id = 420f1d1b-aa68-452a-91ab-4021650b33a5
SayHello count : 1
SayHello count : 2
SayHello count : 3
Calling ReleaseServiceInstance
Calling GetServiceInstance
MyService Ctor called. Id = 349b5842-5e80-4033-a076-4a7b27017b4e
SayHello count : 1
SayHello count : 2
SayHello count : 3
Calling ReleaseServiceInstance
Destructor called for Id : 349b5842-5e80-4033-a076-4a7b27017b4e
Calling GetServiceInstance
MyService Ctor called. Id = bec7b3c6-2658-4193-b6c7-0ad6f2fdc32c
SayHello count : 1
SayHello count : 2
SayHello count : 3

Calling ServiceHost.Close()
Destructor called for Id : bec7b3c6-2658-4193-b6c7-0ad6f2fdc32c
Destructor called for Id : 420f1d1b-aa68-452a-91ab-4021650b33a5
Press any key to continue . . .

As you can see the runtime creates the hidden singleton "3cee8466-39ff-4d3e-a671-1ea8e73066bb" and then uses it to service the first three SayHello calls. Then when the user calls ReleaseInstance there is no log for the the instance being disposed. The subsequent call to GetServiceInstance creates another instance with id "149273da-2c02-4b90-a1f4-d36e87d6991d". This serves the next three calls and when the user calls and will be disposed the next time ReleaseServiceInstance is called. The hidden singleton is disposed only when the ServiceHost is closed. The Destructor called for "bec7b3c6-2658-4193-b6c7-0ad6f2fdc32c" denotes the singleton InstanceContext being closed and that inturn closing the service instance (As this instance is no longer the hidden singleton).

Well-Known Singleton case:

Lets take a look at the output in case of well-known singleton.

MyService Ctor called. Id = d224f9d8-25dd-459d-b6be-c71477ecc20e
SayHello count : 1
SayHello count : 2
SayHello count : 3
Calling ReleaseServiceInstance
Calling GetServiceInstance
SayHello count : 4
SayHello count : 5
SayHello count : 6
Calling ReleaseServiceInstance
Calling GetServiceInstance
SayHello count : 7
SayHello count : 8
SayHello count : 9
Calling ServiceHost.Close()
Press any key to continue . . .

As you can see there us no effect of calling ReleaseServiceInstance/GetServiceInstance and that the instance is never disposed.

The sample program is attached.

Maheshwar Jayaraman [WCF]

Program.cs