465 lines
18 KiB
C#
465 lines
18 KiB
C#
|
//------------------------------------------------------------
|
||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
//------------------------------------------------------------
|
||
|
namespace System.ServiceModel.Dispatcher
|
||
|
{
|
||
|
using System.Collections.Generic;
|
||
|
using System.Runtime;
|
||
|
using System.Runtime.Diagnostics;
|
||
|
using System.ServiceModel.Channels;
|
||
|
using System.ServiceModel.Description;
|
||
|
using System.ServiceModel.Diagnostics;
|
||
|
using System.Threading;
|
||
|
using System.Diagnostics;
|
||
|
|
||
|
abstract class DurableInstanceContextProvider : IInstanceContextProvider
|
||
|
{
|
||
|
ContextCache contextCache;
|
||
|
|
||
|
bool isPerCall;
|
||
|
ServiceHostBase serviceHostBase;
|
||
|
|
||
|
protected DurableInstanceContextProvider(ServiceHostBase serviceHostBase, bool isPerCall)
|
||
|
{
|
||
|
if (serviceHostBase == null)
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("serviceHostBase");
|
||
|
}
|
||
|
|
||
|
this.serviceHostBase = serviceHostBase;
|
||
|
|
||
|
if (serviceHostBase.Description.Behaviors.Find<ServiceThrottlingBehavior>() == null)
|
||
|
{
|
||
|
serviceHostBase.ServiceThrottle.MaxConcurrentInstances = (new ServiceThrottlingBehavior()).MaxConcurrentInstances;
|
||
|
}
|
||
|
this.contextCache = new ContextCache();
|
||
|
this.isPerCall = isPerCall;
|
||
|
}
|
||
|
|
||
|
protected ContextCache Cache
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.contextCache;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public virtual InstanceContext GetExistingInstanceContext(Message message, IContextChannel channel)
|
||
|
{
|
||
|
if (message == null)
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("message");
|
||
|
}
|
||
|
if (channel == null)
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("channel");
|
||
|
}
|
||
|
|
||
|
Guid instanceId = GetInstanceIdFromMessage(message);
|
||
|
InstanceContext result = null;
|
||
|
|
||
|
if (instanceId != Guid.Empty) //Not an activation request.
|
||
|
{
|
||
|
if (contextCache.TryGetInstanceContext(instanceId, out result))
|
||
|
{
|
||
|
lock (result.ThisLock)
|
||
|
{
|
||
|
if (!string.IsNullOrEmpty(channel.SessionId) && !result.IncomingChannels.Contains(channel))
|
||
|
{
|
||
|
result.IncomingChannels.Add(channel);
|
||
|
}
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
public int GetReferenceCount(Guid instanceId)
|
||
|
{
|
||
|
return this.Cache.GetReferenceCount(instanceId);
|
||
|
}
|
||
|
|
||
|
public virtual void InitializeInstanceContext(InstanceContext instanceContext, Message message, IContextChannel channel)
|
||
|
{
|
||
|
if (instanceContext == null)
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("instanceContext");
|
||
|
}
|
||
|
if (message == null)
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("message");
|
||
|
}
|
||
|
if (channel == null)
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("channel");
|
||
|
}
|
||
|
|
||
|
Guid instanceId = GetInstanceIdFromMessage(message);
|
||
|
DurableInstance durableInstance;
|
||
|
if (instanceId == Guid.Empty) //Activation Request.
|
||
|
{
|
||
|
instanceId = Guid.NewGuid();
|
||
|
durableInstance = this.OnCreateNewInstance(instanceId);
|
||
|
message.Properties[DurableMessageDispatchInspector.NewDurableInstanceIdPropertyName] = instanceId;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
durableInstance = this.OnGetExistingInstance(instanceId);
|
||
|
}
|
||
|
|
||
|
Fx.Assert(durableInstance != null, "Durable instance should never be null at this point.");
|
||
|
durableInstance.Open();
|
||
|
|
||
|
instanceContext.Extensions.Add(durableInstance);
|
||
|
|
||
|
if (!string.IsNullOrEmpty(channel.SessionId))
|
||
|
{
|
||
|
instanceContext.IncomingChannels.Add(channel);
|
||
|
}
|
||
|
|
||
|
contextCache.AddInstanceContext(instanceId, instanceContext);
|
||
|
|
||
|
if (DiagnosticUtility.ShouldTraceInformation)
|
||
|
{
|
||
|
string traceText = SR.GetString(SR.TraceCodeDICPInstanceContextCached, instanceId);
|
||
|
TraceUtility.TraceEvent(TraceEventType.Information,
|
||
|
TraceCode.DICPInstanceContextCached, SR.GetString(SR.TraceCodeDICPInstanceContextCached),
|
||
|
new StringTraceRecord("InstanceDetail", traceText),
|
||
|
this, null);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public virtual bool IsIdle(InstanceContext instanceContext)
|
||
|
{
|
||
|
bool removed = false;
|
||
|
|
||
|
if (instanceContext == null)
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("instanceContext");
|
||
|
}
|
||
|
|
||
|
DurableInstance durableInstance = instanceContext.Extensions.Find<DurableInstance>();
|
||
|
|
||
|
if (durableInstance == null)
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
||
|
new InvalidOperationException(
|
||
|
SR2.GetString(
|
||
|
SR2.RequiredInstanceContextExtensionNotFound,
|
||
|
typeof(DurableInstance).Name)));
|
||
|
}
|
||
|
|
||
|
lock (instanceContext.ThisLock)
|
||
|
{
|
||
|
if (instanceContext.IncomingChannels.Count == 0)
|
||
|
{
|
||
|
removed = contextCache.RemoveIfNotBusy(durableInstance.InstanceId, instanceContext);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (removed && DiagnosticUtility.ShouldTraceInformation)
|
||
|
{
|
||
|
string traceText = SR.GetString(SR.TraceCodeDICPInstanceContextRemovedFromCache, durableInstance.InstanceId);
|
||
|
TraceUtility.TraceEvent(TraceEventType.Information,
|
||
|
TraceCode.DICPInstanceContextRemovedFromCache, SR.GetString(SR.TraceCodeDICPInstanceContextRemovedFromCache),
|
||
|
new StringTraceRecord("InstanceDetail", traceText),
|
||
|
this, null);
|
||
|
}
|
||
|
return removed;
|
||
|
}
|
||
|
|
||
|
public virtual void NotifyIdle(InstanceContextIdleCallback callback, InstanceContext instanceContext)
|
||
|
{
|
||
|
|
||
|
}
|
||
|
|
||
|
//Called by MessageInspector.BeforeReply
|
||
|
internal void DecrementActivityCount(Guid instanceId)
|
||
|
{
|
||
|
contextCache.ReleaseReference(instanceId);
|
||
|
}
|
||
|
|
||
|
internal void UnbindAbortedInstance(InstanceContext instanceContext, Guid instanceId)
|
||
|
{
|
||
|
if (instanceContext == null)
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("instanceContext");
|
||
|
}
|
||
|
|
||
|
//We made our best effor to clean up the instancecontext out of our cache.
|
||
|
//If another request already in middle of processing the request on InstanceContext
|
||
|
//It will Fail with CommunicationException.
|
||
|
this.contextCache.Remove(instanceId, instanceContext);
|
||
|
}
|
||
|
|
||
|
protected virtual Guid GetInstanceIdFromMessage(Message message)
|
||
|
{
|
||
|
if (!this.isPerCall)
|
||
|
{
|
||
|
ContextMessageProperty contextProperties = null;
|
||
|
string instanceId = null;
|
||
|
|
||
|
if (ContextMessageProperty.TryGet(message, out contextProperties))
|
||
|
{
|
||
|
if (contextProperties.Context.TryGetValue(WellKnownContextProperties.InstanceId, out instanceId))
|
||
|
{
|
||
|
return Fx.CreateGuid(instanceId);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return Guid.Empty;
|
||
|
}
|
||
|
|
||
|
protected abstract DurableInstance OnCreateNewInstance(Guid instanceId);
|
||
|
protected abstract DurableInstance OnGetExistingInstance(Guid instanceId);
|
||
|
|
||
|
//This class takes self contained lock, never calls out with lock taken.
|
||
|
protected class ContextCache
|
||
|
{
|
||
|
Dictionary<Guid, ContextItem> contextCache;
|
||
|
object lockObject = new object();
|
||
|
|
||
|
public ContextCache()
|
||
|
{
|
||
|
contextCache = new Dictionary<Guid, ContextItem>();
|
||
|
}
|
||
|
|
||
|
public void AddInstanceContext(Guid instanceId, InstanceContext instanceContext)
|
||
|
{
|
||
|
ContextItem contextItem;
|
||
|
int? referenceCount = null;
|
||
|
|
||
|
lock (lockObject)
|
||
|
{
|
||
|
if (!contextCache.TryGetValue(instanceId, out contextItem))
|
||
|
{
|
||
|
//This will be the case for activation request.
|
||
|
contextItem = new ContextItem(instanceId);
|
||
|
referenceCount = contextItem.AddReference();
|
||
|
contextCache.Add(instanceId, contextItem);
|
||
|
}
|
||
|
}
|
||
|
contextItem.InstanceContext = instanceContext;
|
||
|
|
||
|
if (DiagnosticUtility.ShouldTraceInformation && referenceCount.HasValue)
|
||
|
{
|
||
|
string traceText = SR2.GetString(SR2.DurableInstanceRefCountToInstanceContext, instanceId, referenceCount.Value);
|
||
|
TraceUtility.TraceEvent(TraceEventType.Information,
|
||
|
TraceCode.InstanceContextBoundToDurableInstance, SR.GetString(SR.TraceCodeInstanceContextBoundToDurableInstance),
|
||
|
new StringTraceRecord("InstanceDetail", traceText),
|
||
|
this, null);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public bool Contains(Guid instanceId, InstanceContext instanceContext)
|
||
|
{
|
||
|
ContextItem contextItem = null;
|
||
|
|
||
|
lock (this.lockObject)
|
||
|
{
|
||
|
if (contextCache.TryGetValue(instanceId, out contextItem))
|
||
|
{
|
||
|
return object.ReferenceEquals(contextItem.InstanceContext, instanceContext);
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public int GetReferenceCount(Guid instanceId)
|
||
|
{
|
||
|
int result = 0;
|
||
|
|
||
|
lock (lockObject)
|
||
|
{
|
||
|
ContextItem contextItem;
|
||
|
if (contextCache.TryGetValue(instanceId, out contextItem))
|
||
|
{
|
||
|
result = contextItem.ReferenceCount;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
public void ReleaseReference(Guid instanceId)
|
||
|
{
|
||
|
int referenceCount = -1;
|
||
|
ContextItem contextItem;
|
||
|
|
||
|
lock (lockObject)
|
||
|
{
|
||
|
if (contextCache.TryGetValue(instanceId, out contextItem))
|
||
|
{
|
||
|
referenceCount = contextItem.ReleaseReference();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Fx.Assert(false, "Cannot Release Reference of non exisiting items");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (DiagnosticUtility.ShouldTraceInformation)
|
||
|
{
|
||
|
string traceText = SR2.GetString(SR2.DurableInstanceRefCountToInstanceContext, instanceId, referenceCount);
|
||
|
TraceUtility.TraceEvent(TraceEventType.Information,
|
||
|
TraceCode.InstanceContextDetachedFromDurableInstance, SR.GetString(SR.TraceCodeInstanceContextDetachedFromDurableInstance),
|
||
|
new StringTraceRecord("InstanceDetail", traceText),
|
||
|
this, null);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public bool Remove(Guid instanceId, InstanceContext instanceContext)
|
||
|
{
|
||
|
lock (this.lockObject)
|
||
|
{
|
||
|
ContextItem contextItem = null;
|
||
|
if (this.contextCache.TryGetValue(instanceId, out contextItem))
|
||
|
{
|
||
|
if (object.ReferenceEquals(instanceContext, contextItem.InstanceContext))
|
||
|
{
|
||
|
return this.contextCache.Remove(instanceId);
|
||
|
}
|
||
|
}
|
||
|
//InstanceContext is not in memory.
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public bool RemoveIfNotBusy(Guid instanceId, InstanceContext instanceContext)
|
||
|
{
|
||
|
lock (lockObject)
|
||
|
{
|
||
|
ContextItem contextItem = null;
|
||
|
if (contextCache.TryGetValue(instanceId, out contextItem))
|
||
|
{
|
||
|
if (object.ReferenceEquals(contextItem.InstanceContext, instanceContext))
|
||
|
{
|
||
|
return (!contextItem.HasOutstandingReference)
|
||
|
&& (contextCache.Remove(instanceId));
|
||
|
}
|
||
|
}
|
||
|
//InstanceContext is not in memory.
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//Helper method to call from GetExistingInstanceContext
|
||
|
//returns true : If InstanceContext is found in cache & guaranteed to stay in cache until ReleaseReference is called.
|
||
|
//returns false : If InstanceContext is not found in cache;
|
||
|
// reference & slot is created for the ID;
|
||
|
// InitializeInstanceContext to call AddInstanceContext.
|
||
|
public bool TryGetInstanceContext(Guid instanceId, out InstanceContext instanceContext)
|
||
|
{
|
||
|
ContextItem contextItem;
|
||
|
instanceContext = null;
|
||
|
int referenceCount = -1;
|
||
|
|
||
|
try
|
||
|
{
|
||
|
lock (lockObject)
|
||
|
{
|
||
|
if (!contextCache.TryGetValue(instanceId, out contextItem))
|
||
|
{
|
||
|
contextItem = new ContextItem(instanceId);
|
||
|
referenceCount = contextItem.AddReference();
|
||
|
contextCache.Add(instanceId, contextItem);
|
||
|
return false;
|
||
|
}
|
||
|
referenceCount = contextItem.AddReference();
|
||
|
}
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
if (DiagnosticUtility.ShouldTraceInformation)
|
||
|
{
|
||
|
string traceText = SR2.GetString(SR2.DurableInstanceRefCountToInstanceContext, instanceId, referenceCount);
|
||
|
TraceUtility.TraceEvent(TraceEventType.Information,
|
||
|
TraceCode.InstanceContextBoundToDurableInstance, SR.GetString(SR.TraceCodeInstanceContextBoundToDurableInstance),
|
||
|
new StringTraceRecord("InstanceDetail", traceText),
|
||
|
this, null);
|
||
|
}
|
||
|
}
|
||
|
instanceContext = contextItem.InstanceContext;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
class ContextItem
|
||
|
{
|
||
|
InstanceContext context;
|
||
|
Guid instanceId;
|
||
|
object lockObject;
|
||
|
int referenceCount;
|
||
|
|
||
|
public ContextItem(Guid instanceId)
|
||
|
{
|
||
|
lockObject = new object();
|
||
|
referenceCount = 0;
|
||
|
this.instanceId = instanceId;
|
||
|
}
|
||
|
|
||
|
public bool HasOutstandingReference
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.referenceCount > 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public InstanceContext InstanceContext
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (this.context == null)
|
||
|
{
|
||
|
lock (this.lockObject)
|
||
|
{
|
||
|
if (this.context == null)
|
||
|
{
|
||
|
Monitor.Wait(this.lockObject);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
Fx.Assert(this.context != null, "Context cannot be null at this point");
|
||
|
return this.context;
|
||
|
}
|
||
|
set
|
||
|
{
|
||
|
if (value == null)
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("value");
|
||
|
}
|
||
|
this.context = value;
|
||
|
lock (this.lockObject)
|
||
|
{
|
||
|
Monitor.PulseAll(this.lockObject);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public int ReferenceCount
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.referenceCount;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public int AddReference()
|
||
|
{
|
||
|
//Called from higher locks taken
|
||
|
return ++this.referenceCount;
|
||
|
}
|
||
|
|
||
|
public int ReleaseReference()
|
||
|
{
|
||
|
Fx.Assert(referenceCount > 0, "Reference count gone to negative");
|
||
|
//Called from higher locks taken
|
||
|
return --this.referenceCount;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|