1 //----------------------------------------------------------------------------- 2 // Copyright (c) Microsoft Corporation. All rights reserved. 3 //----------------------------------------------------------------------------- 4 5 namespace System.ServiceModel.Activities.Dispatcher 6 { 7 using System.Collections.Generic; 8 using System.Runtime; 9 using System.Threading; 10 using System.Transactions; 11 12 sealed class PersistenceContextEnlistment : IEnlistmentNotification 13 { 14 PreparingEnlistment preparingEnlistment; 15 Enlistment enlistment; 16 17 // This will be true if we have received either a Prepare or Rollback 18 // notification from the transaction manager. If this is true, it is too 19 // late to try to add more entries to the undo collection. 20 bool tooLateForMoreUndo; 21 Transaction transaction; 22 object ThisLock = new object(); 23 24 List<PersistenceContext> enlistedContexts; 25 26 static Action<object> prepareCallback; 27 static Action<object> commitCallback; 28 static Action<object> rollbackCallback; 29 static Action<object> indoubtCallback; 30 PersistenceContextEnlistment(PersistenceContext context, Transaction transaction)31 internal PersistenceContextEnlistment(PersistenceContext context, Transaction transaction) 32 { 33 this.transaction = transaction; 34 35 this.enlistedContexts = new List<PersistenceContext>(); 36 this.enlistedContexts.Add(context); 37 } 38 AddToEnlistment(PersistenceContext context)39 internal void AddToEnlistment(PersistenceContext context) 40 { 41 lock (this.ThisLock) 42 { 43 if (tooLateForMoreUndo) 44 { 45 throw FxTrace.Exception.AsError(new InvalidOperationException(SR.PersistenceTooLateToEnlist)); 46 } 47 48 this.enlistedContexts.Add(context); 49 } 50 } 51 52 internal static Action<object> PrepareCallback 53 { 54 get 55 { 56 if (prepareCallback == null) 57 { 58 prepareCallback = new Action<object>(DoPrepare); 59 } 60 return prepareCallback; 61 } 62 } 63 64 internal static Action<object> CommitCallback 65 { 66 get 67 { 68 if (commitCallback == null) 69 { 70 commitCallback = new Action<object>(DoCommit); 71 } 72 return commitCallback; 73 } 74 } 75 76 internal static Action<object> RollbackCallback 77 { 78 get 79 { 80 if (rollbackCallback == null) 81 { 82 rollbackCallback = new Action<object>(DoRollback); 83 } 84 return rollbackCallback; 85 } 86 } 87 88 internal static Action<object> IndoubtCallback 89 { 90 get 91 { 92 if (indoubtCallback == null) 93 { 94 indoubtCallback = new Action<object>(DoIndoubt); 95 } 96 return indoubtCallback; 97 } 98 } 99 IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)100 void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment) 101 { 102 // We don't want to try to grab one of our locks while executing on the 103 // System.Transactions notification thread because that will block all 104 // the other notifications that need to be made. So schedule the 105 // processing of this on another thread. If we decide that the locks 106 // aren't necessary, we can get rid of this. 107 this.preparingEnlistment = preparingEnlistment; 108 ActionItem.Schedule(PrepareCallback, this); 109 } 110 IEnlistmentNotification.Commit(Enlistment enlistment)111 void IEnlistmentNotification.Commit(Enlistment enlistment) 112 { 113 // We don't want to try to grab one of our locks while executing on the 114 // System.Transactions notification thread because that will block all 115 // the other notifications that need to be made. So schedule the 116 // processing of this on another thread. If we decide that the locks 117 // aren't necessary, we can get rid of this. 118 this.enlistment = enlistment; 119 ActionItem.Schedule(CommitCallback, this); 120 } 121 IEnlistmentNotification.Rollback(Enlistment enlistment)122 void IEnlistmentNotification.Rollback(Enlistment enlistment) 123 { 124 // We don't want to try to grab one of our locks while executing on the 125 // System.Transactions notification thread because that will block all 126 // the other notifications that need to be made. So schedule the 127 // processing of this on another thread. If we decide that the locks 128 // aren't necessary, we can get rid of this. 129 this.enlistment = enlistment; 130 ActionItem.Schedule(RollbackCallback, this); 131 } 132 IEnlistmentNotification.InDoubt(Enlistment enlistment)133 void IEnlistmentNotification.InDoubt(Enlistment enlistment) 134 { 135 // We don't want to try to grab one of our locks while executing on the 136 // System.Transactions notification thread because that will block all 137 // the other notifications that need to be made. So schedule the 138 // processing of this on another thread. If we decide that the locks 139 // aren't necessary, we can get rid of this. 140 this.enlistment = enlistment; 141 ActionItem.Schedule(IndoubtCallback, this); 142 } 143 DoPrepare(object state)144 internal static void DoPrepare(object state) 145 { 146 PersistenceContextEnlistment pcEnlist = state as PersistenceContextEnlistment; 147 Fx.Assert(null != pcEnlist, "PersistenceContextEnlistment.DoPrepare called with an object that is not a PersistenceContext."); 148 149 lock (pcEnlist.ThisLock) 150 { 151 pcEnlist.tooLateForMoreUndo = true; 152 } 153 154 // This needs to be done outside of the lock because it could induce System.Transactions 155 // to do a whole bunch of other work inline, including issuing the Commit call and doing 156 // Completion notifications for hte transaction. We don't want to be holding the lock 157 // during all of that. 158 pcEnlist.preparingEnlistment.Prepared(); 159 } 160 DoCommit(object state)161 internal static void DoCommit(object state) 162 { 163 PersistenceContextEnlistment pcEnlist = state as PersistenceContextEnlistment; 164 Fx.Assert(null != pcEnlist, "PersistenceContextEnlistment.DoCommit called with an object that is not a PersistenceContext."); 165 166 lock (pcEnlist.ThisLock) 167 { 168 // Wake up the next waiter for the pc, if any. 169 foreach (PersistenceContext context in pcEnlist.enlistedContexts) 170 { 171 context.ScheduleNextTransactionWaiter(); 172 } 173 } 174 lock (PersistenceContext.Enlistments) 175 { 176 PersistenceContext.Enlistments.Remove(pcEnlist.transaction.GetHashCode()); 177 } 178 // This needs to be outside the lock because SysTx might do other stuff on the thread. 179 pcEnlist.enlistment.Done(); 180 } 181 DoRollback(object state)182 internal static void DoRollback(object state) 183 { 184 PersistenceContextEnlistment pcEnlist = state as PersistenceContextEnlistment; 185 Fx.Assert(null != pcEnlist, "PersistenceContextEnlistment.DoRollback called with an object that is not a PersistenceContext."); 186 187 lock (pcEnlist.ThisLock) 188 { 189 pcEnlist.tooLateForMoreUndo = true; 190 191 foreach (PersistenceContext context in pcEnlist.enlistedContexts) 192 { 193 context.Abort(); 194 context.ScheduleNextTransactionWaiter(); 195 } 196 } 197 lock (PersistenceContext.Enlistments) 198 { 199 PersistenceContext.Enlistments.Remove(pcEnlist.transaction.GetHashCode()); 200 } 201 // This needs to be outside the lock because SysTx might do other stuff on the thread. 202 pcEnlist.enlistment.Done(); 203 } 204 DoIndoubt(object state)205 internal static void DoIndoubt(object state) 206 { 207 PersistenceContextEnlistment pcEnlist = state as PersistenceContextEnlistment; 208 Fx.Assert(null != pcEnlist, "PersistenceContextEnlistment.DoIndoubt called with an object that is not a PersistenceContext."); 209 210 lock (pcEnlist.ThisLock) 211 { 212 pcEnlist.tooLateForMoreUndo = true; 213 214 foreach (PersistenceContext context in pcEnlist.enlistedContexts) 215 { 216 context.Abort(); 217 context.ScheduleNextTransactionWaiter(); 218 } 219 } 220 lock (PersistenceContext.Enlistments) 221 { 222 PersistenceContext.Enlistments.Remove(pcEnlist.transaction.GetHashCode()); 223 } 224 // This needs to be outside the lock because SysTx might do other stuff on the thread. 225 pcEnlist.enlistment.Done(); 226 } 227 } 228 } 229