1 //----------------------------------------------------------------------------- 2 // Copyright (c) Microsoft Corporation. All rights reserved. 3 //----------------------------------------------------------------------------- 4 5 namespace System.ServiceModel.Activities.Dispatcher 6 { 7 using System.Activities; 8 using System.Activities.DurableInstancing; 9 using System.Activities.Hosting; 10 using System.Collections; 11 using System.Collections.ObjectModel; 12 using System.Collections.Generic; 13 using System.Diagnostics.CodeAnalysis; 14 using System.Runtime; 15 using System.Runtime.DurableInstancing; 16 using System.ServiceModel.Channels; 17 using System.ServiceModel.Activities.Description; 18 using System.Transactions; 19 using System.Xml.Linq; 20 21 sealed class PersistenceContext : CommunicationObject 22 { 23 // Dictionary keyed by Transaction HashCode. The value is the enlistment for that transaction. 24 internal static Dictionary<int, PersistenceContextEnlistment> Enlistments = new Dictionary<int, PersistenceContextEnlistment>(); 25 26 readonly PersistenceProviderDirectory directory; 27 28 readonly InstanceStore store; 29 readonly InstanceHandle handle; 30 31 readonly HashSet<InstanceKey> keysToAssociate; 32 readonly HashSet<InstanceKey> keysToDisassociate; 33 34 static TimeSpan defaultOpenTimeout = TimeSpan.FromSeconds(90); 35 static TimeSpan defaultCloseTimeout = TimeSpan.FromSeconds(90); 36 37 38 bool operationInProgress; 39 40 WorkflowServiceInstance workflowInstance; 41 42 // The hash code of the transaction that has this particular context "locked". 43 // If the value of this property is 0, then no transaction is working on this context 44 // and it is available to be "locked" by a transaction. Locking for transactions is done 45 // with QueueForTransactionLock. This method returns a TransactionWaitAsyncResult. If the 46 // lock was obtained by the call, the resulting AsyncResult will be marked as "Completed" 47 // upon return from QueueForTransactionLock. If not, the caller should wait on the 48 // AsyncResult.AsyncWaitHandle before proceeding to update any of the fields of the context. 49 int lockingTransaction; 50 //We are keeping a reference to both the transaction object and the hash code to avoid calling the GetHashCode multiple times 51 Transaction lockingTransactionObject; 52 53 // This is the queue of TransactionWaitAsyncResult objects that are waiting for the 54 // context to become "unlocked" with respect to a transaction. DequeueTransactionWaiter 55 // removes the first element from this queue and returns it. If there is no element on the 56 // queue, null is returned indicating that there was no outstanding waiter. 57 Queue<TransactionWaitAsyncResult> transactionWaiterQueue; 58 59 // Used by PPD when there is no store. PersistenceContext(PersistenceProviderDirectory directory, Guid instanceId, InstanceKey key, IEnumerable<InstanceKey> associatedKeys)60 internal PersistenceContext(PersistenceProviderDirectory directory, 61 Guid instanceId, InstanceKey key, IEnumerable<InstanceKey> associatedKeys) 62 { 63 Fx.Assert(directory != null, "Directory is null in PersistenceContext."); 64 Fx.Assert(instanceId != Guid.Empty, "Cannot provide an empty instance ID."); 65 66 this.directory = directory; 67 68 InstanceId = instanceId; 69 70 AssociatedKeys = associatedKeys != null ? new HashSet<InstanceKey>(associatedKeys) : 71 new HashSet<InstanceKey>(); 72 if (key != null && !AssociatedKeys.Contains(key)) 73 { 74 AssociatedKeys.Add(key); 75 } 76 77 this.keysToAssociate = new HashSet<InstanceKey>(AssociatedKeys); 78 this.keysToDisassociate = new HashSet<InstanceKey>(); 79 80 this.lockingTransaction = 0; 81 this.Detaching = false; 82 this.transactionWaiterQueue = new Queue<TransactionWaitAsyncResult>(); 83 } 84 85 // Used by PPD when there is a store. PersistenceContext(PersistenceProviderDirectory directory, InstanceStore store, InstanceHandle handle, Guid instanceId, IEnumerable<InstanceKey> associatedKeys, bool newInstance, bool locked, InstanceView view, WorkflowIdentityKey updatedIdentity)86 internal PersistenceContext(PersistenceProviderDirectory directory, InstanceStore store, 87 InstanceHandle handle, Guid instanceId, IEnumerable<InstanceKey> associatedKeys, 88 bool newInstance, bool locked, InstanceView view, WorkflowIdentityKey updatedIdentity) 89 : this(directory, instanceId, null, associatedKeys) 90 { 91 Fx.Assert(store != null, "Null store passed to PersistenceContext."); 92 Fx.Assert(handle != null, "Null handle passed to PersistenceContext."); 93 94 this.store = store; 95 this.handle = handle; 96 97 IsInitialized = !newInstance; 98 IsLocked = locked; 99 100 if (view != null) 101 { 102 ReadSuspendedInfo(view); 103 } 104 105 // If we were loaded or we locked the instance, the keys will have been sync'd. 106 if (IsInitialized || IsLocked) 107 { 108 RationalizeSavedKeys(false); 109 } 110 111 if (IsInitialized) 112 { 113 Fx.Assert(view != null, "View must be specified on an initialized instance."); 114 WorkflowIdentity definitionIdentity; 115 116 if (!TryGetValue<WorkflowIdentity>(view.InstanceMetadata, Workflow45Namespace.DefinitionIdentity, out definitionIdentity)) 117 { 118 definitionIdentity = null; 119 } 120 121 this.workflowInstance = this.directory.InitializeInstance(InstanceId, this, definitionIdentity, updatedIdentity, view.InstanceData, null); 122 } 123 } 124 125 public Guid InstanceId { get; private set; } 126 127 public bool IsLocked { get; private set; } 128 public bool IsInitialized { get; private set; } 129 public bool IsCompleted { get; private set; } 130 public bool IsVisible { get; internal set; } 131 132 public bool IsSuspended { get; set; } 133 public string SuspendedReason { get; set; } 134 135 // Set to true when we detach from the PPD under a transaction. When the transaction completes, 136 // either commit or abort, we will finish the removal from the PPD. 137 internal bool Detaching 138 { 139 get; set; 140 } 141 142 public bool CanPersist 143 { 144 get 145 { 146 return (this.store != null); 147 } 148 } 149 150 public bool IsHandleValid 151 { 152 get 153 { 154 return this.handle == null || this.handle.IsValid; 155 } 156 } 157 158 internal Transaction LockingTransaction 159 { 160 get 161 { 162 lock (ThisLock) 163 { 164 ThrowIfDisposedOrNotOpen(); 165 return this.lockingTransactionObject; 166 } 167 } 168 } 169 170 // Used only by PPD. 171 internal bool IsPermanentlyRemoved { get; set; } 172 173 // If there's a directory, only it can write to this collection as long as the isntance is locked. Otherwise, 174 // only this class can. 175 internal HashSet<InstanceKey> AssociatedKeys { get; private set; } 176 internal ReadOnlyCollection<BookmarkInfo> Bookmarks { get; set; } 177 178 protected override TimeSpan DefaultCloseTimeout { get { return defaultCloseTimeout; } } 179 protected override TimeSpan DefaultOpenTimeout { get { return defaultOpenTimeout; } } 180 181 // Remove key associations. These are never immediately propagated to the store / cache. Succeeds 182 // if the keys don't exist or are associated with a different instance (in which case they are 183 // not disassociated). DisassociateKeys(ICollection<InstanceKey> expiredKeys)184 public void DisassociateKeys(ICollection<InstanceKey> expiredKeys) 185 { 186 ThrowIfDisposedOrNotOpen(); 187 Fx.Assert(expiredKeys != null, "'expiredKeys' parameter to DisassociateKeys cannot be null."); 188 189 try 190 { 191 StartOperation(); 192 ThrowIfCompleted(); 193 ThrowIfNotVisible(); 194 Fx.Assert(!IsInitialized || IsLocked, "Should not be visible if initialized and not locked."); 195 196 foreach (InstanceKey key in expiredKeys) 197 { 198 if (AssociatedKeys.Contains(key) && !this.keysToDisassociate.Contains(key)) 199 { 200 this.keysToDisassociate.Add(key); 201 this.keysToAssociate.Remove(key); 202 } 203 else 204 { 205 Fx.Assert(!this.keysToAssociate.Contains(key), "Cannot be planning to associate this key."); 206 } 207 } 208 } 209 finally 210 { 211 FinishOperation(); 212 } 213 } 214 BeginSave( IDictionary<XName, InstanceValue> instance, SaveStatus saveStatus, TimeSpan timeout, AsyncCallback callback, object state)215 public IAsyncResult BeginSave( 216 IDictionary<XName, InstanceValue> instance, 217 SaveStatus saveStatus, 218 TimeSpan timeout, 219 AsyncCallback callback, 220 object state) 221 { 222 ThrowIfDisposedOrNotOpen(); 223 Fx.AssertAndThrow(instance != null, "'instance' parameter to BeginSave cannot be null."); 224 225 return new SaveAsyncResult(this, instance, saveStatus, timeout, callback, state); 226 } 227 EndSave(IAsyncResult result)228 public void EndSave(IAsyncResult result) 229 { 230 SaveAsyncResult.End(result); 231 } 232 BeginRelease(TimeSpan timeout, AsyncCallback callback, object state)233 public IAsyncResult BeginRelease(TimeSpan timeout, AsyncCallback callback, object state) 234 { 235 ThrowIfDisposedOrNotOpen(); 236 237 return new ReleaseAsyncResult(this, timeout, callback, state); 238 } 239 EndRelease(IAsyncResult result)240 public void EndRelease(IAsyncResult result) 241 { 242 ReleaseAsyncResult.End(result); 243 } 244 BeginAssociateKeys( ICollection<InstanceKey> associatedKeys, TimeSpan timeout, AsyncCallback callback, object state)245 public IAsyncResult BeginAssociateKeys( 246 ICollection<InstanceKey> associatedKeys, TimeSpan timeout, AsyncCallback callback, object state) 247 { 248 return BeginAssociateKeysHelper(associatedKeys, timeout, true, callback, state); 249 } 250 BeginAssociateInfrastructureKeys( ICollection<InstanceKey> associatedKeys, TimeSpan timeout, AsyncCallback callback, object state)251 internal IAsyncResult BeginAssociateInfrastructureKeys( 252 ICollection<InstanceKey> associatedKeys, TimeSpan timeout, AsyncCallback callback, object state) 253 { 254 return BeginAssociateKeysHelper(associatedKeys, timeout, true, callback, state); 255 } 256 BeginAssociateKeysHelper(ICollection<InstanceKey> associatedKeys, TimeSpan timeout, bool applicationKeys, AsyncCallback callback, object state)257 IAsyncResult BeginAssociateKeysHelper(ICollection<InstanceKey> associatedKeys, 258 TimeSpan timeout, bool applicationKeys, AsyncCallback callback, object state) 259 { 260 ThrowIfDisposedOrNotOpen(); 261 Fx.Assert(associatedKeys != null, "'associatedKeys' parameter to BeginAssociateKeys cannot be null."); 262 263 return new AssociateKeysAsyncResult(this, associatedKeys, timeout, applicationKeys, callback, state); 264 } 265 EndAssociateKeys(IAsyncResult result)266 public void EndAssociateKeys(IAsyncResult result) 267 { 268 AssociateKeysAsyncResult.End(result); 269 } 270 EndAssociateInfrastructureKeys(IAsyncResult result)271 internal void EndAssociateInfrastructureKeys(IAsyncResult result) 272 { 273 AssociateKeysAsyncResult.End(result); 274 } 275 276 // UpdateSuspendMetadata and Unlock instance BeginUpdateSuspendMetadata(Exception reason, TimeSpan timeout, AsyncCallback callback, object state)277 public IAsyncResult BeginUpdateSuspendMetadata(Exception reason, TimeSpan timeout, AsyncCallback callback, object state) 278 { 279 ThrowIfDisposedOrNotOpen(); 280 281 return new UpdateSuspendMetadataAsyncResult(this, reason, timeout, callback, state); 282 } 283 EndUpdateSuspendMetadata(IAsyncResult result)284 public void EndUpdateSuspendMetadata(IAsyncResult result) 285 { 286 UpdateSuspendMetadataAsyncResult.End(result); 287 } 288 GetInstance(WorkflowGetInstanceContext parameters)289 public WorkflowServiceInstance GetInstance(WorkflowGetInstanceContext parameters) 290 { 291 if (this.workflowInstance == null && parameters != null) 292 { 293 lock (ThisLock) 294 { 295 ThrowIfDisposedOrNotOpen(); 296 297 if (this.workflowInstance == null) 298 { 299 try 300 { 301 WorkflowServiceInstance result; 302 if (parameters.WorkflowHostingEndpoint != null) 303 { 304 WorkflowHostingResponseContext responseContext = new WorkflowHostingResponseContext(); 305 WorkflowCreationContext creationContext = parameters.WorkflowHostingEndpoint.OnGetCreationContext(parameters.Inputs, parameters.OperationContext, InstanceId, responseContext); 306 if (creationContext == null) 307 { 308 throw FxTrace.Exception.AsError(WorkflowHostingEndpoint.CreateDispatchFaultException()); 309 } 310 result = this.directory.InitializeInstance(InstanceId, this, null, creationContext); 311 312 // Return args 313 parameters.WorkflowCreationContext = creationContext; 314 parameters.WorkflowHostingResponseContext = responseContext; 315 } 316 else 317 { 318 result = this.directory.InitializeInstance(InstanceId, this, null, null); 319 } 320 this.workflowInstance = result; 321 } 322 finally 323 { 324 if (this.workflowInstance == null) 325 { 326 Fault(); 327 } 328 } 329 } 330 } 331 } 332 return this.workflowInstance; 333 } 334 OnAbort()335 protected override void OnAbort() 336 { 337 if (this.handle != null) 338 { 339 this.handle.Free(); 340 } 341 } 342 OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state)343 protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state) 344 { 345 return new CloseAsyncResult(this, callback, state); 346 } 347 OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state)348 protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state) 349 { 350 return new CompletedAsyncResult(callback, state); 351 } 352 OnClose(TimeSpan timeout)353 protected override void OnClose(TimeSpan timeout) 354 { 355 try 356 { 357 StartOperation(); 358 359 if (this.store != null) 360 { 361 this.handle.Free(); 362 } 363 } 364 finally 365 { 366 FinishOperation(); 367 } 368 } 369 OnEndClose(IAsyncResult result)370 protected override void OnEndClose(IAsyncResult result) 371 { 372 CloseAsyncResult.End(result); 373 } 374 OnEndOpen(IAsyncResult result)375 protected override void OnEndOpen(IAsyncResult result) 376 { 377 CompletedAsyncResult.End(result); 378 } 379 380 // PersistenceProviderDirectory calls Open in an async path. Do not introduce blocking work to this method 381 // without changing PersistenceProviderDirectory to call BeginOpen instead. OnOpen(TimeSpan timeout)382 protected override void OnOpen(TimeSpan timeout) 383 { 384 } 385 OnClosing()386 protected override void OnClosing() 387 { 388 base.OnClosing(); 389 this.directory.RemoveInstance(this, true); 390 } 391 OnFaulted()392 protected override void OnFaulted() 393 { 394 base.OnFaulted(); 395 this.directory.RemoveInstance(this, true); 396 } 397 RationalizeSavedKeys(bool updateDirectory)398 void RationalizeSavedKeys(bool updateDirectory) 399 { 400 if (updateDirectory) 401 { 402 this.directory.RemoveAssociations(this, this.keysToDisassociate); 403 } 404 else 405 { 406 foreach (InstanceKey key in this.keysToDisassociate) 407 { 408 AssociatedKeys.Remove(key); 409 } 410 } 411 412 this.keysToAssociate.Clear(); 413 this.keysToDisassociate.Clear(); 414 } 415 ReadSuspendedInfo(InstanceView view)416 void ReadSuspendedInfo(InstanceView view) 417 { 418 string suspendedReason = null; 419 if (TryGetValue<string>(view.InstanceMetadata, WorkflowServiceNamespace.SuspendReason, out suspendedReason)) 420 { 421 IsSuspended = true; 422 SuspendedReason = suspendedReason; 423 } 424 else 425 { 426 IsSuspended = false; 427 SuspendedReason = null; 428 } 429 } 430 StartOperation()431 void StartOperation() 432 { 433 Fx.AssertAndThrow(!this.operationInProgress, "PersistenceContext doesn't support multiple operations."); 434 this.operationInProgress = true; 435 } 436 FinishOperation()437 void FinishOperation() 438 { 439 this.operationInProgress = false; 440 } 441 OnFinishOperationHelper(Exception exception, bool ownsThrottle)442 void OnFinishOperationHelper(Exception exception, bool ownsThrottle) 443 { 444 try 445 { 446 if (exception is OperationCanceledException) 447 { 448 throw FxTrace.Exception.AsError(new CommunicationObjectAbortedException(SR.HandleFreedInDirectory, exception)); 449 } 450 else if (exception is TimeoutException) 451 { 452 Fault(); 453 } 454 } 455 finally 456 { 457 if (ownsThrottle) 458 { 459 this.directory.ReleaseThrottle(); 460 } 461 FinishOperation(); 462 } 463 } 464 ThrowIfCompleted()465 void ThrowIfCompleted() 466 { 467 Fx.AssertAndThrow(!IsCompleted, "PersistenceContext operation invalid: instance already completed."); 468 } 469 ThrowIfNotVisible()470 void ThrowIfNotVisible() 471 { 472 // Be charitable to racy aborts. 473 if (!IsVisible) 474 { 475 lock (ThisLock) 476 { 477 Fx.AssertAndThrow(State != CommunicationState.Opened, 478 "PersistenceContext operation invalid: instance must be visible."); 479 } 480 } 481 } 482 TryGetValue(IDictionary<XName, InstanceValue> data, XName key, out T value)483 internal static bool TryGetValue<T>(IDictionary<XName, InstanceValue> data, XName key, out T value) 484 { 485 InstanceValue instanceValue; 486 value = default(T); 487 if (data.TryGetValue(key, out instanceValue) && !instanceValue.IsDeletedValue) 488 { 489 if (instanceValue.Value is T) 490 { 491 value = (T)instanceValue.Value; 492 return true; 493 } 494 else if (instanceValue.Value == null && !(value is ValueType)) 495 { 496 // need to check for null assignments to value types 497 return true; 498 } 499 else 500 { 501 if (instanceValue.Value == null) 502 { 503 throw FxTrace.Exception.AsError(new InstancePersistenceException(SRCore.NullAssignedToValueType(typeof(T)))); 504 } 505 else 506 { 507 throw FxTrace.Exception.AsError(new InstancePersistenceException(SRCore.IncorrectValueType(typeof(T), instanceValue.Value.GetType()))); 508 } 509 } 510 } 511 else 512 { 513 return false; 514 } 515 } 516 BeginEnlist(TimeSpan timeout, AsyncCallback callback, object state)517 internal TransactionWaitAsyncResult BeginEnlist(TimeSpan timeout, AsyncCallback callback, object state) 518 { 519 ThrowIfDisposedOrNotOpen(); 520 // The transaction to enlist on is in Transaction.Current. The actual enlistment, if needed, will be made in 521 // TransactionWaitAsyncResult when it is notified that it has the transaction lock. 522 return new TransactionWaitAsyncResult(Transaction.Current, this, timeout, callback, state); 523 } 524 525 [SuppressMessage(FxCop.Category.ReliabilityBasic, FxCop.Rule.CommunicationObjectThrowIf, 526 Justification = "We are intentionally re-validating the state of the instance after having acquired the transaction lock")] EndEnlist(IAsyncResult result)527 internal void EndEnlist(IAsyncResult result) 528 { 529 TransactionWaitAsyncResult.End(result); 530 // The PersistenceContext may have been aborted while we were waiting for the transaction lock. 531 ThrowIfDisposedOrNotOpen(); 532 } 533 534 535 // Returns true if the call was able to obtain the transaction lock; false if we had 536 // to queue the request for the lock. QueueForTransactionLock(Transaction requestingTransaction, TransactionWaitAsyncResult txWaitAsyncResult)537 internal bool QueueForTransactionLock(Transaction requestingTransaction, TransactionWaitAsyncResult txWaitAsyncResult) 538 { 539 lock (ThisLock) 540 { 541 // If the transaction "lock" is not already held, give it to this requester. 542 if (0 == this.lockingTransaction) 543 { 544 // It's possible that this particular request is not transacted. 545 if (null != requestingTransaction) 546 { 547 this.lockingTransaction = requestingTransaction.GetHashCode(); 548 this.lockingTransactionObject = requestingTransaction.Clone(); 549 } 550 // No queuing because we weren't already locked by a transaction. 551 return true; 552 } 553 else if ((null != requestingTransaction) && (this.lockingTransaction == requestingTransaction.GetHashCode())) 554 { 555 // Same transaction as the locking transaction - no queuing. 556 return true; 557 } 558 else 559 { 560 // Some other transaction has the lock, so add the AsyncResult to the queue. 561 this.transactionWaiterQueue.Enqueue(txWaitAsyncResult); 562 return false; 563 } 564 } 565 } 566 567 // Dequeue and schedule the top element on queue of waiting TransactionWaitAsyncResult objects. 568 // Before returning this also makes the transaction represented by the dequeued TransactionWaitAsyncResult 569 // the owner of the transaction "lock" for this context. ScheduleNextTransactionWaiter()570 internal void ScheduleNextTransactionWaiter() 571 { 572 TransactionWaitAsyncResult dequeuedWaiter = null; 573 bool detachThis = false; 574 575 lock (ThisLock) 576 { 577 // Only try Dequeue if we have entries on the queue. 578 bool atLeastOneSuccessfullyCompleted = false; 579 if (0 < this.transactionWaiterQueue.Count) 580 { 581 while ((0 < this.transactionWaiterQueue.Count) && !atLeastOneSuccessfullyCompleted) 582 { 583 dequeuedWaiter = this.transactionWaiterQueue.Dequeue(); 584 585 // It's possible that the waiter didn't have a transaction. 586 // If that is the case, we don't have a transaction to "lock" the context. 587 if (null != dequeuedWaiter.Transaction) 588 { 589 this.lockingTransactionObject = dequeuedWaiter.Transaction; 590 this.lockingTransaction = lockingTransactionObject.GetHashCode(); 591 } 592 else 593 { 594 this.lockingTransaction = 0; 595 this.lockingTransactionObject = null; 596 } 597 598 atLeastOneSuccessfullyCompleted = dequeuedWaiter.Complete() || atLeastOneSuccessfullyCompleted; 599 600 if (this.Detaching) 601 { 602 detachThis = true; 603 this.Detaching = false; 604 } 605 606 // If we are doing a permanent detach, we must have received an OnClosing or 607 // OnFaulted while the PersistenceContext was locked for a transaction. In that 608 // case, we want to wake up ALL waiters. 609 if (this.IsPermanentlyRemoved) 610 { 611 this.lockingTransaction = 0; 612 this.lockingTransactionObject = null; 613 while (0 < this.transactionWaiterQueue.Count) 614 { 615 dequeuedWaiter = this.transactionWaiterQueue.Dequeue(); 616 atLeastOneSuccessfullyCompleted = dequeuedWaiter.Complete() || atLeastOneSuccessfullyCompleted; 617 } 618 } 619 620 // Now we need to look for any adjacent waiters in the queue that are 621 // waiting for the same transaction. If we still have entries on the queue, 622 // we must have a waiterToComplete. Note that if we were doing a permanent detach, 623 // there won't be any waiters left in the queue at this point. 624 while (0 < this.transactionWaiterQueue.Count) 625 { 626 TransactionWaitAsyncResult nextWaiter = this.transactionWaiterQueue.Peek(); 627 if (0 == this.lockingTransaction) 628 { 629 // We dequeue this waiter because we shouldn't block transactional waiters 630 // behind non-transactional waiters because there is nothing to wake up the 631 // transactional waiters in that case. Also set this.LockingTransaction 632 // to that of the next waiter. 633 if (null != nextWaiter.Transaction) 634 { 635 this.lockingTransactionObject = nextWaiter.Transaction; 636 this.lockingTransaction = this.lockingTransactionObject.GetHashCode(); 637 } 638 } 639 else if (null != nextWaiter.Transaction) 640 { 641 // Stop looking if the new lockingTransaction is different than 642 // the nextWaiter's transaction. 643 if (this.lockingTransaction != nextWaiter.Transaction.GetHashCode()) 644 { 645 break; // out of the inner-while 646 } 647 } 648 else 649 { 650 // The nextWaiter is non-transational, so it doesn't match the current 651 // lock holder, so we are done. 652 break; // out of the inner-while 653 } 654 655 dequeuedWaiter = this.transactionWaiterQueue.Dequeue(); 656 atLeastOneSuccessfullyCompleted = dequeuedWaiter.Complete() || atLeastOneSuccessfullyCompleted; 657 } 658 } 659 } 660 if (!atLeastOneSuccessfullyCompleted) 661 { 662 // There are no more waiters, so the context is no longer "locked" by a transaction. 663 this.lockingTransaction = 0; 664 this.lockingTransactionObject = null; 665 } 666 } 667 668 // If we are detaching and it is NOT permanently removed, finish the detach by calling RemoveInstance non-transactionally. 669 // It will be marked as permanently removed in OnClosing and OnFaulted and it will have already been removed, so we don't 670 // want to try to remove it again. 671 if (detachThis) 672 { 673 this.directory.RemoveInstance(this, false); 674 } 675 } 676 ScheduleDetach()677 bool ScheduleDetach() 678 { 679 lock (ThisLock) 680 { 681 if (this.lockingTransaction != 0) 682 { 683 Detaching = true; 684 return true; 685 } 686 } 687 return false; 688 } 689 PopulateActivationMetadata(SaveWorkflowCommand saveCommand)690 void PopulateActivationMetadata(SaveWorkflowCommand saveCommand) 691 { 692 bool saveIdentity; 693 if (!IsInitialized) 694 { 695 Fx.Assert(this.directory.InstanceMetadataChanges != null, "We should always be non-null here."); 696 foreach (KeyValuePair<XName, InstanceValue> pair in this.directory.InstanceMetadataChanges) 697 { 698 saveCommand.InstanceMetadataChanges.Add(pair.Key, pair.Value); 699 } 700 saveIdentity = this.workflowInstance.DefinitionIdentity != null; 701 } 702 else 703 { 704 saveIdentity = this.workflowInstance.HasBeenUpdated; 705 } 706 707 if (saveIdentity) 708 { 709 if (this.workflowInstance.DefinitionIdentity != null) 710 { 711 saveCommand.InstanceMetadataChanges.Add(Workflow45Namespace.DefinitionIdentity, new InstanceValue(this.workflowInstance.DefinitionIdentity, InstanceValueOptions.None)); 712 } 713 else 714 { 715 saveCommand.InstanceMetadataChanges.Add(Workflow45Namespace.DefinitionIdentity, InstanceValue.DeletedValue); 716 } 717 } 718 } 719 720 class CloseAsyncResult : AsyncResult 721 { 722 PersistenceContext persistenceContext; 723 CloseAsyncResult(PersistenceContext persistenceContext, AsyncCallback callback, object state)724 public CloseAsyncResult(PersistenceContext persistenceContext, AsyncCallback callback, object state) 725 : base(callback, state) 726 { 727 this.persistenceContext = persistenceContext; 728 OnCompleting = new Action<AsyncResult, Exception>(OnFinishOperation); 729 730 bool success = false; 731 bool completeSelf = false; 732 try 733 { 734 this.persistenceContext.StartOperation(); 735 736 if (this.persistenceContext.store != null) 737 { 738 Fx.Assert(this.persistenceContext.handle != null, "WorkflowInstance failed to call SetHandle - from OnBeginClose."); 739 this.persistenceContext.handle.Free(); 740 } 741 completeSelf = true; 742 success = true; 743 } 744 finally 745 { 746 if (!success) 747 { 748 this.persistenceContext.FinishOperation(); 749 } 750 } 751 752 if (completeSelf) 753 { 754 base.Complete(true); 755 } 756 } 757 End(IAsyncResult result)758 public static void End(IAsyncResult result) 759 { 760 AsyncResult.End<CloseAsyncResult>(result); 761 } 762 OnFinishOperation(AsyncResult result, Exception exception)763 void OnFinishOperation(AsyncResult result, Exception exception) 764 { 765 this.persistenceContext.FinishOperation(); 766 } 767 } 768 769 class SaveAsyncResult : TransactedAsyncResult 770 { 771 static readonly AsyncCompletion handleEndExecute = new AsyncCompletion(HandleEndExecute); 772 static readonly AsyncCompletion handleEndEnlist = new AsyncCompletion(HandleEndEnlist); 773 774 readonly PersistenceContext persistenceContext; 775 readonly SaveStatus saveStatus; 776 readonly TimeoutHelper timeoutHelper; 777 readonly DependentTransaction transaction; 778 SaveAsyncResult(PersistenceContext persistenceContext, IDictionary<XName, InstanceValue> instance, SaveStatus saveStatus, TimeSpan timeout, AsyncCallback callback, object state)779 public SaveAsyncResult(PersistenceContext persistenceContext, IDictionary<XName, InstanceValue> instance, SaveStatus saveStatus, TimeSpan timeout, 780 AsyncCallback callback, object state) 781 : base(callback, state) 782 { 783 this.persistenceContext = persistenceContext; 784 OnCompleting = new Action<AsyncResult, Exception>(OnFinishOperation); 785 786 this.saveStatus = saveStatus; 787 788 bool success = false; 789 try 790 { 791 this.persistenceContext.StartOperation(); 792 793 this.persistenceContext.ThrowIfCompleted(); 794 this.persistenceContext.ThrowIfNotVisible(); 795 Fx.Assert(!this.persistenceContext.IsInitialized || this.persistenceContext.IsLocked, 796 "Should not be visible if initialized and not locked."); 797 798 this.timeoutHelper = new TimeoutHelper(timeout); 799 800 Transaction currentTransaction = Transaction.Current; 801 if (currentTransaction != null) 802 { 803 this.transaction = currentTransaction.DependentClone(DependentCloneOption.BlockCommitUntilComplete); 804 } 805 806 if (this.persistenceContext.store != null) 807 { 808 SaveWorkflowCommand saveCommand = new SaveWorkflowCommand(); 809 foreach (KeyValuePair<XName, InstanceValue> value in instance) 810 { 811 saveCommand.InstanceData.Add(value); 812 } 813 this.persistenceContext.PopulateActivationMetadata(saveCommand); 814 if (this.persistenceContext.IsSuspended) 815 { 816 saveCommand.InstanceMetadataChanges.Add(WorkflowServiceNamespace.SuspendReason, new InstanceValue(this.persistenceContext.SuspendedReason)); 817 } 818 else 819 { 820 saveCommand.InstanceMetadataChanges.Add(WorkflowServiceNamespace.SuspendReason, InstanceValue.DeletedValue); 821 saveCommand.InstanceMetadataChanges.Add(WorkflowServiceNamespace.SuspendException, InstanceValue.DeletedValue); 822 } 823 foreach (InstanceKey key in this.persistenceContext.keysToAssociate) 824 { 825 saveCommand.InstanceKeysToAssociate.Add(key.Value, key.Metadata); 826 } 827 foreach (InstanceKey key in this.persistenceContext.keysToDisassociate) 828 { 829 // We are going to Complete and Disassociate with the same Save command. 830 saveCommand.InstanceKeysToComplete.Add(key.Value); 831 saveCommand.InstanceKeysToFree.Add(key.Value); 832 } 833 834 if (this.saveStatus == SaveStatus.Completed) 835 { 836 saveCommand.CompleteInstance = true; 837 saveCommand.UnlockInstance = true; 838 } 839 else 840 { 841 saveCommand.UnlockInstance = this.saveStatus == SaveStatus.Unlocked; 842 } 843 844 IAsyncResult result = this.persistenceContext.store.BeginExecute( 845 this.persistenceContext.handle, 846 saveCommand, 847 this.timeoutHelper.RemainingTime(), 848 PrepareAsyncCompletion(SaveAsyncResult.handleEndExecute), 849 this); 850 if (SyncContinue(result)) 851 { 852 Complete(true); 853 } 854 } 855 else 856 { 857 if (this.saveStatus == SaveStatus.Completed) 858 { 859 this.persistenceContext.IsCompleted = true; 860 this.persistenceContext.IsLocked = false; 861 } 862 else 863 { 864 this.persistenceContext.IsLocked = this.saveStatus != SaveStatus.Unlocked; 865 } 866 if (AfterSave()) 867 { 868 Complete(true); 869 } 870 } 871 success = true; 872 } 873 catch (OperationCanceledException exception) 874 { 875 throw FxTrace.Exception.AsError(new CommunicationObjectAbortedException(SR.HandleFreedInDirectory, exception)); 876 } 877 catch (TimeoutException) 878 { 879 this.persistenceContext.Fault(); 880 throw; 881 } 882 finally 883 { 884 if (!success) 885 { 886 try 887 { 888 if (this.transaction != null) 889 { 890 this.transaction.Complete(); 891 } 892 } 893 finally 894 { 895 this.persistenceContext.FinishOperation(); 896 } 897 } 898 } 899 } 900 End(IAsyncResult result)901 public static void End(IAsyncResult result) 902 { 903 AsyncResult.End<SaveAsyncResult>(result); 904 } 905 HandleEndExecute(IAsyncResult result)906 static bool HandleEndExecute(IAsyncResult result) 907 { 908 SaveAsyncResult thisPtr = (SaveAsyncResult)result.AsyncState; 909 thisPtr.persistenceContext.store.EndExecute(result); 910 thisPtr.persistenceContext.IsCompleted = thisPtr.saveStatus == SaveStatus.Completed; 911 thisPtr.persistenceContext.IsLocked = thisPtr.saveStatus == SaveStatus.Locked; 912 return thisPtr.AfterSave(); 913 } 914 AfterSave()915 bool AfterSave() 916 { 917 this.persistenceContext.IsInitialized = true; 918 919 if (this.saveStatus != SaveStatus.Locked) 920 { 921 IAsyncResult result; 922 using (PrepareTransactionalCall(this.transaction)) 923 { 924 result = this.persistenceContext.BeginEnlist(this.timeoutHelper.RemainingTime(), PrepareAsyncCompletion(SaveAsyncResult.handleEndEnlist), this); 925 } 926 return SyncContinue(result); 927 } 928 929 return AfterEnlist(); 930 } 931 AfterEnlist()932 bool AfterEnlist() 933 { 934 this.persistenceContext.RationalizeSavedKeys(this.saveStatus == SaveStatus.Locked); 935 return true; 936 } 937 HandleEndEnlist(IAsyncResult result)938 static bool HandleEndEnlist(IAsyncResult result) 939 { 940 SaveAsyncResult thisPtr = (SaveAsyncResult)result.AsyncState; 941 thisPtr.persistenceContext.EndEnlist(result); 942 943 if (!thisPtr.persistenceContext.ScheduleDetach()) 944 { 945 thisPtr.persistenceContext.directory.RemoveInstance(thisPtr.persistenceContext); 946 } 947 return thisPtr.AfterEnlist(); 948 } 949 OnFinishOperation(AsyncResult result, Exception exception)950 void OnFinishOperation(AsyncResult result, Exception exception) 951 { 952 try 953 { 954 this.persistenceContext.OnFinishOperationHelper(exception, false); 955 } 956 finally 957 { 958 if (this.transaction != null) 959 { 960 this.transaction.Complete(); 961 } 962 } 963 } 964 } 965 966 class ReleaseAsyncResult : TransactedAsyncResult 967 { 968 static readonly AsyncCompletion handleEndExecute = new AsyncCompletion(HandleEndExecute); 969 static readonly AsyncCompletion handleEndEnlist = new AsyncCompletion(HandleEndEnlist); 970 971 readonly PersistenceContext persistenceContext; 972 readonly TimeoutHelper timeoutHelper; 973 readonly DependentTransaction transaction; 974 ReleaseAsyncResult(PersistenceContext persistenceContext, TimeSpan timeout, AsyncCallback callback, object state)975 public ReleaseAsyncResult(PersistenceContext persistenceContext, TimeSpan timeout, AsyncCallback callback, object state) 976 : base(callback, state) 977 { 978 this.persistenceContext = persistenceContext; 979 OnCompleting = new Action<AsyncResult, Exception>(OnFinishOperation); 980 981 bool success = false; 982 try 983 { 984 this.persistenceContext.StartOperation(); 985 986 this.timeoutHelper = new TimeoutHelper(timeout); 987 988 Transaction currentTransaction = Transaction.Current; 989 if (currentTransaction != null) 990 { 991 this.transaction = currentTransaction.DependentClone(DependentCloneOption.BlockCommitUntilComplete); 992 } 993 994 if (this.persistenceContext.IsVisible) 995 { 996 if (this.persistenceContext.store != null && this.persistenceContext.IsLocked) 997 { 998 SaveWorkflowCommand saveCommand = new SaveWorkflowCommand() { UnlockInstance = true }; 999 this.persistenceContext.PopulateActivationMetadata(saveCommand); 1000 IAsyncResult result = this.persistenceContext.store.BeginExecute( 1001 this.persistenceContext.handle, 1002 saveCommand, 1003 this.timeoutHelper.RemainingTime(), 1004 PrepareAsyncCompletion(ReleaseAsyncResult.handleEndExecute), 1005 this); 1006 if (SyncContinue(result)) 1007 { 1008 Complete(true); 1009 } 1010 } 1011 else 1012 { 1013 if (AfterUnlock()) 1014 { 1015 Complete(true); 1016 } 1017 } 1018 } 1019 else 1020 { 1021 // If we're not visible because we were aborted in a ----, the caller needs to know. 1022 lock (this.persistenceContext.ThisLock) 1023 { 1024 this.persistenceContext.ThrowIfDisposedOrNotOpen(); 1025 } 1026 Complete(true); 1027 } 1028 success = true; 1029 } 1030 catch (OperationCanceledException exception) 1031 { 1032 throw FxTrace.Exception.AsError(new CommunicationObjectAbortedException(SR.HandleFreedInDirectory, exception)); 1033 } 1034 catch (TimeoutException) 1035 { 1036 this.persistenceContext.Fault(); 1037 throw; 1038 } 1039 finally 1040 { 1041 if (!success) 1042 { 1043 try 1044 { 1045 if (this.transaction != null) 1046 { 1047 this.transaction.Complete(); 1048 } 1049 } 1050 finally 1051 { 1052 this.persistenceContext.FinishOperation(); 1053 } 1054 } 1055 } 1056 } 1057 End(IAsyncResult result)1058 public static void End(IAsyncResult result) 1059 { 1060 AsyncResult.End<ReleaseAsyncResult>(result); 1061 } 1062 HandleEndExecute(IAsyncResult result)1063 static bool HandleEndExecute(IAsyncResult result) 1064 { 1065 ReleaseAsyncResult thisPtr = (ReleaseAsyncResult)result.AsyncState; 1066 thisPtr.persistenceContext.store.EndExecute(result); 1067 return thisPtr.AfterUnlock(); 1068 } 1069 AfterUnlock()1070 bool AfterUnlock() 1071 { 1072 this.persistenceContext.IsLocked = false; 1073 1074 IAsyncResult result; 1075 using (PrepareTransactionalCall(this.transaction)) 1076 { 1077 result = this.persistenceContext.BeginEnlist(this.timeoutHelper.RemainingTime(), PrepareAsyncCompletion(ReleaseAsyncResult.handleEndEnlist), this); 1078 } 1079 return SyncContinue(result); 1080 } 1081 HandleEndEnlist(IAsyncResult result)1082 static bool HandleEndEnlist(IAsyncResult result) 1083 { 1084 ReleaseAsyncResult thisPtr = (ReleaseAsyncResult)result.AsyncState; 1085 thisPtr.persistenceContext.EndEnlist(result); 1086 1087 if (!thisPtr.persistenceContext.ScheduleDetach()) 1088 { 1089 thisPtr.persistenceContext.directory.RemoveInstance(thisPtr.persistenceContext); 1090 } 1091 1092 foreach (InstanceKey key in thisPtr.persistenceContext.keysToAssociate) 1093 { 1094 thisPtr.persistenceContext.AssociatedKeys.Remove(key); 1095 } 1096 thisPtr.persistenceContext.keysToAssociate.Clear(); 1097 thisPtr.persistenceContext.keysToDisassociate.Clear(); 1098 1099 return true; 1100 } 1101 OnFinishOperation(AsyncResult result, Exception exception)1102 void OnFinishOperation(AsyncResult result, Exception exception) 1103 { 1104 try 1105 { 1106 this.persistenceContext.OnFinishOperationHelper(exception, false); 1107 } 1108 finally 1109 { 1110 if (this.transaction != null) 1111 { 1112 this.transaction.Complete(); 1113 } 1114 } 1115 } 1116 } 1117 1118 class AssociateKeysAsyncResult : TransactedAsyncResult 1119 { 1120 static readonly AsyncCompletion handleEndExecute = new AsyncCompletion(HandleEndExecute); 1121 static readonly AsyncCompletion handleEndEnlist = new AsyncCompletion(HandleEndEnlist); 1122 1123 readonly PersistenceContext persistenceContext; 1124 readonly bool applicationKeys; 1125 readonly ICollection<InstanceKey> keysToAssociate; 1126 readonly TimeoutHelper timeoutHelper; 1127 readonly DependentTransaction transaction; 1128 AssociateKeysAsyncResult(PersistenceContext persistenceContext, ICollection<InstanceKey> associatedKeys, TimeSpan timeout, bool applicationKeys, AsyncCallback callback, object state)1129 public AssociateKeysAsyncResult(PersistenceContext persistenceContext, ICollection<InstanceKey> associatedKeys, TimeSpan timeout, 1130 bool applicationKeys, AsyncCallback callback, object state) 1131 : base(callback, state) 1132 { 1133 this.persistenceContext = persistenceContext; 1134 this.applicationKeys = applicationKeys; 1135 this.keysToAssociate = associatedKeys; 1136 this.timeoutHelper = new TimeoutHelper(timeout); 1137 1138 OnCompleting = new Action<AsyncResult, Exception>(OnFinishOperation); 1139 1140 bool success = false; 1141 try 1142 { 1143 this.persistenceContext.StartOperation(); 1144 1145 this.persistenceContext.ThrowIfCompleted(); 1146 this.persistenceContext.ThrowIfNotVisible(); 1147 Fx.Assert(!this.persistenceContext.IsInitialized || this.persistenceContext.IsLocked, 1148 "Should not be visible if initialized and not locked."); 1149 1150 Transaction currentTransaction = Transaction.Current; 1151 if (currentTransaction != null) 1152 { 1153 this.transaction = currentTransaction.DependentClone(DependentCloneOption.BlockCommitUntilComplete); 1154 } 1155 1156 // We need to get the transaction lock and enlist on the transaction, if there is one. 1157 IAsyncResult enlistResult = persistenceContext.BeginEnlist(this.timeoutHelper.RemainingTime(), 1158 this.PrepareAsyncCompletion(handleEndEnlist), this); 1159 if (SyncContinue(enlistResult)) 1160 { 1161 Complete(true); 1162 } 1163 success = true; 1164 } 1165 catch (InstancePersistenceException) 1166 { 1167 this.persistenceContext.Fault(); 1168 throw; 1169 } 1170 catch (OperationCanceledException exception) 1171 { 1172 throw FxTrace.Exception.AsError(new CommunicationObjectAbortedException(SR.HandleFreedInDirectory, exception)); 1173 } 1174 catch (TimeoutException) 1175 { 1176 this.persistenceContext.Fault(); 1177 throw; 1178 } 1179 finally 1180 { 1181 if (!success) 1182 { 1183 try 1184 { 1185 // We need to complete our dependent clone because OnFinishOperation will not 1186 // get called in this case. 1187 if (this.transaction != null) 1188 { 1189 this.transaction.Complete(); 1190 } 1191 } 1192 finally 1193 { 1194 this.persistenceContext.FinishOperation(); 1195 } 1196 } 1197 } 1198 } 1199 End(IAsyncResult result)1200 public static void End(IAsyncResult result) 1201 { 1202 AsyncResult.End<AssociateKeysAsyncResult>(result); 1203 } 1204 HandleEndExecute(IAsyncResult result)1205 static bool HandleEndExecute(IAsyncResult result) 1206 { 1207 AssociateKeysAsyncResult thisPtr = (AssociateKeysAsyncResult)result.AsyncState; 1208 thisPtr.persistenceContext.store.EndExecute(result); 1209 return thisPtr.AfterUpdate(); 1210 } 1211 HandleEndEnlist(IAsyncResult result)1212 static bool HandleEndEnlist(IAsyncResult result) 1213 { 1214 AssociateKeysAsyncResult thisPtr = (AssociateKeysAsyncResult)result.AsyncState; 1215 bool returnValue = false; 1216 1217 if (!thisPtr.persistenceContext.directory.TryAddAssociations( 1218 thisPtr.persistenceContext, 1219 thisPtr.keysToAssociate, 1220 thisPtr.persistenceContext.keysToAssociate, 1221 thisPtr.applicationKeys ? thisPtr.persistenceContext.keysToDisassociate : null)) 1222 { 1223 lock (thisPtr.persistenceContext.ThisLock) 1224 { 1225 thisPtr.persistenceContext.ThrowIfDisposedOrNotOpen(); 1226 } 1227 throw Fx.AssertAndThrow("Should only fail to add keys in a ---- with abort."); 1228 } 1229 1230 if (thisPtr.persistenceContext.directory.ConsistencyScope == DurableConsistencyScope.Global) 1231 { 1232 // Only do a SetKeysToPersist or Save command if we have keys to associate or disassociate. 1233 // It's possible that we got invoked with a key that was already in the 1234 // AssociatedKeys collection. 1235 if ((thisPtr.persistenceContext.keysToAssociate.Count != 0) || 1236 ((thisPtr.persistenceContext.keysToDisassociate.Count != 0) && 1237 (thisPtr.applicationKeys))) 1238 { 1239 if (thisPtr.persistenceContext.store != null) 1240 { 1241 SaveWorkflowCommand saveCommand = new SaveWorkflowCommand(); 1242 foreach (InstanceKey key in thisPtr.persistenceContext.keysToAssociate) 1243 { 1244 saveCommand.InstanceKeysToAssociate.Add(key.Value, key.Metadata); 1245 } 1246 if (thisPtr.applicationKeys) 1247 { 1248 foreach (InstanceKey key in thisPtr.persistenceContext.keysToDisassociate) 1249 { 1250 // We are going to Complete and Disassociate with the same Save command. 1251 saveCommand.InstanceKeysToComplete.Add(key.Value); 1252 saveCommand.InstanceKeysToFree.Add(key.Value); 1253 } 1254 } 1255 IAsyncResult beginExecuteResult = null; 1256 using (thisPtr.PrepareTransactionalCall(thisPtr.transaction)) 1257 { 1258 beginExecuteResult = thisPtr.persistenceContext.store.BeginExecute( 1259 thisPtr.persistenceContext.handle, 1260 saveCommand, 1261 thisPtr.timeoutHelper.RemainingTime(), 1262 thisPtr.PrepareAsyncCompletion(AssociateKeysAsyncResult.handleEndExecute), 1263 thisPtr); 1264 } 1265 returnValue = thisPtr.SyncContinue(beginExecuteResult); 1266 } 1267 } 1268 else 1269 { 1270 returnValue = thisPtr.AfterUpdate(); 1271 } 1272 } 1273 else 1274 { 1275 returnValue = thisPtr.AfterUpdate(); 1276 } 1277 1278 return returnValue; 1279 } 1280 AfterUpdate()1281 bool AfterUpdate() 1282 { 1283 if (this.applicationKeys) 1284 { 1285 this.persistenceContext.RationalizeSavedKeys(true); 1286 } 1287 else 1288 { 1289 this.persistenceContext.keysToAssociate.Clear(); 1290 } 1291 1292 return true; 1293 } 1294 OnFinishOperation(AsyncResult result, Exception exception)1295 void OnFinishOperation(AsyncResult result, Exception exception) 1296 { 1297 if (exception is InstancePersistenceException) 1298 { 1299 this.persistenceContext.Fault(); 1300 } 1301 try 1302 { 1303 this.persistenceContext.OnFinishOperationHelper(exception, false); 1304 } 1305 finally 1306 { 1307 // We are all done. If we have a savedTransaction, we need to complete it now. 1308 if (this.transaction != null) 1309 { 1310 this.transaction.Complete(); 1311 } 1312 } 1313 } 1314 } 1315 1316 class UpdateSuspendMetadataAsyncResult : AsyncResult 1317 { 1318 static readonly AsyncCompletion handleEndExecute = new AsyncCompletion(HandleEndExecute); 1319 1320 readonly PersistenceContext persistenceContext; 1321 readonly TimeoutHelper timeoutHelper; 1322 readonly DependentTransaction transaction; 1323 UpdateSuspendMetadataAsyncResult(PersistenceContext persistenceContext, Exception reason, TimeSpan timeout, AsyncCallback callback, object state)1324 public UpdateSuspendMetadataAsyncResult(PersistenceContext persistenceContext, Exception reason, TimeSpan timeout, AsyncCallback callback, object state) 1325 : base(callback, state) 1326 { 1327 this.persistenceContext = persistenceContext; 1328 OnCompleting = new Action<AsyncResult, Exception>(OnFinishOperation); 1329 1330 bool success = false; 1331 try 1332 { 1333 this.persistenceContext.StartOperation(); 1334 1335 this.timeoutHelper = new TimeoutHelper(timeout); 1336 1337 Transaction currentTransaction = Transaction.Current; 1338 if (currentTransaction != null) 1339 { 1340 this.transaction = currentTransaction.DependentClone(DependentCloneOption.BlockCommitUntilComplete); 1341 } 1342 1343 if (this.persistenceContext.store != null) 1344 { 1345 SaveWorkflowCommand saveCommand = new SaveWorkflowCommand(); 1346 this.persistenceContext.PopulateActivationMetadata(saveCommand); 1347 saveCommand.InstanceMetadataChanges[WorkflowServiceNamespace.SuspendReason] = new InstanceValue(reason.Message); 1348 saveCommand.InstanceMetadataChanges[WorkflowServiceNamespace.SuspendException] = new InstanceValue(reason, InstanceValueOptions.WriteOnly | InstanceValueOptions.Optional); 1349 saveCommand.UnlockInstance = true; 1350 1351 IAsyncResult result = this.persistenceContext.store.BeginExecute( 1352 this.persistenceContext.handle, 1353 saveCommand, 1354 this.timeoutHelper.RemainingTime(), 1355 PrepareAsyncCompletion(handleEndExecute), 1356 this); 1357 if (SyncContinue(result)) 1358 { 1359 Complete(true); 1360 } 1361 } 1362 else 1363 { 1364 Complete(true); 1365 } 1366 success = true; 1367 } 1368 catch (OperationCanceledException exception) 1369 { 1370 throw FxTrace.Exception.AsError(new CommunicationObjectAbortedException(SR.HandleFreedInDirectory, exception)); 1371 } 1372 catch (TimeoutException) 1373 { 1374 this.persistenceContext.Fault(); 1375 throw; 1376 } 1377 finally 1378 { 1379 if (!success) 1380 { 1381 try 1382 { 1383 if (this.transaction != null) 1384 { 1385 this.transaction.Complete(); 1386 } 1387 } 1388 finally 1389 { 1390 this.persistenceContext.FinishOperation(); 1391 } 1392 } 1393 } 1394 } 1395 End(IAsyncResult result)1396 public static void End(IAsyncResult result) 1397 { 1398 AsyncResult.End<UpdateSuspendMetadataAsyncResult>(result); 1399 } 1400 HandleEndExecute(IAsyncResult result)1401 static bool HandleEndExecute(IAsyncResult result) 1402 { 1403 UpdateSuspendMetadataAsyncResult thisPtr = (UpdateSuspendMetadataAsyncResult)result.AsyncState; 1404 thisPtr.persistenceContext.store.EndExecute(result); 1405 return true; 1406 } 1407 OnFinishOperation(AsyncResult result, Exception exception)1408 void OnFinishOperation(AsyncResult result, Exception exception) 1409 { 1410 try 1411 { 1412 this.persistenceContext.OnFinishOperationHelper(exception, false); 1413 } 1414 finally 1415 { 1416 if (this.transaction != null) 1417 { 1418 this.transaction.Complete(); 1419 } 1420 } 1421 } 1422 } 1423 } 1424 } 1425