1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
|
//-----------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
namespace System.ServiceModel.Activities.Dispatcher
{
using System.Runtime;
using System.Transactions;
using System.Threading;
sealed class TransactionWaitAsyncResult : AsyncResult
{
static Action<object> timerCallback;
DependentTransaction dependentTransaction;
IOThreadTimer timer;
[Fx.Tag.SynchronizationObject(Blocking = false)]
object thisLock;
internal TransactionWaitAsyncResult(Transaction transaction, PersistenceContext persistenceContext, TimeSpan timeout, AsyncCallback callback, object state)
: base(callback, state)
{
bool completeSelf = false;
TransactionException exception = null;
this.PersistenceContext = persistenceContext;
this.thisLock = new object();
if (null != transaction)
{
// We want an "blocking" dependent transaction because we want to ensure the transaction
// does not commit successfully while we are still waiting in the queue for the PC transaction
// lock.
this.dependentTransaction = transaction.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
}
else
{
this.dependentTransaction = null;
}
// Put a lock around this and Complete() in case the transaction we are queueing up behind
// finishes and we end up calling Complete() before we actually finish constructing this
// object by creating the DependentClone and setting up the IOThreadTimer.
lock (ThisLock)
{
if (persistenceContext.QueueForTransactionLock(transaction, this))
{
// If we were given a transaction in our constructor, we need to
// create a volatile enlistment on it and complete the
// dependent clone that we created. This will allow the transaction to commit
// successfully when the time comes.
if (null != transaction)
{
// We are not going async, so we need to complete our dependent clone now.
this.dependentTransaction.Complete();
exception = this.CreateVolatileEnlistment(transaction);
}
completeSelf = true;
}
else
{
// If the timeout value is not TimeSpan.MaxValue, start a timer.
if (timeout != TimeSpan.MaxValue)
{
this.timer = new IOThreadTimer(TimeoutCallbackAction, this, true);
this.timer.Set(timeout);
}
}
}
// We didn't want to call Complete while holding the lock.
if (completeSelf)
{
base.Complete(true, exception);
}
}
internal PersistenceContext PersistenceContext { get; set; }
internal Transaction Transaction
{
get
{
return this.dependentTransaction;
}
}
object ThisLock
{
get
{
return this.thisLock;
}
}
internal static Action<object> TimeoutCallbackAction
{
get
{
if (timerCallback == null)
{
timerCallback = new Action<object>(TimeoutCallback);
}
return timerCallback;
}
}
// Returns true if this TransactionWaitAsyncResult was completed and has NOT timed out.
// Returns false if this TransactionWaitAsyncResult has timed out.
internal bool Complete()
{
Exception exception = null;
// Lock to prevent completion while we are still in the process of constructing this object.
lock (ThisLock)
{
// If we have a timer, but it has already expired, return false.
if ((this.timer != null) && (!this.timer.Cancel()))
{
return false;
}
// If we have a dependent transaction, complete it now.
if (this.dependentTransaction != null)
{
// If we were given a transaction in our constructor, we need to
// create a volatile enlistment on it and complete the
// dependent clone that we created. This will allow the transaction to commit
// successfully when the time comes.
exception = this.CreateVolatileEnlistment(this.dependentTransaction);
this.dependentTransaction.Complete();
}
}
// Indicate that we are complete.
Complete(false, exception);
return true;
}
TransactionException CreateVolatileEnlistment(Transaction transactionToEnlist)
{
TransactionException result = null;
PersistenceContextEnlistment enlistment = null;
int key = transactionToEnlist.GetHashCode();
lock (PersistenceContext.Enlistments)
{
try
{
if (!PersistenceContext.Enlistments.TryGetValue(key, out enlistment))
{
enlistment = new PersistenceContextEnlistment(this.PersistenceContext, transactionToEnlist);
transactionToEnlist.EnlistVolatile(enlistment, EnlistmentOptions.None);
// We don't save of the Enlistment object returned from EnlistVolatile. We don't need
// it here. When our PersistenceContextEnlistment object gets notified on Prepare,
// Commit, Rollback, or InDoubt, it is provided with the Enlistment object.
PersistenceContext.Enlistments.Add(key, enlistment);
}
else
{
enlistment.AddToEnlistment(this.PersistenceContext);
}
}
catch (TransactionException txException)
{
result = txException;
// We own the lock but failed to create enlistment. Manually wake up the next waiter.
// We only handle TransactionException, in case of other exception that failed to create enlistment,
// It will fallback to Timeout. This is safe to avoid multiple waiters owning same lock.
this.PersistenceContext.ScheduleNextTransactionWaiter();
}
}
return result;
}
static void TimeoutCallback(object state)
{
TransactionWaitAsyncResult thisPtr = (TransactionWaitAsyncResult)state;
Fx.Assert(null != thisPtr, "TransactionWaitAsyncResult.TimeoutCallback called with an object that is not a TransactionWaitAsyncResult.");
// As a general policy, we are not going to rollback the transaction because of this timeout. Instead, we are letting
// the caller make the decision to rollback or not based on exception we are throwing. It could be that they could
// tolerate the timeout and try something else and still commit the transaction.
if (thisPtr.dependentTransaction != null)
{
thisPtr.dependentTransaction.Complete();
}
thisPtr.Complete(false, new TimeoutException(SR.TransactionPersistenceTimeout));
}
public static void End(IAsyncResult result)
{
AsyncResult.End<TransactionWaitAsyncResult>(result);
}
}
}
|