//------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ namespace System.ServiceModel.Dispatcher { using System; using System.Runtime; using System.Runtime.Diagnostics; using System.ServiceModel; using System.ServiceModel.Description; using System.ServiceModel.Diagnostics; using System.ServiceModel.Persistence; using System.Transactions; using System.Diagnostics; class ServiceDurableInstance : DurableInstance { bool abortInstance; DependentTransaction clonedTransaction; ServiceDurableInstanceContextProvider contextManager; bool existsInPersistence; object instance; LockingPersistenceProvider lockingProvider; bool markedForCompletion; Type newServiceType; TimeSpan operationTimeout; int outstandingOperations; PersistenceProvider provider; DurableRuntimeValidator runtimeValidator; bool saveStateInOperationTransaction; UnknownExceptionAction unknownExceptionAction; public ServiceDurableInstance( PersistenceProvider persistenceProvider, ServiceDurableInstanceContextProvider contextManager, bool saveStateInOperationTransaction, UnknownExceptionAction unknownExceptionAction, DurableRuntimeValidator runtimeValidator, TimeSpan operationTimeout) : this(persistenceProvider, contextManager, saveStateInOperationTransaction, unknownExceptionAction, runtimeValidator, operationTimeout, null) { } public ServiceDurableInstance( PersistenceProvider persistenceProvider, ServiceDurableInstanceContextProvider contextManager, bool saveStateInOperationTransaction, UnknownExceptionAction unknownExceptionAction, DurableRuntimeValidator runtimeValidator, TimeSpan operationTimeout, Type serviceType) : base(contextManager, persistenceProvider == null ? Guid.Empty : persistenceProvider.Id) { if (persistenceProvider == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("persistenceProvider"); } if (contextManager == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("contextManager"); } if (runtimeValidator == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("runtimeValidator"); } Fx.Assert(operationTimeout > TimeSpan.Zero, "Timeout needs to be greater than zero."); this.lockingProvider = persistenceProvider as LockingPersistenceProvider; this.provider = persistenceProvider; this.contextManager = contextManager; this.saveStateInOperationTransaction = saveStateInOperationTransaction; this.unknownExceptionAction = unknownExceptionAction; this.runtimeValidator = runtimeValidator; this.operationTimeout = operationTimeout; this.newServiceType = serviceType; } enum OperationType { None = 0, Delete = 1, Unlock = 2, Create = 3, Update = 4 } public object Instance { get { return this.instance; } } public void AbortInstance() { ConcurrencyMode concurrencyMode = this.runtimeValidator.ConcurrencyMode; if (concurrencyMode != ConcurrencyMode.Single) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new InvalidOperationException( SR2.GetString(SR2.AbortInstanceRequiresSingle))); } if (this.saveStateInOperationTransaction) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new InvalidOperationException( SR2.GetString(SR2.CannotAbortWithSaveStateInTransaction))); } if (this.markedForCompletion) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new InvalidOperationException( SR2.GetString( SR2.DurableOperationMethodInvalid, "AbortInstance", "CompleteInstance"))); } this.abortInstance = true; } public IAsyncResult BeginFinishOperation(bool completeInstance, bool performPersistence, Exception operationException, AsyncCallback callback, object state) { return new FinishOperationAsyncResult(this, completeInstance, performPersistence, operationException, callback, state); } public IAsyncResult BeginStartOperation(bool canCreateInstance, AsyncCallback callback, object state) { return new StartOperationAsyncResult(this, canCreateInstance, callback, state); } public void EndFinishOperation(IAsyncResult result) { FinishOperationAsyncResult.End(result); } public object EndStartOperation(IAsyncResult result) { return StartOperationAsyncResult.End(result); } public void FinishOperation(bool completeInstance, bool performPersistence, Exception operationException) { try { bool disposeInstance; OperationType operation = FinishOperationCommon(completeInstance, operationException, out disposeInstance); Fx.Assert( (performPersistence || (operation != OperationType.Delete && operation != OperationType.Unlock)), "If we aren't performing persistence then we are a NotAllowed contract and therefore should never have loaded from persistence."); if (performPersistence) { switch (operation) { case OperationType.Unlock: // Do the null check out here to avoid creating the scope if (this.lockingProvider != null) { using (PersistenceScope scope = new PersistenceScope( this.saveStateInOperationTransaction, this.clonedTransaction)) { this.lockingProvider.Unlock(this.operationTimeout); } } break; case OperationType.Delete: using (PersistenceScope scope = new PersistenceScope( this.saveStateInOperationTransaction, this.clonedTransaction)) { this.provider.Delete(this.instance, this.operationTimeout); if (DiagnosticUtility.ShouldTraceInformation) { string traceText = SR.GetString(SR.TraceCodeServiceDurableInstanceDeleted, this.InstanceId); TraceUtility.TraceEvent(TraceEventType.Information, TraceCode.ServiceDurableInstanceDeleted, traceText, new StringTraceRecord("DurableInstanceDetail", traceText), this, null); } } break; case OperationType.Create: using (PersistenceScope scope = new PersistenceScope( this.saveStateInOperationTransaction, this.clonedTransaction)) { if (this.lockingProvider != null) { this.lockingProvider.Create(this.Instance, this.operationTimeout, disposeInstance); } else { this.provider.Create(this.Instance, this.operationTimeout); } if (DiagnosticUtility.ShouldTraceInformation) { string traceText = SR2.GetString(SR2.ServiceDurableInstanceSavedDetails, this.InstanceId, (this.lockingProvider != null) ? "True" : "False"); TraceUtility.TraceEvent(TraceEventType.Information, TraceCode.ServiceDurableInstanceSaved, SR.GetString(SR.TraceCodeServiceDurableInstanceSaved), new StringTraceRecord("DurableInstanceDetail", traceText), this, null); } } break; case OperationType.Update: using (PersistenceScope scope = new PersistenceScope( this.saveStateInOperationTransaction, this.clonedTransaction)) { if (this.lockingProvider != null) { this.lockingProvider.Update(this.Instance, this.operationTimeout, disposeInstance); } else { this.provider.Update(this.Instance, this.operationTimeout); } if (DiagnosticUtility.ShouldTraceInformation) { string traceText = SR2.GetString(SR2.ServiceDurableInstanceSavedDetails, this.InstanceId, (this.lockingProvider != null) ? "True" : "False"); TraceUtility.TraceEvent(TraceEventType.Information, TraceCode.ServiceDurableInstanceSaved, SR.GetString(SR.TraceCodeServiceDurableInstanceSaved), new StringTraceRecord("DurableInstanceDetail", traceText), this, null); } } break; case OperationType.None: break; default: Fx.Assert("We should never get an unknown OperationType."); break; } } if (disposeInstance) { DisposeInstance(); } } finally { CompleteClonedTransaction(); } } public void MarkForCompletion() { if (this.abortInstance) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new InvalidOperationException( SR2.GetString( SR2.DurableOperationMethodInvalid, "CompleteInstance", "AbortInstance"))); } this.markedForCompletion = true; } public object StartOperation(bool canCreateInstance) { using (StartOperationScope scope = new StartOperationScope(this)) { if (this.markedForCompletion) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new InstanceNotFoundException(this.InstanceId)); } if (this.instance == null) { if (!TryActivateInstance(canCreateInstance)) { using (PersistenceScope persistenceScope = new PersistenceScope( this.saveStateInOperationTransaction, this.clonedTransaction)) { if (this.lockingProvider != null) { this.instance = this.lockingProvider.Load(this.operationTimeout, true); } else { this.instance = this.provider.Load(this.operationTimeout); } if (DiagnosticUtility.ShouldTraceInformation) { string traceText = SR2.GetString(SR2.ServiceDurableInstanceLoadedDetails, this.InstanceId, (this.lockingProvider != null) ? "True" : "False"); TraceUtility.TraceEvent(TraceEventType.Information, TraceCode.ServiceDurableInstanceLoaded, SR.GetString(SR.TraceCodeServiceDurableInstanceLoaded), new StringTraceRecord("DurableInstanceDetail", traceText), this, null); } } this.existsInPersistence = true; } } scope.Complete(); } Fx.Assert( this.instance != null, "Instance should definitely be non-null here or we should have thrown an exception."); return this.instance; } protected override void OnAbort() { this.provider.Abort(); } protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state) { return this.provider.BeginClose(timeout, callback, state); } protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state) { return this.provider.BeginOpen(timeout, callback, state); } protected override void OnClose(TimeSpan timeout) { this.provider.Close(timeout); } protected override void OnEndClose(IAsyncResult result) { this.provider.EndClose(result); } protected override void OnEndOpen(IAsyncResult result) { this.provider.EndOpen(result); } protected override void OnOpen(TimeSpan timeout) { this.provider.Open(timeout); } void CompleteClonedTransaction() { if (this.clonedTransaction != null) { this.clonedTransaction.Complete(); this.clonedTransaction = null; } } void DisposeInstance() { Fx.Assert( this.instance != null, "Before making this call we should check instance for null."); IDisposable disposableInstance = this.instance as IDisposable; if (disposableInstance != null) { disposableInstance.Dispose(); if (DiagnosticUtility.ShouldTraceInformation) { string traceText = SR.GetString(SR.TraceCodeServiceDurableInstanceDisposed, this.InstanceId); TraceUtility.TraceEvent(TraceEventType.Information, TraceCode.ServiceDurableInstanceDisposed, SR.GetString(SR.TraceCodeServiceDurableInstanceDisposed), new StringTraceRecord("DurableInstanceDetail", traceText), this, null); } } this.instance = null; } OperationType FinishOperationCommon(bool completeInstance, Exception operationException, out bool disposeInstance) { // No need for Interlocked because we don't support // ConcurrencyMode.Multiple this.outstandingOperations--; DurableOperationContext.EndOperation(); Fx.Assert(this.outstandingOperations >= 0, "OutstandingOperations should never go below zero."); Fx.Assert(this.instance != null, "Instance should never been null here - we only get here if StartOperation completes successfully."); OperationType operation = OperationType.None; disposeInstance = false; // This is a "fuzzy" still referenced. Immediately // after this line another message could come in and // reference this InstanceContext, but it doesn't matter // because regardless of scheme used the other message // would have had to reacquire the database lock. bool stillReferenced = this.contextManager.GetReferenceCount(this.InstanceId) > 1; this.markedForCompletion |= completeInstance; if (this.outstandingOperations == 0) { if (this.saveStateInOperationTransaction && this.clonedTransaction != null && this.clonedTransaction.TransactionInformation.Status == TransactionStatus.Aborted) { this.abortInstance = false; this.markedForCompletion = false; disposeInstance = true; } else if (operationException != null && !(operationException is FaultException)) { if (this.unknownExceptionAction == UnknownExceptionAction.TerminateInstance) { if (this.existsInPersistence) { operation = OperationType.Delete; } this.existsInPersistence = true; disposeInstance = true; } else { Fx.Assert(this.unknownExceptionAction == UnknownExceptionAction.AbortInstance, "If it is not TerminateInstance then it must be AbortInstance."); if (this.existsInPersistence) { operation = OperationType.Unlock; } this.existsInPersistence = true; disposeInstance = true; this.markedForCompletion = false; } } else if (this.abortInstance) { this.abortInstance = false; // AbortInstance can only be called in ConcurrencyMode.Single // and therefore markedForCompletion could only have been // set true by this same operation (either declaratively or // programmatically). We set it false again so that the // next operation doesn't cause instance completion. this.markedForCompletion = false; if (this.existsInPersistence && !stillReferenced) { // No need for a transactional version of this as we do not allow // AbortInstance to be called in scenarios with SaveStateInOperationTransaction // set to true Fx.Assert(!this.saveStateInOperationTransaction, "SaveStateInOperationTransaction must be false if we allowed an abort."); if (this.lockingProvider != null) { operation = OperationType.Unlock; } } this.existsInPersistence = true; disposeInstance = true; } else if (this.markedForCompletion) { if (this.existsInPersistence) { // We don't set exists in persistence to // false here because we want the proper // persistence exceptions to get back to the // client if we end up here again. operation = OperationType.Delete; } // Even if we didn't delete the instance because it // never existed we should set this to true. This will // make sure that any future requests to this instance of // ServiceDurableInstance will treat the object as deleted. this.existsInPersistence = true; disposeInstance = true; } else { if (this.existsInPersistence) { operation = OperationType.Update; } else { operation = OperationType.Create; } this.existsInPersistence = true; if (!stillReferenced) { disposeInstance = true; } } } return operation; } bool TryActivateInstance(bool canCreateInstance) { if (this.newServiceType != null && !this.existsInPersistence) { if (canCreateInstance) { this.instance = Activator.CreateInstance(this.newServiceType); return true; } else { DurableErrorHandler.CleanUpInstanceContextAtOperationCompletion(); throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FaultException(new DurableDispatcherAddressingFault())); } } return false; } class FinishOperationAsyncResult : AsyncResult { static AsyncCallback createCallback = Fx.ThunkCallback(new AsyncCallback(CreateComplete)); static AsyncCallback deleteCallback = Fx.ThunkCallback(new AsyncCallback(DeleteComplete)); static AsyncCallback unlockCallback = Fx.ThunkCallback(new AsyncCallback(UnlockComplete)); static AsyncCallback updateCallback = Fx.ThunkCallback(new AsyncCallback(UpdateComplete)); ServiceDurableInstance durableInstance; public FinishOperationAsyncResult(ServiceDurableInstance durableInstance, bool completeInstance, bool performPersistence, Exception operationException, AsyncCallback callback, object state) : base(callback, state) { this.durableInstance = durableInstance; IAsyncResult result = null; OperationType operation = OperationType.None; bool completeSelf = false; bool disposeInstace; operation = this.durableInstance.FinishOperationCommon(completeInstance, operationException, out disposeInstace); if (performPersistence) { switch (operation) { case OperationType.Unlock: if (this.durableInstance.lockingProvider != null) { using (PersistenceScope scope = new PersistenceScope( this.durableInstance.saveStateInOperationTransaction, this.durableInstance.clonedTransaction)) { result = this.durableInstance.lockingProvider.BeginUnlock(this.durableInstance.operationTimeout, unlockCallback, this); } } break; case OperationType.Delete: using (PersistenceScope scope = new PersistenceScope( this.durableInstance.saveStateInOperationTransaction, this.durableInstance.clonedTransaction)) { result = this.durableInstance.provider.BeginDelete(this.durableInstance.Instance, this.durableInstance.operationTimeout, deleteCallback, this); } break; case OperationType.Create: using (PersistenceScope scope = new PersistenceScope( this.durableInstance.saveStateInOperationTransaction, this.durableInstance.clonedTransaction)) { if (this.durableInstance.lockingProvider != null) { result = this.durableInstance.lockingProvider.BeginCreate(this.durableInstance.Instance, this.durableInstance.operationTimeout, disposeInstace, createCallback, this); } else { result = this.durableInstance.provider.BeginCreate(this.durableInstance.Instance, this.durableInstance.operationTimeout, createCallback, this); } } break; case OperationType.Update: using (PersistenceScope scope = new PersistenceScope( this.durableInstance.saveStateInOperationTransaction, this.durableInstance.clonedTransaction)) { if (this.durableInstance.lockingProvider != null) { result = this.durableInstance.lockingProvider.BeginUpdate(this.durableInstance.Instance, this.durableInstance.operationTimeout, disposeInstace, updateCallback, this); } else { result = this.durableInstance.provider.BeginUpdate(this.durableInstance.Instance, this.durableInstance.operationTimeout, updateCallback, this); } } break; case OperationType.None: break; default: Fx.Assert("Unknown OperationType was passed in."); break; } } if (disposeInstace) { this.durableInstance.DisposeInstance(); } if (operation == OperationType.None || (result != null && result.CompletedSynchronously)) { completeSelf = true; } if (!performPersistence) { Fx.Assert(result == null, "Should not have had a result if we didn't perform persistence."); Complete(true); return; } if (completeSelf) { CallEndOperation(operation, result); Complete(true); } } public static void End(IAsyncResult result) { AsyncResult.End(result); } static void CreateComplete(IAsyncResult result) { HandleOperationCompletion(OperationType.Create, result); } static void DeleteComplete(IAsyncResult result) { HandleOperationCompletion(OperationType.Delete, result); } static void HandleOperationCompletion(OperationType operation, IAsyncResult result) { if (result.CompletedSynchronously) { return; } Fx.Assert(result.AsyncState is FinishOperationAsyncResult, "Async state should have been FinishOperationAsyncResult"); FinishOperationAsyncResult finishResult = (FinishOperationAsyncResult) result.AsyncState; Exception completionException = null; try { finishResult.CallEndOperation(operation, result); } catch (Exception e) { if (Fx.IsFatal(e)) { throw; } completionException = e; } finishResult.Complete(false, completionException); } static void UnlockComplete(IAsyncResult result) { HandleOperationCompletion(OperationType.Unlock, result); } static void UpdateComplete(IAsyncResult result) { HandleOperationCompletion(OperationType.Update, result); } void CallEndOperation(OperationType operation, IAsyncResult result) { try { switch (operation) { case OperationType.Delete: this.durableInstance.provider.EndDelete(result); break; case OperationType.Unlock: this.durableInstance.lockingProvider.EndUnlock(result); break; case OperationType.Create: this.durableInstance.provider.EndCreate(result); break; case OperationType.Update: this.durableInstance.provider.EndUpdate(result); break; case OperationType.None: break; default: Fx.Assert("Should never have an unknown value for this enum."); break; } } finally { this.durableInstance.CompleteClonedTransaction(); } } } class PersistenceScope : IDisposable { DependentTransaction clonedTransaction; TransactionScope scope; public PersistenceScope(bool saveStateInOperationTransaction, DependentTransaction clonedTransaction) { if (!saveStateInOperationTransaction) { this.scope = new TransactionScope(TransactionScopeOption.Suppress); } else if (clonedTransaction != null) { this.clonedTransaction = clonedTransaction; this.scope = new TransactionScope(clonedTransaction); } } public void Dispose() { if (this.scope != null) { this.scope.Complete(); this.scope.Dispose(); this.scope = null; } } } class StartOperationAsyncResult : AsyncResult { static AsyncCallback loadCallback = Fx.ThunkCallback(new AsyncCallback(LoadComplete)); ServiceDurableInstance durableInstance; OperationContext operationContext; StartOperationScope scope; public StartOperationAsyncResult(ServiceDurableInstance durableInstance, bool canCreateInstance, AsyncCallback callback, object state) : base(callback, state) { this.durableInstance = durableInstance; bool completeSelf = false; IAsyncResult result = null; this.operationContext = OperationContext.Current; scope = new StartOperationScope(this.durableInstance); bool success = false; try { if (this.durableInstance.instance == null) { if (this.durableInstance.TryActivateInstance(canCreateInstance)) { completeSelf = true; } else { using (PersistenceScope persistenceScope = new PersistenceScope( this.durableInstance.saveStateInOperationTransaction, this.durableInstance.clonedTransaction)) { if (this.durableInstance.lockingProvider != null) { result = this.durableInstance.lockingProvider.BeginLoad(this.durableInstance.operationTimeout, true, loadCallback, this); } else { result = this.durableInstance.provider.BeginLoad(this.durableInstance.operationTimeout, loadCallback, this); } } this.durableInstance.existsInPersistence = true; if (result.CompletedSynchronously) { completeSelf = true; } } } else { completeSelf = true; } success = true; } finally { if (!success) { scope.Dispose(); } } if (completeSelf) { try { if (result != null) { this.durableInstance.instance = this.durableInstance.provider.EndLoad(result); } Fx.Assert(this.durableInstance.instance != null, "The instance should always be set here."); Complete(true); scope.Complete(); } finally { scope.Dispose(); } } } public static object End(IAsyncResult result) { StartOperationAsyncResult startResult = AsyncResult.End(result); return startResult.durableInstance.instance; } static void LoadComplete(IAsyncResult result) { if (result.CompletedSynchronously) { return; } Fx.Assert(result.AsyncState is StartOperationAsyncResult, "Should have been passed a StartOperationAsyncResult as the state"); StartOperationAsyncResult startResult = (StartOperationAsyncResult) result.AsyncState; Exception completionException = null; OperationContext oldOperationContext = OperationContext.Current; OperationContext.Current = startResult.operationContext; try { try { startResult.durableInstance.instance = startResult.durableInstance.provider.EndLoad(result); Fx.Assert(startResult.durableInstance.instance != null, "The instance should always be set here."); startResult.scope.Complete(); } catch (Exception e) { if (Fx.IsFatal(e)) { throw; } completionException = e; } finally { startResult.scope.Dispose(); } startResult.Complete(false, completionException); } finally { OperationContext.Current = oldOperationContext; } } } class StartOperationScope : IDisposable { ServiceDurableInstance durableInstance; bool success; public StartOperationScope(ServiceDurableInstance durableInstance) { this.durableInstance = durableInstance; this.durableInstance.runtimeValidator.ValidateRuntime(); DurableOperationContext.BeginOperation(); // No need for Interlocked because we don't support // ConcurrencyMode.Multiple this.durableInstance.outstandingOperations++; if (this.durableInstance.saveStateInOperationTransaction && Transaction.Current != null) { this.durableInstance.clonedTransaction = Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete); } } public void Complete() { this.success = true; } public void Dispose() { if (!this.success) { Fx.Assert(OperationContext.Current != null, "Operation context should not be null at this point."); DurableOperationContext.EndOperation(); this.durableInstance.outstandingOperations--; this.durableInstance.CompleteClonedTransaction(); } } } } }