673 lines
20 KiB
C#
673 lines
20 KiB
C#
|
//-----------------------------------------------------------------------------
|
||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
|
||
|
namespace System.ServiceModel
|
||
|
{
|
||
|
using System.Collections.Generic;
|
||
|
using System.Diagnostics;
|
||
|
using System.Runtime;
|
||
|
using System.Runtime.CompilerServices;
|
||
|
using System.ServiceModel.Channels;
|
||
|
using System.ServiceModel.Diagnostics;
|
||
|
using System.ServiceModel.Dispatcher;
|
||
|
using System.Threading;
|
||
|
using System.ServiceModel.Diagnostics.Application;
|
||
|
|
||
|
public sealed class InstanceContext : CommunicationObject, IExtensibleObject<InstanceContext>
|
||
|
{
|
||
|
internal static InstanceContextEmptyCallback NotifyEmptyCallback = new InstanceContextEmptyCallback(InstanceContext.NotifyEmpty);
|
||
|
internal static InstanceContextIdleCallback NotifyIdleCallback = new InstanceContextIdleCallback(InstanceContext.NotifyIdle);
|
||
|
|
||
|
bool autoClose;
|
||
|
InstanceBehavior behavior;
|
||
|
ServiceChannelManager channels;
|
||
|
ConcurrencyInstanceContextFacet concurrency;
|
||
|
ExtensionCollection<InstanceContext> extensions;
|
||
|
readonly ServiceHostBase host;
|
||
|
QuotaThrottle quotaThrottle;
|
||
|
ServiceThrottle serviceThrottle;
|
||
|
int instanceContextManagerIndex;
|
||
|
object serviceInstanceLock = new object();
|
||
|
SynchronizationContext synchronizationContext;
|
||
|
TransactionInstanceContextFacet transaction;
|
||
|
object userObject;
|
||
|
bool wellKnown;
|
||
|
SynchronizedCollection<IChannel> wmiChannels;
|
||
|
bool isUserCreated;
|
||
|
|
||
|
public InstanceContext(object implementation)
|
||
|
: this(null, implementation)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
public InstanceContext(ServiceHostBase host, object implementation)
|
||
|
: this(host, implementation, true)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
internal InstanceContext(ServiceHostBase host, object implementation, bool isUserCreated)
|
||
|
: this(host, implementation, true, isUserCreated)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
internal InstanceContext(ServiceHostBase host, object implementation, bool wellKnown, bool isUserCreated)
|
||
|
{
|
||
|
this.host = host;
|
||
|
if (implementation != null)
|
||
|
{
|
||
|
this.userObject = implementation;
|
||
|
this.wellKnown = wellKnown;
|
||
|
}
|
||
|
this.autoClose = false;
|
||
|
this.channels = new ServiceChannelManager(this);
|
||
|
this.isUserCreated = isUserCreated;
|
||
|
}
|
||
|
|
||
|
public InstanceContext(ServiceHostBase host)
|
||
|
: this(host, true)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
internal InstanceContext(ServiceHostBase host, bool isUserCreated)
|
||
|
{
|
||
|
if (host == null)
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentNullException("host"));
|
||
|
}
|
||
|
|
||
|
this.host = host;
|
||
|
this.autoClose = true;
|
||
|
this.channels = new ServiceChannelManager(this, NotifyEmptyCallback);
|
||
|
this.isUserCreated = isUserCreated;
|
||
|
}
|
||
|
|
||
|
internal bool IsUserCreated
|
||
|
{
|
||
|
get { return this.isUserCreated; }
|
||
|
set { this.isUserCreated = value; }
|
||
|
}
|
||
|
|
||
|
internal bool IsWellKnown
|
||
|
{
|
||
|
get { return this.wellKnown; }
|
||
|
}
|
||
|
|
||
|
internal bool AutoClose
|
||
|
{
|
||
|
get { return this.autoClose; }
|
||
|
set { this.autoClose = value; }
|
||
|
}
|
||
|
|
||
|
internal InstanceBehavior Behavior
|
||
|
{
|
||
|
get { return this.behavior; }
|
||
|
set
|
||
|
{
|
||
|
if (this.behavior == null)
|
||
|
{
|
||
|
this.behavior = value;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal ConcurrencyInstanceContextFacet Concurrency
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (this.concurrency == null)
|
||
|
{
|
||
|
lock (this.ThisLock)
|
||
|
{
|
||
|
if (this.concurrency == null)
|
||
|
this.concurrency = new ConcurrencyInstanceContextFacet();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return this.concurrency;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal static InstanceContext Current
|
||
|
{
|
||
|
get { return OperationContext.Current != null ? OperationContext.Current.InstanceContext : null; }
|
||
|
}
|
||
|
|
||
|
protected override TimeSpan DefaultCloseTimeout
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (this.host != null)
|
||
|
{
|
||
|
return this.host.CloseTimeout;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return ServiceDefaults.CloseTimeout;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected override TimeSpan DefaultOpenTimeout
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (this.host != null)
|
||
|
{
|
||
|
return this.host.OpenTimeout;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return ServiceDefaults.OpenTimeout;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public IExtensionCollection<InstanceContext> Extensions
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
this.ThrowIfClosed();
|
||
|
lock (this.ThisLock)
|
||
|
{
|
||
|
if (this.extensions == null)
|
||
|
this.extensions = new ExtensionCollection<InstanceContext>(this, this.ThisLock);
|
||
|
return this.extensions;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal bool HasTransaction
|
||
|
{
|
||
|
get { return (this.transaction != null) && !object.Equals(this.transaction.Attached, null); }
|
||
|
}
|
||
|
|
||
|
public ICollection<IChannel> IncomingChannels
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
this.ThrowIfClosed();
|
||
|
return channels.IncomingChannels;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool IsBusy
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (this.State == CommunicationState.Closed)
|
||
|
return false;
|
||
|
return this.channels.IsBusy;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool IsSingleton
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return ((this.behavior != null) &&
|
||
|
InstanceContextProviderBase.IsProviderSingleton(this.behavior.InstanceContextProvider));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public ICollection<IChannel> OutgoingChannels
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
this.ThrowIfClosed();
|
||
|
return channels.OutgoingChannels;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public ServiceHostBase Host
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
this.ThrowIfClosed();
|
||
|
return this.host;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public int ManualFlowControlLimit
|
||
|
{
|
||
|
get { return this.EnsureQuotaThrottle().Limit; }
|
||
|
set { this.EnsureQuotaThrottle().SetLimit(value); }
|
||
|
}
|
||
|
|
||
|
internal QuotaThrottle QuotaThrottle
|
||
|
{
|
||
|
get { return this.quotaThrottle; }
|
||
|
}
|
||
|
|
||
|
internal ServiceThrottle ServiceThrottle
|
||
|
{
|
||
|
get { return this.serviceThrottle; }
|
||
|
set
|
||
|
{
|
||
|
this.ThrowIfDisposed();
|
||
|
this.serviceThrottle = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal int InstanceContextManagerIndex
|
||
|
{
|
||
|
get { return this.instanceContextManagerIndex; }
|
||
|
set { this.instanceContextManagerIndex = value; }
|
||
|
}
|
||
|
|
||
|
public SynchronizationContext SynchronizationContext
|
||
|
{
|
||
|
get { return this.synchronizationContext; }
|
||
|
set
|
||
|
{
|
||
|
this.ThrowIfClosedOrOpened();
|
||
|
this.synchronizationContext = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
new internal object ThisLock
|
||
|
{
|
||
|
get { return base.ThisLock; }
|
||
|
}
|
||
|
|
||
|
internal TransactionInstanceContextFacet Transaction
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (this.transaction == null)
|
||
|
{
|
||
|
lock (this.ThisLock)
|
||
|
{
|
||
|
if (this.transaction == null)
|
||
|
this.transaction = new TransactionInstanceContextFacet(this);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return this.transaction;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal object UserObject
|
||
|
{
|
||
|
get { return this.userObject; }
|
||
|
}
|
||
|
|
||
|
internal ICollection<IChannel> WmiChannels
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (this.wmiChannels == null)
|
||
|
{
|
||
|
lock (this.ThisLock)
|
||
|
{
|
||
|
if (this.wmiChannels == null)
|
||
|
{
|
||
|
this.wmiChannels = new SynchronizedCollection<IChannel>();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return this.wmiChannels;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected override void OnAbort()
|
||
|
{
|
||
|
channels.Abort();
|
||
|
this.Unload();
|
||
|
}
|
||
|
|
||
|
internal IAsyncResult BeginCloseInput(TimeSpan timeout, AsyncCallback callback, object state)
|
||
|
{
|
||
|
return channels.BeginCloseInput(timeout, callback, state);
|
||
|
}
|
||
|
|
||
|
internal void BindRpc(ref MessageRpc rpc)
|
||
|
{
|
||
|
this.ThrowIfClosed();
|
||
|
this.channels.IncrementActivityCount();
|
||
|
rpc.SuccessfullyBoundInstance = true;
|
||
|
}
|
||
|
|
||
|
internal void BindIncomingChannel(ServiceChannel channel)
|
||
|
{
|
||
|
this.ThrowIfDisposed();
|
||
|
|
||
|
channel.InstanceContext = this;
|
||
|
IChannel proxy = (IChannel)channel.Proxy;
|
||
|
this.channels.AddIncomingChannel(proxy);
|
||
|
|
||
|
// CSDMain 265783: Memory Leak on Chat Stress test scenario
|
||
|
// There's a race condition while on one thread we received a new request from underlying sessionful channel
|
||
|
// and on another thread we just aborted the channel. So the channel will be added to the IncomingChannels list of
|
||
|
// ServiceChannelManager and never get a chance to be removed.
|
||
|
if (proxy != null)
|
||
|
{
|
||
|
CommunicationState state = channel.State;
|
||
|
if (state == CommunicationState.Closing
|
||
|
|| state == CommunicationState.Closed
|
||
|
|| state == CommunicationState.Faulted)
|
||
|
{
|
||
|
this.channels.RemoveChannel(proxy);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CloseIfNotBusy()
|
||
|
{
|
||
|
if (!(this.State != CommunicationState.Created && this.State != CommunicationState.Opening))
|
||
|
{
|
||
|
Fx.Assert("InstanceContext.CloseIfNotBusy: (this.State != CommunicationState.Created && this.State != CommunicationState.Opening)");
|
||
|
}
|
||
|
|
||
|
if (this.State != CommunicationState.Opened)
|
||
|
return;
|
||
|
|
||
|
if (this.IsBusy)
|
||
|
return;
|
||
|
|
||
|
if (this.behavior.CanUnload(this) == false)
|
||
|
return;
|
||
|
|
||
|
try
|
||
|
{
|
||
|
if (this.State == CommunicationState.Opened)
|
||
|
this.Close();
|
||
|
}
|
||
|
catch (ObjectDisposedException e)
|
||
|
{
|
||
|
DiagnosticUtility.TraceHandledException(e, TraceEventType.Information);
|
||
|
}
|
||
|
catch (InvalidOperationException e)
|
||
|
{
|
||
|
DiagnosticUtility.TraceHandledException(e, TraceEventType.Information);
|
||
|
}
|
||
|
catch (CommunicationException e)
|
||
|
{
|
||
|
DiagnosticUtility.TraceHandledException(e, TraceEventType.Information);
|
||
|
}
|
||
|
catch (TimeoutException e)
|
||
|
{
|
||
|
if (TD.CloseTimeoutIsEnabled())
|
||
|
{
|
||
|
TD.CloseTimeout(e.Message);
|
||
|
}
|
||
|
DiagnosticUtility.TraceHandledException(e, TraceEventType.Information);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal void CloseInput(TimeSpan timeout)
|
||
|
{
|
||
|
channels.CloseInput(timeout);
|
||
|
}
|
||
|
|
||
|
internal void EndCloseInput(IAsyncResult result)
|
||
|
{
|
||
|
channels.EndCloseInput(result);
|
||
|
}
|
||
|
|
||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||
|
internal void CompleteAttachedTransaction()
|
||
|
{
|
||
|
Exception error = null;
|
||
|
|
||
|
if (!this.behavior.TransactionAutoCompleteOnSessionClose)
|
||
|
{
|
||
|
error = new Exception();
|
||
|
if (DiagnosticUtility.ShouldTraceInformation)
|
||
|
TraceUtility.TraceEvent(TraceEventType.Information,
|
||
|
TraceCode.TxCompletionStatusAbortedOnSessionClose,
|
||
|
SR.GetString(SR.TraceCodeTxCompletionStatusAbortedOnSessionClose,
|
||
|
transaction.Attached.TransactionInformation.LocalIdentifier)
|
||
|
);
|
||
|
|
||
|
}
|
||
|
else if (DiagnosticUtility.ShouldTraceInformation)
|
||
|
{
|
||
|
TraceUtility.TraceEvent(TraceEventType.Information,
|
||
|
TraceCode.TxCompletionStatusCompletedForTACOSC,
|
||
|
SR.GetString(SR.TraceCodeTxCompletionStatusCompletedForTACOSC,
|
||
|
transaction.Attached.TransactionInformation.LocalIdentifier)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
transaction.CompletePendingTransaction(transaction.Attached, error);
|
||
|
transaction.Attached = null;
|
||
|
}
|
||
|
|
||
|
QuotaThrottle EnsureQuotaThrottle()
|
||
|
{
|
||
|
lock (this.ThisLock)
|
||
|
{
|
||
|
if (this.quotaThrottle == null)
|
||
|
{
|
||
|
this.quotaThrottle = new QuotaThrottle(ImmutableDispatchRuntime.GotDynamicInstanceContext, this.ThisLock);
|
||
|
this.quotaThrottle.Owner = "InstanceContext";
|
||
|
}
|
||
|
return this.quotaThrottle;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal void FaultInternal()
|
||
|
{
|
||
|
this.Fault();
|
||
|
}
|
||
|
|
||
|
public object GetServiceInstance()
|
||
|
{
|
||
|
return this.GetServiceInstance(null);
|
||
|
}
|
||
|
|
||
|
public object GetServiceInstance(Message message)
|
||
|
{
|
||
|
lock (this.serviceInstanceLock)
|
||
|
{
|
||
|
this.ThrowIfClosedOrNotOpen();
|
||
|
|
||
|
object current = this.userObject;
|
||
|
|
||
|
if (current != null)
|
||
|
{
|
||
|
return current;
|
||
|
}
|
||
|
|
||
|
if (this.behavior == null)
|
||
|
{
|
||
|
Exception error = new InvalidOperationException(SR.GetString(SR.SFxInstanceNotInitialized));
|
||
|
if (message != null)
|
||
|
{
|
||
|
throw TraceUtility.ThrowHelperError(error, message);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(error);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
object newUserObject;
|
||
|
if (message != null)
|
||
|
{
|
||
|
newUserObject = this.behavior.GetInstance(this, message);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
newUserObject = this.behavior.GetInstance(this);
|
||
|
}
|
||
|
if (newUserObject != null)
|
||
|
{
|
||
|
this.SetUserObject(newUserObject);
|
||
|
}
|
||
|
|
||
|
return newUserObject;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public int IncrementManualFlowControlLimit(int incrementBy)
|
||
|
{
|
||
|
return this.EnsureQuotaThrottle().IncrementLimit(incrementBy);
|
||
|
}
|
||
|
|
||
|
void Load()
|
||
|
{
|
||
|
if (this.behavior != null)
|
||
|
{
|
||
|
this.behavior.Initialize(this);
|
||
|
}
|
||
|
|
||
|
if (this.host != null)
|
||
|
{
|
||
|
this.host.BindInstance(this);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void NotifyEmpty(InstanceContext instanceContext)
|
||
|
{
|
||
|
if (instanceContext.autoClose)
|
||
|
{
|
||
|
instanceContext.CloseIfNotBusy();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void NotifyIdle(InstanceContext instanceContext)
|
||
|
{
|
||
|
instanceContext.CloseIfNotBusy();
|
||
|
}
|
||
|
|
||
|
protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state)
|
||
|
{
|
||
|
return new CloseAsyncResult(timeout, callback, state, this);
|
||
|
}
|
||
|
|
||
|
protected override void OnEndClose(IAsyncResult result)
|
||
|
{
|
||
|
CloseAsyncResult.End(result);
|
||
|
}
|
||
|
|
||
|
protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state)
|
||
|
{
|
||
|
return new CompletedAsyncResult(callback, state);
|
||
|
}
|
||
|
|
||
|
protected override void OnEndOpen(IAsyncResult result)
|
||
|
{
|
||
|
CompletedAsyncResult.End(result);
|
||
|
}
|
||
|
|
||
|
protected override void OnClose(TimeSpan timeout)
|
||
|
{
|
||
|
channels.Close(timeout);
|
||
|
this.Unload();
|
||
|
}
|
||
|
|
||
|
protected override void OnClosed()
|
||
|
{
|
||
|
base.OnClosed();
|
||
|
|
||
|
ServiceThrottle throttle = this.serviceThrottle;
|
||
|
if (throttle != null)
|
||
|
{
|
||
|
throttle.DeactivateInstanceContext();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected override void OnFaulted()
|
||
|
{
|
||
|
base.OnFaulted();
|
||
|
|
||
|
if (this.IsSingleton && (this.host != null))
|
||
|
{
|
||
|
this.host.FaultInternal();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected override void OnOpen(TimeSpan timeout)
|
||
|
{
|
||
|
TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
|
||
|
}
|
||
|
|
||
|
protected override void OnOpened()
|
||
|
{
|
||
|
base.OnOpened();
|
||
|
}
|
||
|
|
||
|
protected override void OnOpening()
|
||
|
{
|
||
|
this.Load();
|
||
|
base.OnOpening();
|
||
|
}
|
||
|
|
||
|
public void ReleaseServiceInstance()
|
||
|
{
|
||
|
this.ThrowIfDisposedOrNotOpen();
|
||
|
this.SetUserObject(null);
|
||
|
}
|
||
|
|
||
|
void SetUserObject(object newUserObject)
|
||
|
{
|
||
|
if (this.behavior != null && !this.wellKnown)
|
||
|
{
|
||
|
object oldUserObject = Interlocked.Exchange(ref this.userObject, newUserObject);
|
||
|
|
||
|
if ((oldUserObject != null) && (this.host != null) && !Object.Equals(oldUserObject, this.host.DisposableInstance))
|
||
|
{
|
||
|
this.behavior.ReleaseInstance(this, oldUserObject);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal void UnbindRpc(ref MessageRpc rpc)
|
||
|
{
|
||
|
if (rpc.InstanceContext == this && rpc.SuccessfullyBoundInstance)
|
||
|
{
|
||
|
this.channels.DecrementActivityCount();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal void UnbindIncomingChannel(ServiceChannel channel)
|
||
|
{
|
||
|
this.channels.RemoveChannel((IChannel)channel.Proxy);
|
||
|
}
|
||
|
|
||
|
void Unload()
|
||
|
{
|
||
|
this.SetUserObject(null);
|
||
|
|
||
|
if (this.host != null)
|
||
|
{
|
||
|
this.host.UnbindInstance(this);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class CloseAsyncResult : AsyncResult
|
||
|
{
|
||
|
InstanceContext instanceContext;
|
||
|
TimeoutHelper timeoutHelper;
|
||
|
|
||
|
public CloseAsyncResult(TimeSpan timeout, AsyncCallback callback, object state, InstanceContext instanceContext)
|
||
|
: base(callback, state)
|
||
|
{
|
||
|
this.timeoutHelper = new TimeoutHelper(timeout);
|
||
|
this.instanceContext = instanceContext;
|
||
|
IAsyncResult result = this.instanceContext.channels.BeginClose(this.timeoutHelper.RemainingTime(), PrepareAsyncCompletion(new AsyncCompletion(CloseChannelsCallback)), this);
|
||
|
if (result.CompletedSynchronously && CloseChannelsCallback(result))
|
||
|
{
|
||
|
base.Complete(true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static void End(IAsyncResult result)
|
||
|
{
|
||
|
AsyncResult.End<CloseAsyncResult>(result);
|
||
|
}
|
||
|
|
||
|
bool CloseChannelsCallback(IAsyncResult result)
|
||
|
{
|
||
|
Fx.Assert(object.ReferenceEquals(this, result.AsyncState), "AsyncState should be this");
|
||
|
this.instanceContext.channels.EndClose(result);
|
||
|
this.instanceContext.Unload();
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|