1 //---------------------------------------------------------------- 2 // Copyright (c) Microsoft Corporation. All rights reserved. 3 //---------------------------------------------------------------- 4 5 namespace System.Runtime.DurableInstancing 6 { 7 using System.Collections.Generic; 8 using System.Collections.ObjectModel; 9 using System.Runtime.Serialization; 10 using System.Security; 11 using System.Threading; 12 using System.Transactions; 13 using System.Xml.Linq; 14 using System.Runtime.Diagnostics; 15 16 [Fx.Tag.XamlVisible(false)] 17 public sealed class InstancePersistenceContext 18 { 19 readonly TimeSpan timeout; 20 21 Transaction transaction; 22 bool freezeTransaction; 23 CommittableTransaction myTransaction; 24 int cancellationHandlerCalled; 25 EventTraceActivity eventTraceActivity; 26 InstancePersistenceContext(InstanceHandle handle, Transaction transaction)27 internal InstancePersistenceContext(InstanceHandle handle, Transaction transaction) 28 : this(handle) 29 { 30 Fx.Assert(transaction != null, "Null Transaction passed to InstancePersistenceContext."); 31 32 // Let's take our own clone of the transaction. We need to do this because we might need to 33 // create a TransactionScope using the transaction and in cases where we are dealing with a 34 // transaction that is flowed into the workflow on a message, the DependentTransaction that the 35 // dispatcher creates and sets to Transaction.Current may already be Completed by the time a 36 // Save operation is done. And since TransactionScope creates a DependentTransaction, it won't 37 // be able to. 38 // We don't create another DependentClone because we are going to do a EnlistVolatile on the 39 // transaction ourselves. 40 this.transaction = transaction.Clone(); 41 IsHostTransaction = true; 42 this.eventTraceActivity = handle.EventTraceActivity; 43 } 44 InstancePersistenceContext(InstanceHandle handle, TimeSpan timeout)45 internal InstancePersistenceContext(InstanceHandle handle, TimeSpan timeout) 46 : this(handle) 47 { 48 this.timeout = timeout; 49 } 50 InstancePersistenceContext(InstanceHandle handle)51 InstancePersistenceContext(InstanceHandle handle) 52 { 53 Fx.Assert(handle != null, "Null handle passed to InstancePersistenceContext."); 54 55 InstanceHandle = handle; 56 57 // Fork a copy of the current view to be the new working view. It starts with no query results. 58 InstanceView newView = handle.View.Clone(); 59 newView.InstanceStoreQueryResults = null; 60 InstanceView = newView; 61 62 this.cancellationHandlerCalled = 0; 63 } 64 65 public InstanceHandle InstanceHandle { get; private set; } 66 public InstanceView InstanceView { get; private set; } 67 68 public long InstanceVersion 69 { 70 get 71 { 72 return InstanceHandle.Version; 73 } 74 } 75 76 internal EventTraceActivity EventTraceActivity 77 { 78 get 79 { 80 return this.eventTraceActivity; 81 } 82 } 83 84 public Guid LockToken 85 { 86 get 87 { 88 Fx.Assert(InstanceHandle.Owner == null || InstanceHandle.Owner.OwnerToken == InstanceView.InstanceOwner.OwnerToken, "Mismatched lock tokens."); 89 90 // If the handle doesn't own the lock yet, return the owner LockToken, which is needed to check whether this owner already owns locks. 91 return InstanceHandle.Owner == null ? Guid.Empty : InstanceHandle.Owner.OwnerToken; 92 } 93 } 94 95 public object UserContext 96 { 97 get 98 { 99 return InstanceHandle.ProviderObject; 100 } 101 } 102 103 bool CancelRequested { get; set; } 104 105 ExecuteAsyncResult RootAsyncResult { get; set; } 106 ExecuteAsyncResult LastAsyncResult { get; set; } 107 bool IsHostTransaction { get; set; } 108 109 bool Active 110 { 111 get 112 { 113 return RootAsyncResult != null; 114 } 115 } 116 SetCancellationHandler(Action<InstancePersistenceContext> cancellationHandler)117 public void SetCancellationHandler(Action<InstancePersistenceContext> cancellationHandler) 118 { 119 ThrowIfNotActive("SetCancellationHandler"); 120 LastAsyncResult.CancellationHandler = cancellationHandler; 121 if (CancelRequested && (cancellationHandler != null)) 122 { 123 try 124 { 125 if (Interlocked.CompareExchange(ref this.cancellationHandlerCalled, 0, 1) == 0) 126 { 127 cancellationHandler(this); 128 } 129 } 130 catch (Exception exception) 131 { 132 if (Fx.IsFatal(exception)) 133 { 134 throw; 135 } 136 throw Fx.Exception.AsError(new CallbackException(SRCore.OnCancelRequestedThrew, exception)); 137 } 138 } 139 } 140 BindInstanceOwner(Guid instanceOwnerId, Guid lockToken)141 public void BindInstanceOwner(Guid instanceOwnerId, Guid lockToken) 142 { 143 if (instanceOwnerId == Guid.Empty) 144 { 145 throw Fx.Exception.Argument("instanceOwnerId", SRCore.GuidCannotBeEmpty); 146 } 147 if (lockToken == Guid.Empty) 148 { 149 throw Fx.Exception.Argument("lockToken", SRCore.GuidCannotBeEmpty); 150 } 151 ThrowIfNotActive("BindInstanceOwner"); 152 153 InstanceOwner owner = InstanceHandle.Store.GetOrCreateOwner(instanceOwnerId, lockToken); 154 155 InstanceView.BindOwner(owner); 156 IsHandleDoomedByRollback = true; 157 158 InstanceHandle.BindOwner(owner); 159 } 160 BindInstance(Guid instanceId)161 public void BindInstance(Guid instanceId) 162 { 163 if (instanceId == Guid.Empty) 164 { 165 throw Fx.Exception.Argument("instanceId", SRCore.GuidCannotBeEmpty); 166 } 167 ThrowIfNotActive("BindInstance"); 168 169 InstanceView.BindInstance(instanceId); 170 IsHandleDoomedByRollback = true; 171 172 InstanceHandle.BindInstance(instanceId); 173 } 174 BindEvent(InstancePersistenceEvent persistenceEvent)175 public void BindEvent(InstancePersistenceEvent persistenceEvent) 176 { 177 if (persistenceEvent == null) 178 { 179 throw Fx.Exception.ArgumentNull("persistenceEvent"); 180 } 181 ThrowIfNotActive("BindEvent"); 182 183 if (!InstanceView.IsBoundToInstanceOwner) 184 { 185 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.ContextMustBeBoundToOwner)); 186 } 187 IsHandleDoomedByRollback = true; 188 189 InstanceHandle.BindOwnerEvent(persistenceEvent); 190 } 191 BindAcquiredLock(long instanceVersion)192 public void BindAcquiredLock(long instanceVersion) 193 { 194 if (instanceVersion < 0) 195 { 196 throw Fx.Exception.ArgumentOutOfRange("instanceVersion", instanceVersion, SRCore.InvalidLockToken); 197 } 198 ThrowIfNotActive("BindAcquiredLock"); 199 200 // This call has a synchronization, so we are guaranteed it is only successful once. 201 InstanceView.BindLock(instanceVersion); 202 IsHandleDoomedByRollback = true; 203 204 InstanceHandle.Bind(instanceVersion); 205 } 206 BindReclaimedLock(long instanceVersion, TimeSpan timeout)207 public void BindReclaimedLock(long instanceVersion, TimeSpan timeout) 208 { 209 AsyncWaitHandle wait = InitiateBindReclaimedLockHelper("BindReclaimedLock", instanceVersion, timeout); 210 if (!wait.Wait(timeout)) 211 { 212 InstanceHandle.CancelReclaim(new TimeoutException(SRCore.TimedOutWaitingForLockResolution)); 213 } 214 ConcludeBindReclaimedLockHelper(); 215 } 216 BeginBindReclaimedLock(long instanceVersion, TimeSpan timeout, AsyncCallback callback, object state)217 public IAsyncResult BeginBindReclaimedLock(long instanceVersion, TimeSpan timeout, AsyncCallback callback, object state) 218 { 219 AsyncWaitHandle wait = InitiateBindReclaimedLockHelper("BeginBindReclaimedLock", instanceVersion, timeout); 220 return new BindReclaimedLockAsyncResult(this, wait, timeout, callback, state); 221 } 222 EndBindReclaimedLock(IAsyncResult result)223 public void EndBindReclaimedLock(IAsyncResult result) 224 { 225 BindReclaimedLockAsyncResult.End(result); 226 } 227 CreateBindReclaimedLockException(long instanceVersion)228 public Exception CreateBindReclaimedLockException(long instanceVersion) 229 { 230 AsyncWaitHandle wait = InitiateBindReclaimedLockHelper("CreateBindReclaimedLockException", instanceVersion, TimeSpan.MaxValue); 231 return new BindReclaimedLockException(wait); 232 } 233 InitiateBindReclaimedLockHelper(string methodName, long instanceVersion, TimeSpan timeout)234 AsyncWaitHandle InitiateBindReclaimedLockHelper(string methodName, long instanceVersion, TimeSpan timeout) 235 { 236 if (instanceVersion < 0) 237 { 238 throw Fx.Exception.ArgumentOutOfRange("instanceVersion", instanceVersion, SRCore.InvalidLockToken); 239 } 240 TimeoutHelper.ThrowIfNegativeArgument(timeout); 241 ThrowIfNotActive(methodName); 242 243 // This call has a synchronization, so we are guaranteed it is only successful once. 244 InstanceView.StartBindLock(instanceVersion); 245 IsHandleDoomedByRollback = true; 246 247 AsyncWaitHandle wait = InstanceHandle.StartReclaim(instanceVersion); 248 if (wait == null) 249 { 250 InstanceHandle.Free(); 251 throw Fx.Exception.AsError(new InstanceHandleConflictException(LastAsyncResult.CurrentCommand.Name, InstanceView.InstanceId)); 252 } 253 return wait; 254 } 255 ConcludeBindReclaimedLockHelper()256 void ConcludeBindReclaimedLockHelper() 257 { 258 // If FinishReclaim doesn't throw an exception, we are done - the reclaim was successful. 259 // The Try / Finally makes up for the reverse order of setting the handle, then the view. 260 long instanceVersion = -1; 261 try 262 { 263 if (!InstanceHandle.FinishReclaim(ref instanceVersion)) 264 { 265 InstanceHandle.Free(); 266 throw Fx.Exception.AsError(new InstanceHandleConflictException(LastAsyncResult.CurrentCommand.Name, InstanceView.InstanceId)); 267 } 268 Fx.Assert(instanceVersion >= 0, "Where did the instance version go?"); 269 } 270 finally 271 { 272 if (instanceVersion >= 0) 273 { 274 InstanceView.FinishBindLock(instanceVersion); 275 } 276 } 277 } 278 PersistedInstance(IDictionary<XName, InstanceValue> data)279 public void PersistedInstance(IDictionary<XName, InstanceValue> data) 280 { 281 ThrowIfNotLocked(); 282 ThrowIfCompleted(); 283 ThrowIfNotTransactional("PersistedInstance"); 284 285 InstanceView.InstanceData = data.ReadOnlyCopy(true); 286 InstanceView.InstanceDataConsistency = InstanceValueConsistency.None; 287 InstanceView.InstanceState = InstanceState.Initialized; 288 } 289 LoadedInstance(InstanceState state, IDictionary<XName, InstanceValue> instanceData, IDictionary<XName, InstanceValue> instanceMetadata, IDictionary<Guid, IDictionary<XName, InstanceValue>> associatedInstanceKeyMetadata, IDictionary<Guid, IDictionary<XName, InstanceValue>> completedInstanceKeyMetadata)290 public void LoadedInstance(InstanceState state, IDictionary<XName, InstanceValue> instanceData, IDictionary<XName, InstanceValue> instanceMetadata, IDictionary<Guid, IDictionary<XName, InstanceValue>> associatedInstanceKeyMetadata, IDictionary<Guid, IDictionary<XName, InstanceValue>> completedInstanceKeyMetadata) 291 { 292 if (state == InstanceState.Uninitialized) 293 { 294 if (instanceData != null && instanceData.Count > 0) 295 { 296 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.UninitializedCannotHaveData)); 297 } 298 } 299 else if (state == InstanceState.Completed) 300 { 301 if (associatedInstanceKeyMetadata != null && associatedInstanceKeyMetadata.Count > 0) 302 { 303 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.CompletedMustNotHaveAssociatedKeys)); 304 } 305 } 306 else if (state != InstanceState.Initialized) 307 { 308 throw Fx.Exception.Argument("state", SRCore.InvalidInstanceState); 309 } 310 ThrowIfNoInstance(); 311 ThrowIfNotActive("PersistedInstance"); 312 313 InstanceValueConsistency consistency = InstanceView.IsBoundToLock || state == InstanceState.Completed ? InstanceValueConsistency.None : InstanceValueConsistency.InDoubt; 314 315 ReadOnlyDictionaryInternal<XName, InstanceValue> instanceDataCopy = instanceData.ReadOnlyCopy(false); 316 ReadOnlyDictionaryInternal<XName, InstanceValue> instanceMetadataCopy = instanceMetadata.ReadOnlyCopy(false); 317 318 Dictionary<Guid, InstanceKeyView> keysCopy = null; 319 int totalKeys = (associatedInstanceKeyMetadata != null ? associatedInstanceKeyMetadata.Count : 0) + (completedInstanceKeyMetadata != null ? completedInstanceKeyMetadata.Count : 0); 320 if (totalKeys > 0) 321 { 322 keysCopy = new Dictionary<Guid, InstanceKeyView>(totalKeys); 323 } 324 if (associatedInstanceKeyMetadata != null && associatedInstanceKeyMetadata.Count > 0) 325 { 326 foreach (KeyValuePair<Guid, IDictionary<XName, InstanceValue>> keyMetadata in associatedInstanceKeyMetadata) 327 { 328 InstanceKeyView view = new InstanceKeyView(keyMetadata.Key); 329 view.InstanceKeyState = InstanceKeyState.Associated; 330 view.InstanceKeyMetadata = keyMetadata.Value.ReadOnlyCopy(false); 331 view.InstanceKeyMetadataConsistency = InstanceView.IsBoundToLock ? InstanceValueConsistency.None : InstanceValueConsistency.InDoubt; 332 keysCopy.Add(view.InstanceKey, view); 333 } 334 } 335 336 if (completedInstanceKeyMetadata != null && completedInstanceKeyMetadata.Count > 0) 337 { 338 foreach (KeyValuePair<Guid, IDictionary<XName, InstanceValue>> keyMetadata in completedInstanceKeyMetadata) 339 { 340 InstanceKeyView view = new InstanceKeyView(keyMetadata.Key); 341 view.InstanceKeyState = InstanceKeyState.Completed; 342 view.InstanceKeyMetadata = keyMetadata.Value.ReadOnlyCopy(false); 343 view.InstanceKeyMetadataConsistency = consistency; 344 keysCopy.Add(view.InstanceKey, view); 345 } 346 } 347 348 InstanceView.InstanceState = state; 349 350 InstanceView.InstanceData = instanceDataCopy; 351 InstanceView.InstanceDataConsistency = consistency; 352 353 InstanceView.InstanceMetadata = instanceMetadataCopy; 354 InstanceView.InstanceMetadataConsistency = consistency; 355 356 InstanceView.InstanceKeys = keysCopy == null ? null : new ReadOnlyDictionaryInternal<Guid, InstanceKeyView>(keysCopy); 357 InstanceView.InstanceKeysConsistency = consistency; 358 } 359 CompletedInstance()360 public void CompletedInstance() 361 { 362 ThrowIfNotLocked(); 363 ThrowIfUninitialized(); 364 ThrowIfCompleted(); 365 if ((InstanceView.InstanceKeysConsistency & InstanceValueConsistency.InDoubt) == 0) 366 { 367 foreach (KeyValuePair<Guid, InstanceKeyView> key in InstanceView.InstanceKeys) 368 { 369 if (key.Value.InstanceKeyState == InstanceKeyState.Associated) 370 { 371 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.CannotCompleteWithKeys)); 372 } 373 } 374 } 375 ThrowIfNotTransactional("CompletedInstance"); 376 377 InstanceView.InstanceState = InstanceState.Completed; 378 } 379 ReadInstanceMetadata(IDictionary<XName, InstanceValue> metadata, bool complete)380 public void ReadInstanceMetadata(IDictionary<XName, InstanceValue> metadata, bool complete) 381 { 382 ThrowIfNoInstance(); 383 ThrowIfNotActive("ReadInstanceMetadata"); 384 385 if (InstanceView.InstanceMetadataConsistency == InstanceValueConsistency.None) 386 { 387 return; 388 } 389 390 if (complete) 391 { 392 InstanceView.InstanceMetadata = metadata.ReadOnlyCopy(false); 393 InstanceView.InstanceMetadataConsistency = InstanceView.IsBoundToLock || InstanceView.InstanceState == InstanceState.Completed ? InstanceValueConsistency.None : InstanceValueConsistency.InDoubt; 394 } 395 else 396 { 397 if ((InstanceView.IsBoundToLock || InstanceView.InstanceState == InstanceState.Completed) && (InstanceView.InstanceMetadataConsistency & InstanceValueConsistency.InDoubt) != 0) 398 { 399 // In this case, prefer throwing out old data and keeping only authoritative data. 400 InstanceView.InstanceMetadata = metadata.ReadOnlyMergeInto(null, false); 401 InstanceView.InstanceMetadataConsistency = InstanceValueConsistency.Partial; 402 } 403 else 404 { 405 InstanceView.InstanceMetadata = metadata.ReadOnlyMergeInto(InstanceView.InstanceMetadata, false); 406 InstanceView.InstanceMetadataConsistency |= InstanceValueConsistency.Partial; 407 } 408 } 409 } 410 WroteInstanceMetadataValue(XName name, InstanceValue value)411 public void WroteInstanceMetadataValue(XName name, InstanceValue value) 412 { 413 if (name == null) 414 { 415 throw Fx.Exception.ArgumentNull("name"); 416 } 417 if (value == null) 418 { 419 throw Fx.Exception.ArgumentNull("value"); 420 } 421 ThrowIfNotLocked(); 422 ThrowIfCompleted(); 423 ThrowIfNotTransactional("WroteInstanceMetadataValue"); 424 425 InstanceView.AccumulatedMetadataWrites[name] = value; 426 } 427 AssociatedInstanceKey(Guid key)428 public void AssociatedInstanceKey(Guid key) 429 { 430 if (key == Guid.Empty) 431 { 432 throw Fx.Exception.Argument("key", SRCore.InvalidKeyArgument); 433 } 434 ThrowIfNotLocked(); 435 ThrowIfCompleted(); 436 ThrowIfNotTransactional("AssociatedInstanceKey"); 437 438 Dictionary<Guid, InstanceKeyView> copy = new Dictionary<Guid, InstanceKeyView>(InstanceView.InstanceKeys); 439 if ((InstanceView.InstanceKeysConsistency & InstanceValueConsistency.InDoubt) == 0 && copy.ContainsKey(key)) 440 { 441 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.KeyAlreadyAssociated)); 442 } 443 InstanceKeyView keyView = new InstanceKeyView(key); 444 keyView.InstanceKeyState = InstanceKeyState.Associated; 445 keyView.InstanceKeyMetadataConsistency = InstanceValueConsistency.None; 446 copy[keyView.InstanceKey] = keyView; 447 InstanceView.InstanceKeys = new ReadOnlyDictionaryInternal<Guid, InstanceKeyView>(copy); 448 } 449 CompletedInstanceKey(Guid key)450 public void CompletedInstanceKey(Guid key) 451 { 452 if (key == Guid.Empty) 453 { 454 throw Fx.Exception.Argument("key", SRCore.InvalidKeyArgument); 455 } 456 ThrowIfNotLocked(); 457 ThrowIfCompleted(); 458 ThrowIfNotTransactional("CompletedInstanceKey"); 459 460 InstanceKeyView existingKeyView; 461 InstanceView.InstanceKeys.TryGetValue(key, out existingKeyView); 462 if ((InstanceView.InstanceKeysConsistency & InstanceValueConsistency.InDoubt) == 0) 463 { 464 if (existingKeyView != null) 465 { 466 if (existingKeyView.InstanceKeyState == InstanceKeyState.Completed) 467 { 468 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.KeyAlreadyCompleted)); 469 } 470 } 471 else if ((InstanceView.InstanceKeysConsistency & InstanceValueConsistency.Partial) == 0) 472 { 473 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.KeyNotAssociated)); 474 } 475 } 476 477 if (existingKeyView != null) 478 { 479 existingKeyView.InstanceKeyState = InstanceKeyState.Completed; 480 } 481 else 482 { 483 Dictionary<Guid, InstanceKeyView> copy = new Dictionary<Guid, InstanceKeyView>(InstanceView.InstanceKeys); 484 InstanceKeyView keyView = new InstanceKeyView(key); 485 keyView.InstanceKeyState = InstanceKeyState.Completed; 486 keyView.InstanceKeyMetadataConsistency = InstanceValueConsistency.Partial; 487 copy[keyView.InstanceKey] = keyView; 488 InstanceView.InstanceKeys = new ReadOnlyDictionaryInternal<Guid, InstanceKeyView>(copy); 489 } 490 } 491 UnassociatedInstanceKey(Guid key)492 public void UnassociatedInstanceKey(Guid key) 493 { 494 if (key == Guid.Empty) 495 { 496 throw Fx.Exception.Argument("key", SRCore.InvalidKeyArgument); 497 } 498 ThrowIfNotLocked(); 499 ThrowIfCompleted(); 500 ThrowIfNotTransactional("UnassociatedInstanceKey"); 501 502 InstanceKeyView existingKeyView; 503 InstanceView.InstanceKeys.TryGetValue(key, out existingKeyView); 504 if ((InstanceView.InstanceKeysConsistency & InstanceValueConsistency.InDoubt) == 0) 505 { 506 if (existingKeyView != null) 507 { 508 if (existingKeyView.InstanceKeyState == InstanceKeyState.Associated) 509 { 510 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.KeyNotCompleted)); 511 } 512 } 513 else if ((InstanceView.InstanceKeysConsistency & InstanceValueConsistency.Partial) == 0) 514 { 515 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.KeyAlreadyUnassociated)); 516 } 517 } 518 519 if (existingKeyView != null) 520 { 521 Dictionary<Guid, InstanceKeyView> copy = new Dictionary<Guid, InstanceKeyView>(InstanceView.InstanceKeys); 522 copy.Remove(key); 523 InstanceView.InstanceKeys = new ReadOnlyDictionaryInternal<Guid, InstanceKeyView>(copy); 524 } 525 } 526 ReadInstanceKeyMetadata(Guid key, IDictionary<XName, InstanceValue> metadata, bool complete)527 public void ReadInstanceKeyMetadata(Guid key, IDictionary<XName, InstanceValue> metadata, bool complete) 528 { 529 if (key == Guid.Empty) 530 { 531 throw Fx.Exception.Argument("key", SRCore.InvalidKeyArgument); 532 } 533 ThrowIfNoInstance(); 534 ThrowIfNotActive("ReadInstanceKeyMetadata"); 535 536 InstanceKeyView keyView; 537 if (!InstanceView.InstanceKeys.TryGetValue(key, out keyView)) 538 { 539 if (InstanceView.InstanceKeysConsistency == InstanceValueConsistency.None) 540 { 541 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.KeyNotAssociated)); 542 } 543 544 Dictionary<Guid, InstanceKeyView> copy = new Dictionary<Guid, InstanceKeyView>(InstanceView.InstanceKeys); 545 keyView = new InstanceKeyView(key); 546 if (complete) 547 { 548 keyView.InstanceKeyMetadata = metadata.ReadOnlyCopy(false); 549 keyView.InstanceKeyMetadataConsistency = InstanceValueConsistency.None; 550 } 551 else 552 { 553 keyView.InstanceKeyMetadata = metadata.ReadOnlyMergeInto(null, false); 554 keyView.InstanceKeyMetadataConsistency = InstanceValueConsistency.Partial; 555 } 556 if (!InstanceView.IsBoundToLock && InstanceView.InstanceState != InstanceState.Completed) 557 { 558 keyView.InstanceKeyMetadataConsistency |= InstanceValueConsistency.InDoubt; 559 } 560 copy[keyView.InstanceKey] = keyView; 561 InstanceView.InstanceKeys = new ReadOnlyDictionaryInternal<Guid, InstanceKeyView>(copy); 562 } 563 else 564 { 565 if (keyView.InstanceKeyMetadataConsistency == InstanceValueConsistency.None) 566 { 567 return; 568 } 569 570 if (complete) 571 { 572 keyView.InstanceKeyMetadata = metadata.ReadOnlyCopy(false); 573 keyView.InstanceKeyMetadataConsistency = InstanceView.IsBoundToLock || InstanceView.InstanceState == InstanceState.Completed ? InstanceValueConsistency.None : InstanceValueConsistency.InDoubt; 574 } 575 else 576 { 577 if ((InstanceView.IsBoundToLock || InstanceView.InstanceState == InstanceState.Completed) && (keyView.InstanceKeyMetadataConsistency & InstanceValueConsistency.InDoubt) != 0) 578 { 579 // In this case, prefer throwing out old data and keeping only authoritative data. 580 keyView.InstanceKeyMetadata = metadata.ReadOnlyMergeInto(null, false); 581 keyView.InstanceKeyMetadataConsistency = InstanceValueConsistency.Partial; 582 } 583 else 584 { 585 keyView.InstanceKeyMetadata = metadata.ReadOnlyMergeInto(keyView.InstanceKeyMetadata, false); 586 keyView.InstanceKeyMetadataConsistency |= InstanceValueConsistency.Partial; 587 } 588 } 589 } 590 } 591 WroteInstanceKeyMetadataValue(Guid key, XName name, InstanceValue value)592 public void WroteInstanceKeyMetadataValue(Guid key, XName name, InstanceValue value) 593 { 594 if (key == Guid.Empty) 595 { 596 throw Fx.Exception.Argument("key", SRCore.InvalidKeyArgument); 597 } 598 if (name == null) 599 { 600 throw Fx.Exception.ArgumentNull("name"); 601 } 602 if (value == null) 603 { 604 throw Fx.Exception.ArgumentNull("value"); 605 } 606 ThrowIfNotLocked(); 607 ThrowIfCompleted(); 608 ThrowIfNotTransactional("WroteInstanceKeyMetadataValue"); 609 610 InstanceKeyView keyView; 611 if (!InstanceView.InstanceKeys.TryGetValue(key, out keyView)) 612 { 613 if (InstanceView.InstanceKeysConsistency == InstanceValueConsistency.None) 614 { 615 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.KeyNotAssociated)); 616 } 617 618 if (!value.IsWriteOnly() && !value.IsDeletedValue) 619 { 620 Dictionary<Guid, InstanceKeyView> copy = new Dictionary<Guid, InstanceKeyView>(InstanceView.InstanceKeys); 621 keyView = new InstanceKeyView(key); 622 keyView.AccumulatedMetadataWrites.Add(name, value); 623 keyView.InstanceKeyMetadataConsistency = InstanceValueConsistency.Partial; 624 copy[keyView.InstanceKey] = keyView; 625 InstanceView.InstanceKeys = new ReadOnlyDictionaryInternal<Guid, InstanceKeyView>(copy); 626 InstanceView.InstanceKeysConsistency |= InstanceValueConsistency.Partial; 627 } 628 } 629 else 630 { 631 keyView.AccumulatedMetadataWrites.Add(name, value); 632 } 633 } 634 ReadInstanceOwnerMetadata(IDictionary<XName, InstanceValue> metadata, bool complete)635 public void ReadInstanceOwnerMetadata(IDictionary<XName, InstanceValue> metadata, bool complete) 636 { 637 ThrowIfNoOwner(); 638 ThrowIfNotActive("ReadInstanceOwnerMetadata"); 639 640 if (InstanceView.InstanceOwnerMetadataConsistency == InstanceValueConsistency.None) 641 { 642 return; 643 } 644 645 if (complete) 646 { 647 InstanceView.InstanceOwnerMetadata = metadata.ReadOnlyCopy(false); 648 InstanceView.InstanceOwnerMetadataConsistency = InstanceValueConsistency.InDoubt; 649 } 650 else 651 { 652 InstanceView.InstanceOwnerMetadata = metadata.ReadOnlyMergeInto(InstanceView.InstanceOwnerMetadata, false); 653 InstanceView.InstanceOwnerMetadataConsistency |= InstanceValueConsistency.Partial; 654 } 655 } 656 WroteInstanceOwnerMetadataValue(XName name, InstanceValue value)657 public void WroteInstanceOwnerMetadataValue(XName name, InstanceValue value) 658 { 659 if (name == null) 660 { 661 throw Fx.Exception.ArgumentNull("name"); 662 } 663 if (value == null) 664 { 665 throw Fx.Exception.ArgumentNull("value"); 666 } 667 ThrowIfNoOwner(); 668 ThrowIfNotTransactional("WroteInstanceOwnerMetadataValue"); 669 670 InstanceView.AccumulatedOwnerMetadataWrites.Add(name, value); 671 } 672 QueriedInstanceStore(InstanceStoreQueryResult queryResult)673 public void QueriedInstanceStore(InstanceStoreQueryResult queryResult) 674 { 675 if (queryResult == null) 676 { 677 throw Fx.Exception.ArgumentNull("queryResult"); 678 } 679 ThrowIfNotActive("QueriedInstanceStore"); 680 681 InstanceView.QueryResultsBacking.Add(queryResult); 682 } 683 684 [Fx.Tag.Throws.Timeout("The operation timed out.")] 685 [Fx.Tag.Throws(typeof(OperationCanceledException), "The operation was canceled because the InstanceHandle has been freed.")] 686 [Fx.Tag.Throws(typeof(InstancePersistenceException), "A command failed.")] 687 [Fx.Tag.Blocking(CancelMethod = "NotifyHandleFree")] Execute(InstancePersistenceCommand command, TimeSpan timeout)688 public void Execute(InstancePersistenceCommand command, TimeSpan timeout) 689 { 690 if (command == null) 691 { 692 throw Fx.Exception.ArgumentNull("command"); 693 } 694 ThrowIfNotActive("Execute"); 695 696 try 697 { 698 ReconcileTransaction(); 699 ExecuteAsyncResult.End(new ExecuteAsyncResult(this, command, timeout)); 700 } 701 catch (TimeoutException) 702 { 703 InstanceHandle.Free(); 704 throw; 705 } 706 catch (OperationCanceledException) 707 { 708 InstanceHandle.Free(); 709 throw; 710 } 711 } 712 713 // For each level of hierarchy of command execution, only one BeginExecute may be pending at a time. 714 [Fx.Tag.InheritThrows(From = "Execute")] BeginExecute(InstancePersistenceCommand command, TimeSpan timeout, AsyncCallback callback, object state)715 public IAsyncResult BeginExecute(InstancePersistenceCommand command, TimeSpan timeout, AsyncCallback callback, object state) 716 { 717 if (command == null) 718 { 719 throw Fx.Exception.ArgumentNull("command"); 720 } 721 ThrowIfNotActive("BeginExecute"); 722 723 try 724 { 725 ReconcileTransaction(); 726 return new ExecuteAsyncResult(this, command, timeout, callback, state); 727 } 728 catch (TimeoutException) 729 { 730 InstanceHandle.Free(); 731 throw; 732 } 733 catch (OperationCanceledException) 734 { 735 InstanceHandle.Free(); 736 throw; 737 } 738 } 739 740 [Fx.Tag.InheritThrows(From = "Execute")] 741 [Fx.Tag.Blocking(CancelMethod = "NotifyHandleFree", Conditional = "!result.IsCompleted")] EndExecute(IAsyncResult result)742 public void EndExecute(IAsyncResult result) 743 { 744 ExecuteAsyncResult.End(result); 745 } 746 747 internal Transaction Transaction 748 { 749 get 750 { 751 return this.transaction; 752 } 753 } 754 755 internal bool IsHandleDoomedByRollback { get; private set; } 756 RequireTransaction()757 internal void RequireTransaction() 758 { 759 if (this.transaction != null) 760 { 761 return; 762 } 763 Fx.AssertAndThrow(!this.freezeTransaction, "RequireTransaction called when transaction is frozen."); 764 Fx.AssertAndThrow(Active, "RequireTransaction called when no command is active."); 765 766 // It's ok if some time has passed since the timeout value was acquired, it is ok to run long. This transaction is not generally responsible 767 // for timing out the Execute operation. The exception to this rule is during Commit. 768 this.myTransaction = new CommittableTransaction(new TransactionOptions() { IsolationLevel = IsolationLevel.ReadCommitted, Timeout = this.timeout }); 769 Transaction clone = this.myTransaction.Clone(); 770 RootAsyncResult.SetInteriorTransaction(this.myTransaction, true); 771 this.transaction = clone; 772 } 773 PrepareForReuse()774 internal void PrepareForReuse() 775 { 776 Fx.AssertAndThrow(!Active, "Prior use not yet complete!"); 777 Fx.AssertAndThrow(IsHostTransaction, "Can only reuse contexts with host transactions."); 778 } 779 NotifyHandleFree()780 internal void NotifyHandleFree() 781 { 782 CancelRequested = true; 783 ExecuteAsyncResult lastAsyncResult = LastAsyncResult; 784 Action<InstancePersistenceContext> onCancel = lastAsyncResult == null ? null : lastAsyncResult.CancellationHandler; 785 if (onCancel != null) 786 { 787 try 788 { 789 if (Interlocked.CompareExchange(ref this.cancellationHandlerCalled, 0, 1) == 0) 790 { 791 onCancel(this); 792 } 793 } 794 catch (Exception exception) 795 { 796 if (Fx.IsFatal(exception)) 797 { 798 throw; 799 } 800 throw Fx.Exception.AsError(new CallbackException(SRCore.OnCancelRequestedThrew, exception)); 801 } 802 } 803 } 804 805 [Fx.Tag.Blocking(CancelMethod = "NotifyHandleFree")] OuterExecute(InstanceHandle initialInstanceHandle, InstancePersistenceCommand command, Transaction transaction, TimeSpan timeout)806 internal static InstanceView OuterExecute(InstanceHandle initialInstanceHandle, InstancePersistenceCommand command, Transaction transaction, TimeSpan timeout) 807 { 808 try 809 { 810 return ExecuteAsyncResult.End(new ExecuteAsyncResult(initialInstanceHandle, command, transaction, timeout)); 811 } 812 catch (TimeoutException) 813 { 814 initialInstanceHandle.Free(); 815 throw; 816 } 817 catch (OperationCanceledException) 818 { 819 initialInstanceHandle.Free(); 820 throw; 821 } 822 } 823 BeginOuterExecute(InstanceHandle initialInstanceHandle, InstancePersistenceCommand command, Transaction transaction, TimeSpan timeout, AsyncCallback callback, object state)824 internal static IAsyncResult BeginOuterExecute(InstanceHandle initialInstanceHandle, InstancePersistenceCommand command, Transaction transaction, TimeSpan timeout, AsyncCallback callback, object state) 825 { 826 try 827 { 828 return new ExecuteAsyncResult(initialInstanceHandle, command, transaction, timeout, callback, state); 829 } 830 catch (TimeoutException) 831 { 832 initialInstanceHandle.Free(); 833 throw; 834 } 835 catch (OperationCanceledException) 836 { 837 initialInstanceHandle.Free(); 838 throw; 839 } 840 } 841 842 [Fx.Tag.Blocking(CancelMethod = "NotifyHandleFree", Conditional = "!result.IsCompleted")] EndOuterExecute(IAsyncResult result)843 internal static InstanceView EndOuterExecute(IAsyncResult result) 844 { 845 InstanceView finalState = ExecuteAsyncResult.End(result); 846 if (finalState == null) 847 { 848 throw Fx.Exception.Argument("result", InternalSR.InvalidAsyncResult); 849 } 850 return finalState; 851 } 852 ThrowIfNotLocked()853 void ThrowIfNotLocked() 854 { 855 if (!InstanceView.IsBoundToLock) 856 { 857 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.InstanceOperationRequiresLock)); 858 } 859 } 860 ThrowIfNoInstance()861 void ThrowIfNoInstance() 862 { 863 if (!InstanceView.IsBoundToInstance) 864 { 865 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.InstanceOperationRequiresInstance)); 866 } 867 } 868 ThrowIfNoOwner()869 void ThrowIfNoOwner() 870 { 871 if (!InstanceView.IsBoundToInstanceOwner) 872 { 873 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.InstanceOperationRequiresOwner)); 874 } 875 } 876 ThrowIfCompleted()877 void ThrowIfCompleted() 878 { 879 if (InstanceView.IsBoundToLock && InstanceView.InstanceState == InstanceState.Completed) 880 { 881 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.InstanceOperationRequiresNotCompleted)); 882 } 883 } 884 ThrowIfUninitialized()885 void ThrowIfUninitialized() 886 { 887 if (InstanceView.IsBoundToLock && InstanceView.InstanceState == InstanceState.Uninitialized) 888 { 889 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.InstanceOperationRequiresNotUninitialized)); 890 } 891 } 892 ThrowIfNotActive(string methodName)893 void ThrowIfNotActive(string methodName) 894 { 895 if (!Active) 896 { 897 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.OutsideInstanceExecutionScope(methodName))); 898 } 899 } 900 ThrowIfNotTransactional(string methodName)901 void ThrowIfNotTransactional(string methodName) 902 { 903 ThrowIfNotActive(methodName); 904 if (RootAsyncResult.CurrentCommand.IsTransactionEnlistmentOptional) 905 { 906 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.OutsideTransactionalCommand(methodName))); 907 } 908 } 909 ReconcileTransaction()910 void ReconcileTransaction() 911 { 912 // If the provider fails to flow the transaction, that's fine, we don't consider that a request 913 // not to use one. 914 Transaction transaction = Transaction.Current; 915 if (transaction != null) 916 { 917 if (this.transaction == null) 918 { 919 if (this.freezeTransaction) 920 { 921 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.MustSetTransactionOnFirstCall)); 922 } 923 RootAsyncResult.SetInteriorTransaction(transaction, false); 924 this.transaction = transaction; 925 } 926 else if (!transaction.Equals(this.transaction)) 927 { 928 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.CannotReplaceTransaction)); 929 } 930 } 931 this.freezeTransaction = true; 932 } 933 934 class ExecuteAsyncResult : TransactedAsyncResult, ISinglePhaseNotification 935 { 936 static AsyncCompletion onAcquireContext = new AsyncCompletion(OnAcquireContext); 937 static AsyncCompletion onTryCommand = new AsyncCompletion(OnTryCommand); 938 static AsyncCompletion onCommit = new AsyncCompletion(OnCommit); 939 static Action<object, TimeoutException> onBindReclaimed = new Action<object, TimeoutException>(OnBindReclaimed); 940 static Action<object, TimeoutException> onCommitWait = new Action<object, TimeoutException>(OnCommitWait); 941 942 readonly InstanceHandle initialInstanceHandle; 943 readonly Stack<IEnumerator<InstancePersistenceCommand>> executionStack; 944 readonly TimeoutHelper timeoutHelper; 945 readonly ExecuteAsyncResult priorAsyncResult; 946 947 InstancePersistenceContext context; 948 CommittableTransaction transactionToCommit; 949 IEnumerator<InstancePersistenceCommand> currentExecution; 950 AsyncWaitHandle waitForTransaction; 951 Action<InstancePersistenceContext> cancellationHandler; 952 bool executeCalledByCurrentCommand; 953 bool rolledBack; 954 bool inDoubt; 955 956 InstanceView finalState; 957 ExecuteAsyncResult(InstanceHandle initialInstanceHandle, InstancePersistenceCommand command, Transaction transaction, TimeSpan timeout, AsyncCallback callback, object state)958 public ExecuteAsyncResult(InstanceHandle initialInstanceHandle, InstancePersistenceCommand command, Transaction transaction, TimeSpan timeout, AsyncCallback callback, object state) 959 : this(command, timeout, callback, state) 960 { 961 this.initialInstanceHandle = initialInstanceHandle; 962 963 OnCompleting = new Action<AsyncResult, Exception>(SimpleCleanup); 964 965 IAsyncResult result = this.initialInstanceHandle.BeginAcquireExecutionContext(transaction, this.timeoutHelper.RemainingTime(), PrepareAsyncCompletion(ExecuteAsyncResult.onAcquireContext), this); 966 if (result.CompletedSynchronously) 967 { 968 // After this stage, must complete explicitly in order to get Cleanup to run correctly. 969 bool completeSelf = false; 970 Exception completionException = null; 971 try 972 { 973 completeSelf = OnAcquireContext(result); 974 } 975 catch (Exception exception) 976 { 977 if (Fx.IsFatal(exception)) 978 { 979 throw; 980 } 981 completeSelf = true; 982 completionException = exception; 983 } 984 if (completeSelf) 985 { 986 Complete(true, completionException); 987 } 988 } 989 } 990 ExecuteAsyncResult(InstancePersistenceContext context, InstancePersistenceCommand command, TimeSpan timeout, AsyncCallback callback, object state)991 public ExecuteAsyncResult(InstancePersistenceContext context, InstancePersistenceCommand command, TimeSpan timeout, AsyncCallback callback, object state) 992 : this(command, timeout, callback, state) 993 { 994 this.context = context; 995 996 this.priorAsyncResult = this.context.LastAsyncResult; 997 Fx.Assert(this.priorAsyncResult != null, "The LastAsyncResult should already have been checked."); 998 this.priorAsyncResult.executeCalledByCurrentCommand = true; 999 1000 OnCompleting = new Action<AsyncResult, Exception>(SimpleCleanup); 1001 1002 bool completeSelf = false; 1003 bool success = false; 1004 try 1005 { 1006 this.context.LastAsyncResult = this; 1007 if (RunLoop()) 1008 { 1009 completeSelf = true; 1010 } 1011 success = true; 1012 } 1013 finally 1014 { 1015 if (!success) 1016 { 1017 this.context.LastAsyncResult = this.priorAsyncResult; 1018 } 1019 } 1020 if (completeSelf) 1021 { 1022 Complete(true); 1023 } 1024 } 1025 1026 [Fx.Tag.Blocking(CancelMethod = "NotifyHandleFree", CancelDeclaringType = typeof(InstancePersistenceContext))] ExecuteAsyncResult(InstanceHandle initialInstanceHandle, InstancePersistenceCommand command, Transaction transaction, TimeSpan timeout)1027 public ExecuteAsyncResult(InstanceHandle initialInstanceHandle, InstancePersistenceCommand command, Transaction transaction, TimeSpan timeout) 1028 : this(command, timeout, null, null) 1029 { 1030 this.initialInstanceHandle = initialInstanceHandle; 1031 this.context = this.initialInstanceHandle.AcquireExecutionContext(transaction, this.timeoutHelper.RemainingTime()); 1032 1033 Exception completionException = null; 1034 try 1035 { 1036 // After this stage, must complete explicitly in order to get Cleanup to run correctly. 1037 this.context.RootAsyncResult = this; 1038 this.context.LastAsyncResult = this; 1039 OnCompleting = new Action<AsyncResult, Exception>(Cleanup); 1040 1041 RunLoopCore(true); 1042 1043 if (this.transactionToCommit != null) 1044 { 1045 try 1046 { 1047 this.transactionToCommit.Commit(); 1048 } 1049 catch (TransactionException) 1050 { 1051 // Since we are enlisted in this transaction, we can ignore exceptions from Commit. 1052 } 1053 this.transactionToCommit = null; 1054 } 1055 1056 DoWaitForTransaction(true); 1057 } 1058 catch (Exception exception) 1059 { 1060 if (Fx.IsFatal(exception)) 1061 { 1062 throw; 1063 } 1064 completionException = exception; 1065 } 1066 Complete(true, completionException); 1067 } 1068 1069 [Fx.Tag.Blocking(CancelMethod = "NotifyHandleFree", CancelDeclaringType = typeof(InstancePersistenceContext))] ExecuteAsyncResult(InstancePersistenceContext context, InstancePersistenceCommand command, TimeSpan timeout)1070 public ExecuteAsyncResult(InstancePersistenceContext context, InstancePersistenceCommand command, TimeSpan timeout) 1071 : this(command, timeout, null, null) 1072 { 1073 this.context = context; 1074 1075 this.priorAsyncResult = this.context.LastAsyncResult; 1076 Fx.Assert(this.priorAsyncResult != null, "The LastAsyncResult should already have been checked."); 1077 this.priorAsyncResult.executeCalledByCurrentCommand = true; 1078 1079 bool success = false; 1080 try 1081 { 1082 this.context.LastAsyncResult = this; 1083 RunLoopCore(true); 1084 success = true; 1085 } 1086 finally 1087 { 1088 this.context.LastAsyncResult = this.priorAsyncResult; 1089 if (!success && this.context.IsHandleDoomedByRollback) 1090 { 1091 this.context.InstanceHandle.Free(); 1092 } 1093 } 1094 Complete(true); 1095 } 1096 ExecuteAsyncResult(InstancePersistenceCommand command, TimeSpan timeout, AsyncCallback callback, object state)1097 ExecuteAsyncResult(InstancePersistenceCommand command, TimeSpan timeout, AsyncCallback callback, object state) 1098 : base(callback, state) 1099 { 1100 this.executionStack = new Stack<IEnumerator<InstancePersistenceCommand>>(2); 1101 this.timeoutHelper = new TimeoutHelper(timeout); 1102 1103 this.currentExecution = (new List<InstancePersistenceCommand> { command }).GetEnumerator(); 1104 } 1105 1106 internal InstancePersistenceCommand CurrentCommand { get; private set; } 1107 1108 internal Action<InstancePersistenceContext> CancellationHandler 1109 { 1110 get 1111 { 1112 Action<InstancePersistenceContext> handler = this.cancellationHandler; 1113 ExecuteAsyncResult current = this; 1114 while (handler == null) 1115 { 1116 current = current.priorAsyncResult; 1117 if (current == null) 1118 { 1119 break; 1120 } 1121 handler = current.cancellationHandler; 1122 } 1123 return handler; 1124 } 1125 1126 set 1127 { 1128 this.cancellationHandler = value; 1129 } 1130 } 1131 SetInteriorTransaction(Transaction interiorTransaction, bool needsCommit)1132 public void SetInteriorTransaction(Transaction interiorTransaction, bool needsCommit) 1133 { 1134 Fx.Assert(!this.context.IsHostTransaction, "SetInteriorTransaction called for a host transaction."); 1135 1136 if (this.waitForTransaction != null) 1137 { 1138 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.ExecuteMustBeNested)); 1139 } 1140 1141 bool success = false; 1142 try 1143 { 1144 this.waitForTransaction = new AsyncWaitHandle(EventResetMode.ManualReset); 1145 interiorTransaction.EnlistVolatile(this, EnlistmentOptions.None); 1146 success = true; 1147 } 1148 finally 1149 { 1150 if (!success) 1151 { 1152 if (this.waitForTransaction != null) 1153 { 1154 this.waitForTransaction.Set(); 1155 } 1156 } 1157 else if (needsCommit) 1158 { 1159 this.transactionToCommit = (CommittableTransaction)interiorTransaction; 1160 } 1161 } 1162 } 1163 1164 [Fx.Tag.Blocking(CancelMethod = "NotifyHandleFree", CancelDeclaringType = typeof(InstancePersistenceContext), Conditional = "!result.IsCOmpleted")] End(IAsyncResult result)1165 public static InstanceView End(IAsyncResult result) 1166 { 1167 ExecuteAsyncResult thisPtr = AsyncResult.End<ExecuteAsyncResult>(result); 1168 Fx.Assert((thisPtr.finalState == null) == (thisPtr.initialInstanceHandle == null), "Should have thrown an exception if this is null on the outer result."); 1169 return thisPtr.finalState; 1170 } 1171 OnAcquireContext(IAsyncResult result)1172 static bool OnAcquireContext(IAsyncResult result) 1173 { 1174 ExecuteAsyncResult thisPtr = (ExecuteAsyncResult)result.AsyncState; 1175 thisPtr.context = thisPtr.initialInstanceHandle.EndAcquireExecutionContext(result); 1176 thisPtr.context.RootAsyncResult = thisPtr; 1177 thisPtr.context.LastAsyncResult = thisPtr; 1178 thisPtr.OnCompleting = new Action<AsyncResult, Exception>(thisPtr.Cleanup); 1179 return thisPtr.RunLoop(); 1180 } 1181 1182 [Fx.Tag.Blocking(CancelMethod = "NotifyHandleFree", CancelDeclaringType = typeof(InstancePersistenceContext), Conditional = "synchronous")] RunLoopCore(bool synchronous)1183 bool RunLoopCore(bool synchronous) 1184 { 1185 while (this.currentExecution != null) 1186 { 1187 if (this.currentExecution.MoveNext()) 1188 { 1189 bool isFirstCommand = CurrentCommand == null; 1190 this.executeCalledByCurrentCommand = false; 1191 CurrentCommand = this.currentExecution.Current; 1192 1193 Fx.Assert(isFirstCommand || this.executionStack.Count > 0, "The first command should always remain at the top of the stack."); 1194 1195 if (isFirstCommand) 1196 { 1197 if (this.priorAsyncResult != null) 1198 { 1199 if (this.priorAsyncResult.CurrentCommand.IsTransactionEnlistmentOptional && !CurrentCommand.IsTransactionEnlistmentOptional) 1200 { 1201 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.CannotInvokeTransactionalFromNonTransactional)); 1202 } 1203 } 1204 } 1205 else if (this.executionStack.Peek().Current.IsTransactionEnlistmentOptional) 1206 { 1207 if (!CurrentCommand.IsTransactionEnlistmentOptional) 1208 { 1209 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.CannotInvokeTransactionalFromNonTransactional)); 1210 } 1211 } 1212 else if (this.priorAsyncResult == null) 1213 { 1214 // This is not the first command. Since the whole thing wasn't done at once by the 1215 // provider, force a transaction if the first command required one. 1216 this.context.RequireTransaction(); 1217 } 1218 1219 // Intentionally calling MayBindLockToInstanceHandle prior to Validate. This is a publically visible order. 1220 bool mayBindLockToInstanceHandle = CurrentCommand.AutomaticallyAcquiringLock; 1221 CurrentCommand.Validate(this.context.InstanceView); 1222 1223 if (mayBindLockToInstanceHandle) 1224 { 1225 if (isFirstCommand) 1226 { 1227 if (this.priorAsyncResult != null) 1228 { 1229 if (!this.priorAsyncResult.CurrentCommand.AutomaticallyAcquiringLock) 1230 { 1231 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.CannotInvokeBindingFromNonBinding)); 1232 } 1233 } 1234 else if (!this.context.InstanceView.IsBoundToInstanceOwner) 1235 { 1236 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.MayBindLockCommandShouldValidateOwner)); 1237 } 1238 else if (!this.context.InstanceView.IsBoundToLock) 1239 { 1240 // This is the first command in the set and it may lock, so we must start the bind. 1241 this.context.InstanceHandle.StartPotentialBind(); 1242 } 1243 } 1244 else if (!this.executionStack.Peek().Current.AutomaticallyAcquiringLock) 1245 { 1246 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.CannotInvokeBindingFromNonBinding)); 1247 } 1248 } 1249 1250 if (this.context.CancelRequested) 1251 { 1252 throw Fx.Exception.AsError(new OperationCanceledException(SRCore.HandleFreed)); 1253 } 1254 1255 BindReclaimedLockException bindReclaimedLockException = null; 1256 if (synchronous) 1257 { 1258 bool commandProcessed; 1259 TransactionScope txScope = null; 1260 try 1261 { 1262 txScope = TransactionHelper.CreateTransactionScope(this.context.Transaction); 1263 commandProcessed = this.context.InstanceHandle.Store.TryCommand(this.context, CurrentCommand, this.timeoutHelper.RemainingTime()); 1264 } 1265 catch (BindReclaimedLockException exception) 1266 { 1267 bindReclaimedLockException = exception; 1268 commandProcessed = true; 1269 } 1270 finally 1271 { 1272 TransactionHelper.CompleteTransactionScope(ref txScope); 1273 } 1274 AfterCommand(commandProcessed); 1275 if (bindReclaimedLockException != null) 1276 { 1277 BindReclaimed(!bindReclaimedLockException.MarkerWaitHandle.Wait(this.timeoutHelper.RemainingTime())); 1278 } 1279 } 1280 else 1281 { 1282 IAsyncResult result; 1283 using (PrepareTransactionalCall(this.context.Transaction)) 1284 { 1285 try 1286 { 1287 result = this.context.InstanceHandle.Store.BeginTryCommand(this.context, CurrentCommand, this.timeoutHelper.RemainingTime(), PrepareAsyncCompletion(ExecuteAsyncResult.onTryCommand), this); 1288 } 1289 catch (BindReclaimedLockException exception) 1290 { 1291 bindReclaimedLockException = exception; 1292 result = null; 1293 } 1294 } 1295 if (result == null) 1296 { 1297 AfterCommand(true); 1298 if (!bindReclaimedLockException.MarkerWaitHandle.WaitAsync(ExecuteAsyncResult.onBindReclaimed, this, this.timeoutHelper.RemainingTime())) 1299 { 1300 return false; 1301 } 1302 BindReclaimed(false); 1303 } 1304 else 1305 { 1306 if (!CheckSyncContinue(result) || !DoEndCommand(result)) 1307 { 1308 return false; 1309 } 1310 } 1311 } 1312 } 1313 else if (this.executionStack.Count > 0) 1314 { 1315 this.currentExecution = this.executionStack.Pop(); 1316 } 1317 else 1318 { 1319 this.currentExecution = null; 1320 } 1321 } 1322 1323 CurrentCommand = null; 1324 return true; 1325 } 1326 RunLoop()1327 bool RunLoop() 1328 { 1329 if (!RunLoopCore(false)) 1330 { 1331 return false; 1332 } 1333 1334 // If this is an inner command, return true right away to continue this execution episode in a different async result. 1335 if (this.initialInstanceHandle == null) 1336 { 1337 return true; 1338 } 1339 1340 // This is is an outer scope. We need to commit and/or wait for commit if necessary. 1341 if (this.transactionToCommit != null) 1342 { 1343 IAsyncResult result = null; 1344 try 1345 { 1346 result = this.transactionToCommit.BeginCommit(PrepareAsyncCompletion(ExecuteAsyncResult.onCommit), this); 1347 } 1348 catch (TransactionException) 1349 { 1350 // Since we are enlisted in the transaction, we can ignore exceptions from Commit. 1351 this.transactionToCommit = null; 1352 } 1353 if (result != null) 1354 { 1355 return result.CompletedSynchronously ? OnCommit(result) : false; 1356 } 1357 } 1358 1359 return DoWaitForTransaction(false); 1360 } 1361 OnTryCommand(IAsyncResult result)1362 static bool OnTryCommand(IAsyncResult result) 1363 { 1364 ExecuteAsyncResult thisPtr = (ExecuteAsyncResult)result.AsyncState; 1365 return thisPtr.DoEndCommand(result) && thisPtr.RunLoop(); 1366 } 1367 1368 [Fx.Tag.GuaranteeNonBlocking] DoEndCommand(IAsyncResult result)1369 bool DoEndCommand(IAsyncResult result) 1370 { 1371 bool commandProcessed; 1372 BindReclaimedLockException bindReclaimedLockException = null; 1373 try 1374 { 1375 commandProcessed = this.context.InstanceHandle.Store.EndTryCommand(result); 1376 } 1377 catch (BindReclaimedLockException exception) 1378 { 1379 bindReclaimedLockException = exception; 1380 commandProcessed = true; 1381 } 1382 AfterCommand(commandProcessed); 1383 if (bindReclaimedLockException != null) 1384 { 1385 if (!bindReclaimedLockException.MarkerWaitHandle.WaitAsync(ExecuteAsyncResult.onBindReclaimed, this, this.timeoutHelper.RemainingTime())) 1386 { 1387 return false; 1388 } 1389 BindReclaimed(false); 1390 } 1391 return true; 1392 } 1393 AfterCommand(bool commandProcessed)1394 void AfterCommand(bool commandProcessed) 1395 { 1396 if (!object.ReferenceEquals(this.context.LastAsyncResult, this)) 1397 { 1398 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.ExecuteMustBeNested)); 1399 } 1400 if (!commandProcessed) 1401 { 1402 if (this.executeCalledByCurrentCommand) 1403 { 1404 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.TryCommandCannotExecuteSubCommandsAndReduce)); 1405 } 1406 IEnumerable<InstancePersistenceCommand> reduction = CurrentCommand.Reduce(this.context.InstanceView); 1407 if (reduction == null) 1408 { 1409 throw Fx.Exception.AsError(new NotSupportedException(SRCore.ProviderDoesNotSupportCommand(CurrentCommand.Name))); 1410 } 1411 this.executionStack.Push(this.currentExecution); 1412 this.currentExecution = reduction.GetEnumerator(); 1413 } 1414 } 1415 OnBindReclaimed(object state, TimeoutException timeoutException)1416 static void OnBindReclaimed(object state, TimeoutException timeoutException) 1417 { 1418 ExecuteAsyncResult thisPtr = (ExecuteAsyncResult)state; 1419 1420 bool completeSelf; 1421 Exception completionException = null; 1422 try 1423 { 1424 thisPtr.BindReclaimed(timeoutException != null); 1425 completeSelf = thisPtr.RunLoop(); 1426 } 1427 catch (Exception exception) 1428 { 1429 if (Fx.IsFatal(exception)) 1430 { 1431 throw; 1432 } 1433 completionException = exception; 1434 completeSelf = true; 1435 } 1436 if (completeSelf) 1437 { 1438 thisPtr.Complete(false, completionException); 1439 } 1440 } 1441 BindReclaimed(bool timedOut)1442 void BindReclaimed(bool timedOut) 1443 { 1444 if (timedOut) 1445 { 1446 this.context.InstanceHandle.CancelReclaim(new TimeoutException(SRCore.TimedOutWaitingForLockResolution)); 1447 } 1448 this.context.ConcludeBindReclaimedLockHelper(); 1449 1450 // If we get here, the reclaim attempt succeeded and we own the lock - but we are in the 1451 // CreateBindReclaimedLockException path, which auto-cancels on success. 1452 this.context.InstanceHandle.Free(); 1453 throw Fx.Exception.AsError(new OperationCanceledException(SRCore.BindReclaimSucceeded)); 1454 } 1455 1456 [Fx.Tag.GuaranteeNonBlocking] OnCommit(IAsyncResult result)1457 static bool OnCommit(IAsyncResult result) 1458 { 1459 ExecuteAsyncResult thisPtr = (ExecuteAsyncResult)result.AsyncState; 1460 try 1461 { 1462 thisPtr.transactionToCommit.EndCommit(result); 1463 } 1464 catch (TransactionException) 1465 { 1466 // Since we are enlisted in the transaction, we can ignore exceptions from Commit. 1467 } 1468 thisPtr.transactionToCommit = null; 1469 return thisPtr.DoWaitForTransaction(false); 1470 } 1471 1472 [Fx.Tag.Blocking(CancelMethod = "NotifyHandleFree", CancelDeclaringType = typeof(InstancePersistenceContext), Conditional = "synchronous")] DoWaitForTransaction(bool synchronous)1473 bool DoWaitForTransaction(bool synchronous) 1474 { 1475 if (this.waitForTransaction != null) 1476 { 1477 if (synchronous) 1478 { 1479 TimeSpan waitTimeout = this.timeoutHelper.RemainingTime(); 1480 if (!this.waitForTransaction.Wait(waitTimeout)) 1481 { 1482 throw Fx.Exception.AsError(new TimeoutException(InternalSR.TimeoutOnOperation(waitTimeout))); 1483 } 1484 } 1485 else 1486 { 1487 if (!this.waitForTransaction.WaitAsync(ExecuteAsyncResult.onCommitWait, this, this.timeoutHelper.RemainingTime())) 1488 { 1489 return false; 1490 } 1491 } 1492 Exception exception = AfterCommitWait(); 1493 if (exception != null) 1494 { 1495 throw Fx.Exception.AsError(exception); 1496 } 1497 } 1498 else if (this.context.IsHostTransaction) 1499 { 1500 // For host transactions, we need to provide a clone of the intermediate state as the final state. 1501 this.finalState = this.context.InstanceView.Clone(); 1502 this.finalState.MakeReadOnly(); 1503 1504 // The intermediate state should have the query results cleared - they are per-call of Execute. 1505 this.context.InstanceView.InstanceStoreQueryResults = null; 1506 } 1507 else 1508 { 1509 // If we get here, there's no transaction at all. Need to "commit" the intermediate state. 1510 CommitHelper(); 1511 if (this.finalState == null) 1512 { 1513 this.context.InstanceHandle.Free(); 1514 throw Fx.Exception.AsError(new InstanceHandleConflictException(null, this.context.InstanceView.InstanceId)); 1515 } 1516 } 1517 return true; 1518 } 1519 OnCommitWait(object state, TimeoutException exception)1520 static void OnCommitWait(object state, TimeoutException exception) 1521 { 1522 ExecuteAsyncResult thisPtr = (ExecuteAsyncResult)state; 1523 thisPtr.Complete(false, exception ?? thisPtr.AfterCommitWait()); 1524 } 1525 AfterCommitWait()1526 Exception AfterCommitWait() 1527 { 1528 if (this.inDoubt) 1529 { 1530 this.context.InstanceHandle.Free(); 1531 return new TransactionInDoubtException(SRCore.TransactionInDoubtNonHost); 1532 } 1533 if (this.rolledBack) 1534 { 1535 if (this.context.IsHandleDoomedByRollback) 1536 { 1537 this.context.InstanceHandle.Free(); 1538 } 1539 return new TransactionAbortedException(SRCore.TransactionRolledBackNonHost); 1540 } 1541 if (this.finalState == null) 1542 { 1543 this.context.InstanceHandle.Free(); 1544 return new InstanceHandleConflictException(null, this.context.InstanceView.InstanceId); 1545 } 1546 return null; 1547 } 1548 CommitHelper()1549 void CommitHelper() 1550 { 1551 this.finalState = this.context.InstanceHandle.Commit(this.context.InstanceView); 1552 } 1553 SimpleCleanup(AsyncResult result, Exception exception)1554 void SimpleCleanup(AsyncResult result, Exception exception) 1555 { 1556 if (this.initialInstanceHandle == null) 1557 { 1558 Fx.Assert(this.priorAsyncResult != null, "In the non-outer case, we should always have a priorAsyncResult here, since we set it before ----igining OnComplete."); 1559 this.context.LastAsyncResult = this.priorAsyncResult; 1560 } 1561 if (exception != null) 1562 { 1563 if (this.context != null && this.context.IsHandleDoomedByRollback) 1564 { 1565 this.context.InstanceHandle.Free(); 1566 } 1567 else if (exception is TimeoutException || exception is OperationCanceledException) 1568 { 1569 if (this.context == null) 1570 { 1571 this.initialInstanceHandle.Free(); 1572 } 1573 else 1574 { 1575 this.context.InstanceHandle.Free(); 1576 } 1577 } 1578 } 1579 } 1580 Cleanup(AsyncResult result, Exception exception)1581 void Cleanup(AsyncResult result, Exception exception) 1582 { 1583 try 1584 { 1585 SimpleCleanup(result, exception); 1586 if (this.transactionToCommit != null) 1587 { 1588 try 1589 { 1590 this.transactionToCommit.Rollback(exception); 1591 } 1592 catch (TransactionException) 1593 { 1594 } 1595 } 1596 } 1597 finally 1598 { 1599 Fx.AssertAndThrowFatal(this.context.Active, "Out-of-sync between InstanceExecutionContext and ExecutionAsyncResult."); 1600 1601 this.context.LastAsyncResult = null; 1602 this.context.RootAsyncResult = null; 1603 this.context.InstanceHandle.ReleaseExecutionContext(); 1604 } 1605 } 1606 ISinglePhaseNotification.SinglePhaseCommit(SinglePhaseEnlistment singlePhaseEnlistment)1607 void ISinglePhaseNotification.SinglePhaseCommit(SinglePhaseEnlistment singlePhaseEnlistment) 1608 { 1609 CommitHelper(); 1610 singlePhaseEnlistment.Committed(); 1611 this.waitForTransaction.Set(); 1612 } 1613 IEnlistmentNotification.Commit(Enlistment enlistment)1614 void IEnlistmentNotification.Commit(Enlistment enlistment) 1615 { 1616 CommitHelper(); 1617 enlistment.Done(); 1618 this.waitForTransaction.Set(); 1619 } 1620 IEnlistmentNotification.InDoubt(Enlistment enlistment)1621 void IEnlistmentNotification.InDoubt(Enlistment enlistment) 1622 { 1623 enlistment.Done(); 1624 this.inDoubt = true; 1625 this.waitForTransaction.Set(); 1626 } 1627 IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)1628 void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment) 1629 { 1630 preparingEnlistment.Prepared(); 1631 } 1632 IEnlistmentNotification.Rollback(Enlistment enlistment)1633 void IEnlistmentNotification.Rollback(Enlistment enlistment) 1634 { 1635 enlistment.Done(); 1636 this.rolledBack = true; 1637 this.waitForTransaction.Set(); 1638 } 1639 } 1640 1641 class BindReclaimedLockAsyncResult : AsyncResult 1642 { 1643 static Action<object, TimeoutException> waitComplete = new Action<object, TimeoutException>(OnWaitComplete); 1644 1645 readonly InstancePersistenceContext context; 1646 BindReclaimedLockAsyncResult(InstancePersistenceContext context, AsyncWaitHandle wait, TimeSpan timeout, AsyncCallback callback, object state)1647 public BindReclaimedLockAsyncResult(InstancePersistenceContext context, AsyncWaitHandle wait, TimeSpan timeout, AsyncCallback callback, object state) 1648 : base(callback, state) 1649 { 1650 this.context = context; 1651 1652 if (wait.WaitAsync(BindReclaimedLockAsyncResult.waitComplete, this, timeout)) 1653 { 1654 this.context.ConcludeBindReclaimedLockHelper(); 1655 Complete(true); 1656 } 1657 } 1658 OnWaitComplete(object state, TimeoutException timeoutException)1659 static void OnWaitComplete(object state, TimeoutException timeoutException) 1660 { 1661 BindReclaimedLockAsyncResult thisPtr = (BindReclaimedLockAsyncResult)state; 1662 1663 Exception completionException = null; 1664 try 1665 { 1666 if (timeoutException != null) 1667 { 1668 thisPtr.context.InstanceHandle.CancelReclaim(new TimeoutException(SRCore.TimedOutWaitingForLockResolution)); 1669 } 1670 thisPtr.context.ConcludeBindReclaimedLockHelper(); 1671 } 1672 catch (Exception exception) 1673 { 1674 if (Fx.IsFatal(exception)) 1675 { 1676 throw; 1677 } 1678 completionException = exception; 1679 } 1680 thisPtr.Complete(false, completionException); 1681 } 1682 End(IAsyncResult result)1683 public static void End(IAsyncResult result) 1684 { 1685 AsyncResult.End<BindReclaimedLockAsyncResult>(result); 1686 } 1687 } 1688 1689 [Serializable] 1690 class BindReclaimedLockException : Exception 1691 { BindReclaimedLockException()1692 public BindReclaimedLockException() 1693 { 1694 } 1695 BindReclaimedLockException(AsyncWaitHandle markerWaitHandle)1696 internal BindReclaimedLockException(AsyncWaitHandle markerWaitHandle) 1697 : base(SRCore.BindReclaimedLockException) 1698 { 1699 MarkerWaitHandle = markerWaitHandle; 1700 } 1701 1702 internal AsyncWaitHandle MarkerWaitHandle { get; private set; } 1703 1704 [SecurityCritical] BindReclaimedLockException(SerializationInfo info, StreamingContext context)1705 protected BindReclaimedLockException(SerializationInfo info, StreamingContext context) 1706 : base(info, context) 1707 { 1708 } 1709 } 1710 } 1711 } 1712