File: TransactionWaitAsyncResult.cs

package info (click to toggle)
mono 4.6.2.7%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 778,148 kB
  • ctags: 914,052
  • sloc: cs: 5,779,509; xml: 2,773,713; ansic: 432,645; sh: 14,749; makefile: 12,361; perl: 2,488; python: 1,434; cpp: 849; asm: 531; sql: 95; sed: 16; php: 1
file content (199 lines) | stat: -rw-r--r-- 8,244 bytes parent folder | download | duplicates (7)
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);
        }
    }
}