//----------------------------------------------------------------------------- // 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 { 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 extensions; readonly ServiceHostBase host; QuotaThrottle quotaThrottle; ServiceThrottle serviceThrottle; int instanceContextManagerIndex; object serviceInstanceLock = new object(); SynchronizationContext synchronizationContext; TransactionInstanceContextFacet transaction; object userObject; bool wellKnown; SynchronizedCollection 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 Extensions { get { this.ThrowIfClosed(); lock (this.ThisLock) { if (this.extensions == null) this.extensions = new ExtensionCollection(this, this.ThisLock); return this.extensions; } } } internal bool HasTransaction { get { return (this.transaction != null) && !object.Equals(this.transaction.Attached, null); } } public ICollection 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 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 WmiChannels { get { if (this.wmiChannels == null) { lock (this.ThisLock) { if (this.wmiChannels == null) { this.wmiChannels = new SynchronizedCollection(); } } } 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(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; } } } }