|
//------------------------------------------------------------
// 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<FinishOperationAsyncResult>(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<StartOperationAsyncResult>(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();
}
}
}
}
}
|