//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//---------------------------------------------------------------
namespace System.Runtime.DurableInstancing
{
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Transactions;
using System.Xml.Linq;
[Fx.Tag.XamlVisible(false)]
public sealed class InstanceHandle
{
[Fx.Tag.SynchronizationObject(Blocking=false)]
readonly object thisLock = new object();
object providerObject;
bool providerObjectSet;
bool needFreedNotification;
PreparingEnlistment pendingPreparingEnlistment;
AcquireContextAsyncResult pendingRollback;
InstanceHandleReference inProgressBind;
WaitForEventsAsyncResult waitResult;
HashSet boundOwnerEvents;
HashSet pendingOwnerEvents;
// Fields used to implement an atomic Guid Id get/set property.
Guid id;
volatile bool idIsSet;
internal InstanceHandle(InstanceStore store, InstanceOwner owner)
{
Fx.Assert(store != null, "Shouldn't be possible.");
Version = -1;
Store = store;
Owner = owner;
View = new InstanceView(owner);
IsValid = true;
}
internal InstanceHandle(InstanceStore store, InstanceOwner owner, Guid instanceId)
{
Fx.Assert(store != null, "Shouldn't be possible here either.");
Fx.Assert(instanceId != Guid.Empty, "Should be validating this.");
Version = -1;
Store = store;
Owner = owner;
Id = instanceId;
View = new InstanceView(owner, instanceId);
IsValid = true;
}
public bool IsValid { get; private set; }
internal InstanceView View { get; private set; }
internal InstanceStore Store { get; private set; }
internal InstanceOwner Owner { get; private set; }
// Since writing to a Guid field is not atomic, we need synchronization between reading and writing. The idIsSet boolean field can only
// appear true once the id field is completely written due to the memory barriers implied by the reads and writes to a volatile field.
// Writes to bool fields are atomic, and this property is only written to once. By checking the bool prior to reading the Guid, we can
// be sure that the Guid is fully materialized when read.
internal Guid Id
{
get
{
// this.idIsSet is volatile.
if (!this.idIsSet)
{
return Guid.Empty;
}
return this.id;
}
private set
{
Fx.Assert(value != Guid.Empty, "Cannot set an empty Id.");
Fx.Assert(this.id == Guid.Empty, "Cannot set Id more than once.");
Fx.Assert(!this.idIsSet, "idIsSet out of [....] with id.");
this.id = value;
// this.isIdSet is volatile.
this.idIsSet = true;
}
}
internal long Version { get; private set; }
internal InstanceHandle ConflictingHandle { get; set; }
internal object ProviderObject
{
get
{
return this.providerObject;
}
set
{
this.providerObject = value;
this.providerObjectSet = true;
}
}
// When non-null, a transaction is pending.
AcquireContextAsyncResult CurrentTransactionalAsyncResult { get; set; }
bool OperationPending { get; set; }
bool TooLateToEnlist { get; set; }
AcquireContextAsyncResult AcquirePending { get; set; }
InstancePersistenceContext CurrentExecutionContext { get; set; }
object ThisLock
{
get
{
return this.thisLock;
}
}
public void Free()
{
if (!this.providerObjectSet)
{
throw Fx.Exception.AsError(new InvalidOperationException(SRCore.HandleFreedBeforeInitialized));
}
if (!IsValid)
{
return;
}
List handlesPendingResolution = null;
WaitForEventsAsyncResult resultToCancel = null;
try
{
bool needNotification = false;
InstancePersistenceContext currentContext = null;
lock (ThisLock)
{
if (!IsValid)
{
return;
}
IsValid = false;
IEnumerable eventsToUnbind = null;
if (this.pendingOwnerEvents != null && this.pendingOwnerEvents.Count > 0)
{
eventsToUnbind = this.pendingOwnerEvents.Select(persistenceEvent => persistenceEvent.Name);
}
if (this.boundOwnerEvents != null && this.boundOwnerEvents.Count > 0)
{
eventsToUnbind = eventsToUnbind == null ? this.boundOwnerEvents : eventsToUnbind.Concat(this.boundOwnerEvents);
}
if (eventsToUnbind != null)
{
Fx.Assert(Owner != null, "How do we have owner events without an owner.");
Store.RemoveHandleFromEvents(this, eventsToUnbind, Owner);
}
if (this.waitResult != null)
{
resultToCancel = this.waitResult;
this.waitResult = null;
}
if (OperationPending)
{
if (AcquirePending != null)
{
// If in this stage, we need to short-circuit the pending transaction.
Fx.Assert(CurrentTransactionalAsyncResult != null, "Should have a pending transaction if we are waiting for it.");
CurrentTransactionalAsyncResult.WaitForHostTransaction.Set();
this.needFreedNotification = true;
}
else
{
// Here, just notify the currently executing command.
Fx.Assert(CurrentExecutionContext != null, "Must have either this or AcquirePending set.");
currentContext = CurrentExecutionContext;
}
}
else
{
needNotification = true;
if (this.inProgressBind != null)
{
Owner.CancelBind(ref this.inProgressBind, ref handlesPendingResolution);
}
else if (Version != -1)
{
// This means the handle was successfully bound in the past. Need to remove it from the table of handles.
Owner.Unbind(this);
}
}
}
if (currentContext != null)
{
// Need to do this not in a lock.
currentContext.NotifyHandleFree();
lock (ThisLock)
{
if (OperationPending)
{
this.needFreedNotification = true;
// Cancel any pending lock reclaim here.
if (this.inProgressBind != null)
{
Fx.Assert(Owner != null, "Must be bound to owner to have an inProgressBind for the lock in CancelReclaim.");
// Null reason defaults to OperationCanceledException. (Defer creating it since this might not be a
// reclaim attempt, but we don't know until we take the HandlesLock.)
Owner.FaultBind(ref this.inProgressBind, ref handlesPendingResolution, null);
}
}
else
{
needNotification = true;
}
}
}
if (needNotification)
{
Store.FreeInstanceHandle(this, ProviderObject);
}
}
finally
{
if (resultToCancel != null)
{
resultToCancel.Canceled();
}
InstanceOwner.ResolveHandles(handlesPendingResolution);
}
}
internal void BindOwnerEvent(InstancePersistenceEvent persistenceEvent)
{
lock (ThisLock)
{
Fx.Assert(OperationPending, "Should only be called during an operation.");
Fx.Assert(AcquirePending == null, "Should only be called after acquiring the transaction.");
Fx.Assert(Owner != null, "Must be bound to owner to have an owner-scoped event.");
if (IsValid && (this.boundOwnerEvents == null || !this.boundOwnerEvents.Contains(persistenceEvent.Name)))
{
if (this.pendingOwnerEvents == null)
{
this.pendingOwnerEvents = new HashSet();
}
else if (this.pendingOwnerEvents.Contains(persistenceEvent))
{
return;
}
this.pendingOwnerEvents.Add(persistenceEvent);
Store.PendHandleToEvent(this, persistenceEvent, Owner);
}
}
}
internal void StartPotentialBind()
{
lock (ThisLock)
{
Fx.AssertAndThrow(Version == -1, "Handle already bound to a lock.");
Fx.Assert(OperationPending, "Should only be called during an operation.");
Fx.Assert(AcquirePending == null, "Should only be called after acquiring the transaction.");
Fx.Assert(this.inProgressBind == null, "StartPotentialBind should only be called once per command.");
Fx.Assert(Owner != null, "Must be bound to owner to have an inProgressBind for the lock.");
Owner.StartBind(this, ref this.inProgressBind);
}
}
internal void BindOwner(InstanceOwner owner)
{
Fx.Assert(owner != null, "Null owner passed to BindOwner.");
lock (ThisLock)
{
Fx.Assert(this.inProgressBind == null, "How did we get a bind in progress without an owner?");
Fx.Assert(Owner == null, "BindOwner called when we already have an owner.");
Owner = owner;
}
}
internal void BindInstance(Guid instanceId)
{
Fx.Assert(instanceId != Guid.Empty, "BindInstance called with empty Guid.");
List handlesPendingResolution = null;
try
{
lock (ThisLock)
{
Fx.Assert(Id == Guid.Empty, "Instance already boud in BindInstance.");
Id = instanceId;
Fx.Assert(OperationPending, "BindInstance should only be called during an operation.");
Fx.Assert(AcquirePending == null, "BindInstance should only be called after acquiring the transaction.");
if (this.inProgressBind != null)
{
Fx.Assert(Owner != null, "Must be bound to owner to have an inProgressBind for the lock.");
Owner.InstanceBound(ref this.inProgressBind, ref handlesPendingResolution);
}
}
}
finally
{
InstanceOwner.ResolveHandles(handlesPendingResolution);
}
}
internal void Bind(long instanceVersion)
{
Fx.AssertAndThrow(instanceVersion >=0, "Negative instanceVersion passed to Bind.");
Fx.Assert(Owner != null, "Bind called before owner bound.");
Fx.Assert(Id != Guid.Empty, "Bind called before instance bound.");
lock (ThisLock)
{
Fx.AssertAndThrow(Version == -1, "This should only be reachable once per handle.");
Version = instanceVersion;
Fx.Assert(OperationPending, "Bind should only be called during an operation.");
Fx.Assert(AcquirePending == null, "Bind should only be called after acquiring the transaction.");
if (this.inProgressBind == null)
{
throw Fx.Exception.AsError(new InvalidOperationException(SRCore.BindLockRequiresCommandFlag));
}
}
}
// Returns null if an InstanceHandleConflictException should be thrown.
internal AsyncWaitHandle StartReclaim(long instanceVersion)
{
List handlesPendingResolution = null;
try
{
lock (ThisLock)
{
Fx.AssertAndThrow(Version == -1, "StartReclaim should only be reachable if the lock hasn't been bound.");
Fx.Assert(OperationPending, "StartReclaim should only be called during an operation.");
Fx.Assert(AcquirePending == null, "StartReclaim should only be called after acquiring the transaction.");
if (this.inProgressBind == null)
{
throw Fx.Exception.AsError(new InvalidOperationException(SRCore.BindLockRequiresCommandFlag));
}
Fx.Assert(Owner != null, "Must be bound to owner to have an inProgressBind for the lock in StartReclaim.");
return Owner.InitiateLockResolution(instanceVersion, ref this.inProgressBind, ref handlesPendingResolution);
}
}
finally
{
InstanceOwner.ResolveHandles(handlesPendingResolution);
}
}
// After calling this method, the caller doesn't need to wait for the wait handle to become set (but they can).
internal void CancelReclaim(Exception reason)
{
List handlesPendingResolution = null;
try
{
lock (ThisLock)
{
if (this.inProgressBind == null)
{
throw Fx.Exception.AsError(new InvalidOperationException(SRCore.DoNotCompleteTryCommandWithPendingReclaim));
}
Fx.Assert(Owner != null, "Must be bound to owner to have an inProgressBind for the lock in CancelReclaim.");
Owner.FaultBind(ref this.inProgressBind, ref handlesPendingResolution, reason);
}
}
finally
{
InstanceOwner.ResolveHandles(handlesPendingResolution);
}
}
// Returns the false if an InstanceHandleConflictException should be thrown.
internal bool FinishReclaim(ref long instanceVersion)
{
List handlesPendingResolution = null;
try
{
lock (ThisLock)
{
if (this.inProgressBind == null)
{
throw Fx.Exception.AsError(new InvalidOperationException(SRCore.DoNotCompleteTryCommandWithPendingReclaim));
}
Fx.Assert(Owner != null, "Must be bound to owner to have an inProgressBind for the lock in CancelReclaim.");
if (!Owner.FinishBind(ref this.inProgressBind, ref instanceVersion, ref handlesPendingResolution))
{
return false;
}
Fx.AssertAndThrow(Version == -1, "Should only be able to set the version once per handle.");
Fx.AssertAndThrow(instanceVersion >= 0, "Incorrect version resulting from conflict resolution.");
Version = instanceVersion;
return true;
}
}
finally
{
InstanceOwner.ResolveHandles(handlesPendingResolution);
}
}
[Fx.Tag.Blocking(CancelMethod = "Free")]
internal InstancePersistenceContext AcquireExecutionContext(Transaction hostTransaction, TimeSpan timeout)
{
bool setOperationPending = false;
InstancePersistenceContext result = null;
try
{
result = AcquireContextAsyncResult.End(new AcquireContextAsyncResult(this, hostTransaction, timeout, out setOperationPending));
Fx.AssertAndThrow(result != null, "Null result returned from AcquireContextAsyncResult (synchronous).");
return result;
}
finally
{
if (result == null && setOperationPending)
{
FinishOperation();
}
}
}
internal IAsyncResult BeginAcquireExecutionContext(Transaction hostTransaction, TimeSpan timeout, AsyncCallback callback, object state)
{
bool setOperationPending = false;
IAsyncResult result = null;
try
{
result = new AcquireContextAsyncResult(this, hostTransaction, timeout, out setOperationPending, callback, state);
return result;
}
finally
{
if (result == null && setOperationPending)
{
FinishOperation();
}
}
}
[Fx.Tag.Blocking(CancelMethod = "Free", Conditional = "!result.IsCompleted")]
internal InstancePersistenceContext EndAcquireExecutionContext(IAsyncResult result)
{
return AcquireContextAsyncResult.End(result);
}
internal void ReleaseExecutionContext()
{
Fx.Assert(OperationPending, "ReleaseExecutionContext called with no operation pending.");
FinishOperation();
}
// Returns null if an InstanceHandleConflictException should be thrown.
internal InstanceView Commit(InstanceView newState)
{
Fx.Assert(newState != null, "Null view passed to Commit.");
newState.MakeReadOnly();
View = newState;
List handlesPendingResolution = null;
InstanceHandle handleToFree = null;
List normals = null;
WaitForEventsAsyncResult resultToComplete = null;
try
{
lock (ThisLock)
{
if (this.inProgressBind != null)
{
// If there's a Version, it should be committed.
if (Version != -1)
{
if (!Owner.TryCompleteBind(ref this.inProgressBind, ref handlesPendingResolution, out handleToFree))
{
return null;
}
}
else
{
Fx.Assert(OperationPending, "Should have cancelled this bind in FinishOperation.");
Fx.Assert(AcquirePending == null, "Should not be in Commit during AcquirePending.");
Owner.CancelBind(ref this.inProgressBind, ref handlesPendingResolution);
}
}
if (this.pendingOwnerEvents != null && IsValid)
{
if (this.boundOwnerEvents == null)
{
this.boundOwnerEvents = new HashSet();
}
foreach (InstancePersistenceEvent persistenceEvent in this.pendingOwnerEvents)
{
if (!this.boundOwnerEvents.Add(persistenceEvent.Name))
{
Fx.Assert("Should not have conflicts between pending and bound events.");
continue;
}
InstancePersistenceEvent normal = Store.AddHandleToEvent(this, persistenceEvent, Owner);
if (normal != null)
{
if (normals == null)
{
normals = new List(this.pendingOwnerEvents.Count);
}
normals.Add(normal);
}
}
this.pendingOwnerEvents = null;
if (normals != null && this.waitResult != null)
{
resultToComplete = this.waitResult;
this.waitResult = null;
}
}
return View;
}
}
finally
{
InstanceOwner.ResolveHandles(handlesPendingResolution);
// This is a convenience, it is not required for correctness.
if (handleToFree != null)
{
Fx.Assert(!object.ReferenceEquals(handleToFree, this), "Shouldn't have been told to free ourselves.");
handleToFree.Free();
}
if (resultToComplete != null)
{
resultToComplete.Signaled(normals);
}
}
}
void OnPrepare(PreparingEnlistment preparingEnlistment)
{
bool prepareNeeded = false;
lock (ThisLock)
{
if (TooLateToEnlist)
{
// Skip this if somehow we already got rolled back or committed.
return;
}
TooLateToEnlist = true;
if (OperationPending && AcquirePending == null)
{
Fx.Assert(CurrentExecutionContext != null, "Should either be acquiring or executing in Prepare.");
this.pendingPreparingEnlistment = preparingEnlistment;
}
else
{
prepareNeeded = true;
}
}
if (prepareNeeded)
{
preparingEnlistment.Prepared();
}
}
void OnRollBack(AcquireContextAsyncResult rollingBack)
{
bool rollbackNeeded = false;
lock (ThisLock)
{
TooLateToEnlist = true;
if (OperationPending && AcquirePending == null)
{
Fx.Assert(CurrentExecutionContext != null, "Should either be acquiring or executing in RollBack.");
this.pendingRollback = rollingBack;
// Don't prepare and roll back.
this.pendingPreparingEnlistment = null;
}
else
{
rollbackNeeded = true;
}
}
if (rollbackNeeded)
{
rollingBack.RollBack();
}
}
void FinishOperation()
{
List handlesPendingResolution = null;
try
{
bool needNotification;
PreparingEnlistment preparingEnlistment;
AcquireContextAsyncResult pendingRollback;
lock (ThisLock)
{
OperationPending = false;
AcquirePending = null;
CurrentExecutionContext = null;
// This means we could have bound the handle, but didn't - clear the state here.
if (this.inProgressBind != null && (Version == -1 || !IsValid))
{
Owner.CancelBind(ref this.inProgressBind, ref handlesPendingResolution);
}
else if (Version != -1 && !IsValid)
{
// This means the handle was successfully bound in the past. Need to remove it from the table of handles.
Owner.Unbind(this);
}
needNotification = this.needFreedNotification;
this.needFreedNotification = false;
preparingEnlistment = this.pendingPreparingEnlistment;
this.pendingPreparingEnlistment = null;
pendingRollback = this.pendingRollback;
this.pendingRollback = null;
}
try
{
if (needNotification)
{
Store.FreeInstanceHandle(this, ProviderObject);
}
}
finally
{
if (pendingRollback != null)
{
Fx.Assert(preparingEnlistment == null, "Should not have both.");
pendingRollback.RollBack();
}
else if (preparingEnlistment != null)
{
preparingEnlistment.Prepared();
}
}
}
finally
{
InstanceOwner.ResolveHandles(handlesPendingResolution);
}
}
List StartWaiting(WaitForEventsAsyncResult result, IOThreadTimer timeoutTimer, TimeSpan timeout)
{
lock (ThisLock)
{
if (this.waitResult != null)
{
throw Fx.Exception.AsError(new InvalidOperationException(SRCore.WaitAlreadyInProgress));
}
if (!IsValid)
{
throw Fx.Exception.AsError(new OperationCanceledException(SRCore.HandleFreed));
}
if (this.boundOwnerEvents != null && this.boundOwnerEvents.Count > 0)
{
Fx.Assert(Owner != null, "How do we have owner events without an owner.");
List readyEvents = Store.SelectSignaledEvents(this.boundOwnerEvents, Owner);
if (readyEvents != null)
{
Fx.Assert(readyEvents.Count != 0, "Should not return a zero-length list.");
return readyEvents;
}
}
this.waitResult = result;
// This is done here to be under the lock. That way it doesn't get canceled before it is set.
if (timeoutTimer != null)
{
timeoutTimer.Set(timeout);
}
return null;
}
}
bool CancelWaiting(WaitForEventsAsyncResult result)
{
lock (ThisLock)
{
Fx.Assert(result != null, "Null result passed to CancelWaiting.");
if (!object.ReferenceEquals(this.waitResult, result))
{
return false;
}
this.waitResult = null;
return true;
}
}
internal void EventReady(InstancePersistenceEvent persistenceEvent)
{
WaitForEventsAsyncResult resultToComplete = null;
lock (ThisLock)
{
if (this.waitResult != null)
{
resultToComplete = this.waitResult;
this.waitResult = null;
}
}
if (resultToComplete != null)
{
resultToComplete.Signaled(persistenceEvent);
}
}
internal static IAsyncResult BeginWaitForEvents(InstanceHandle handle, TimeSpan timeout, AsyncCallback callback, object state)
{
return new WaitForEventsAsyncResult(handle, timeout, callback, state);
}
internal static List EndWaitForEvents(IAsyncResult result)
{
return WaitForEventsAsyncResult.End(result);
}
class AcquireContextAsyncResult : AsyncResult, IEnlistmentNotification
{
static Action