Moving BSTR conv to native code in SecureStringToBSTR.
[mono.git] / mcs / class / referencesource / System.Activities / System / Activities / Hosting / WorkflowInstance.cs
1 //-----------------------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 //-----------------------------------------------------------------------------
4
5 namespace System.Activities.Hosting
6 {
7     using System;
8     using System.Text;
9     using System.Activities.DynamicUpdate;
10     using System.Activities.Runtime;
11     using System.Activities.Tracking;
12     using System.Activities.Validation;
13     using System.Collections.Generic;
14     using System.Collections.ObjectModel;
15     using System.Diagnostics;
16     using System.Diagnostics.CodeAnalysis;
17     using System.Globalization;
18     using System.Runtime;
19     using System.Runtime.DurableInstancing;
20     using System.Threading;
21     using System.Xml.Linq;
22
23     [Fx.Tag.XamlVisible(false)]
24     public abstract class WorkflowInstance
25     {
26         static readonly IDictionary<string, LocationInfo> EmptyMappedVariablesDictionary = new ReadOnlyDictionaryInternal<string, LocationInfo>(new Dictionary<string, LocationInfo>(0));
27
28         const int True = 1;
29         const int False = 0;
30
31         WorkflowInstanceControl controller;
32         TrackingProvider trackingProvider;
33         SynchronizationContext syncContext;
34         LocationReferenceEnvironment hostEnvironment;
35         ActivityExecutor executor;
36         int isPerformingOperation;
37         bool isInitialized;
38         WorkflowInstanceExtensionCollection extensions;
39
40         // Tracking for one-time actions per in-memory instance
41         bool hasTrackedResumed;
42         bool hasTrackedCompletion;
43
44         bool isAborted;
45         Exception abortedException;
46
47 #if DEBUG
48         StackTrace abortStack;
49 #endif
50
51         protected WorkflowInstance(Activity workflowDefinition)
52             : this(workflowDefinition, null)
53         {
54         }
55
56         protected WorkflowInstance(Activity workflowDefinition, WorkflowIdentity definitionIdentity)
57         {
58             if (workflowDefinition == null)
59             {
60                 throw FxTrace.Exception.ArgumentNull("workflowDefinition");
61             }
62
63             this.WorkflowDefinition = workflowDefinition;
64             this.DefinitionIdentity = definitionIdentity;
65         }
66
67         public abstract Guid Id
68         {
69             get;
70         }
71
72         internal bool HasTrackingParticipant
73         {
74             get;
75             private set;
76         }
77
78         internal bool HasTrackedStarted
79         {
80             get;
81             private set;
82         }
83
84         internal bool HasPersistenceModule
85         {
86             get;
87             private set;
88         }
89
90         public SynchronizationContext SynchronizationContext
91         {
92             get
93             {
94                 return this.syncContext;
95             }
96             set
97             {
98                 ThrowIfReadOnly();
99                 this.syncContext = value;
100             }
101         }
102
103         public LocationReferenceEnvironment HostEnvironment
104         {
105             get
106             {
107                 return this.hostEnvironment;
108             }
109             set
110             {
111                 ThrowIfReadOnly();
112                 this.hostEnvironment = value;
113             }
114         }
115
116         public Activity WorkflowDefinition
117         {
118             get;
119             private set;
120         }
121
122         public WorkflowIdentity DefinitionIdentity
123         {
124             get;
125             private set;
126         }
127
128         protected bool IsReadOnly
129         {
130             get
131             {
132                 return this.isInitialized;
133             }
134         }
135
136         protected internal abstract bool SupportsInstanceKeys
137         {
138             get;
139         }
140
141         // this is going away
142         internal TrackingProvider TrackingProvider
143         {
144             get
145             {
146                 Fx.Assert(HasTrackingParticipant, "we should only be called if we have a tracking participant");
147                 return this.trackingProvider;
148             }
149         }
150
151         protected WorkflowInstanceControl Controller
152         {
153             get
154             {
155                 if (!this.isInitialized)
156                 {
157                     throw FxTrace.Exception.AsError(new InvalidOperationException(SR.ControllerInvalidBeforeInitialize));
158                 }
159
160                 return this.controller;
161             }
162         }
163
164         // host-facing access to our cascading ExtensionManager resolution
165         protected internal T GetExtension<T>() where T : class
166         {
167             if (this.extensions != null)
168             {
169                 return this.extensions.Find<T>();
170             }
171             else
172             {
173                 return default(T);
174             }
175         }
176
177         protected internal IEnumerable<T> GetExtensions<T>() where T : class
178         {
179             if (this.extensions != null)
180             {
181                 return this.extensions.FindAll<T>();
182             }
183             else
184             {
185                 return new T[0];
186             }
187         }
188
189         // locks down the given extensions manager and runs cache metadata on the workflow definition
190         protected void RegisterExtensionManager(WorkflowInstanceExtensionManager extensionManager)
191         {
192             ValidateWorkflow(extensionManager);
193             this.extensions = WorkflowInstanceExtensionManager.CreateInstanceExtensions(this.WorkflowDefinition, extensionManager);
194             if (this.extensions != null)
195             {
196                 this.HasPersistenceModule = this.extensions.HasPersistenceModule;
197             }
198         }
199
200         // dispose the extensions that implement IDisposable
201         protected void DisposeExtensions()
202         {
203             if (this.extensions != null)
204             {
205                 this.extensions.Dispose();
206                 this.extensions = null;
207             }
208         }
209
210         protected static IList<ActivityBlockingUpdate> GetActivitiesBlockingUpdate(object deserializedRuntimeState, DynamicUpdateMap updateMap)
211         {
212             ActivityExecutor executor = deserializedRuntimeState as ActivityExecutor;
213             if (executor == null)
214             {
215                 throw FxTrace.Exception.Argument("deserializedRuntimeState", SR.InvalidRuntimeState);
216             }
217             if (updateMap == null)
218             {
219                 throw FxTrace.Exception.ArgumentNull("updateMap");
220             }
221
222             DynamicUpdateMap rootMap = updateMap;
223             if (updateMap.IsForImplementation)
224             {
225                 rootMap = updateMap.AsRootMap();
226             }
227             IList<ActivityBlockingUpdate> result = executor.GetActivitiesBlockingUpdate(rootMap);
228             if (result == null)
229             {
230                 result = new List<ActivityBlockingUpdate>();
231             }
232
233             return result;
234         }
235
236         // used for Create scenarios where you are providing root information
237         protected void Initialize(IDictionary<string, object> workflowArgumentValues, IList<Handle> workflowExecutionProperties)
238         {
239             ThrowIfAborted();
240             ThrowIfReadOnly();
241             this.executor = new ActivityExecutor(this);
242
243             EnsureDefinitionReady();
244             // workflowArgumentValues signals whether we are a new or loaded instance, so we can't pass in null.
245             // workflowExecutionProperties is allowed to be null
246             InitializeCore(workflowArgumentValues ?? ActivityUtilities.EmptyParameters, workflowExecutionProperties);
247         }
248
249         // used for Load scenarios where you are rehydrating a WorkflowInstance
250         protected void Initialize(object deserializedRuntimeState)
251         {
252             Initialize(deserializedRuntimeState, null);
253         }        
254
255         protected void Initialize(object deserializedRuntimeState, DynamicUpdateMap updateMap)
256         {
257             ThrowIfAborted();
258             ThrowIfReadOnly();
259             this.executor = deserializedRuntimeState as ActivityExecutor;
260
261             if (this.executor == null)
262             {
263                 throw FxTrace.Exception.Argument("deserializedRuntimeState", SR.InvalidRuntimeState);
264             }
265             this.executor.ThrowIfNonSerializable();
266
267             EnsureDefinitionReady();
268
269             WorkflowIdentity originalDefinitionIdentity = this.executor.WorkflowIdentity;      
270             bool success = false;
271             Collection<ActivityBlockingUpdate> updateErrors = null;
272             try
273             {
274                 if (updateMap != null)
275                 {
276                     // check if map is for implementaiton,                    
277                     if (updateMap.IsForImplementation)
278                     {
279                         // if so, the definition root must be an activity 
280                         // with no public/imported children and no public/imported delegates.
281                         if (DynamicUpdateMap.CanUseImplementationMapAsRoot(this.WorkflowDefinition))
282                         {
283                             updateMap = updateMap.AsRootMap();
284                         }
285                         else
286                         {
287                             throw FxTrace.Exception.AsError(new InstanceUpdateException(SR.InvalidImplementationAsWorkflowRoot));
288                         }
289                     }
290
291                     updateMap.ThrowIfInvalid(this.WorkflowDefinition);
292
293                     this.executor.WorkflowIdentity = this.DefinitionIdentity;
294
295                     this.executor.UpdateInstancePhase1(updateMap, this.WorkflowDefinition, ref updateErrors);
296                     ThrowIfDynamicUpdateErrorExists(updateErrors);
297                 }
298
299                 InitializeCore(null, null);
300
301                 if (updateMap != null)
302                 {
303                     this.executor.UpdateInstancePhase2(updateMap, ref updateErrors);
304                     ThrowIfDynamicUpdateErrorExists(updateErrors);
305                     // Track that dynamic update is successful
306                     if (this.Controller.TrackingEnabled)
307                     {
308                         this.Controller.Track(new WorkflowInstanceUpdatedRecord(this.Id, this.WorkflowDefinition.DisplayName, originalDefinitionIdentity, this.executor.WorkflowIdentity));
309                     }
310                 }
311
312                 success = true;
313             }
314             catch (InstanceUpdateException updateException)
315             {
316                 // Can't track through the controller because initialization failed
317                 if (this.HasTrackingParticipant && this.TrackingProvider.ShouldTrackWorkflowInstanceRecords)
318                 {
319                     IList<ActivityBlockingUpdate> blockingActivities = updateException.BlockingActivities;
320                     if (blockingActivities.Count == 0)
321                     {
322                         blockingActivities = new List<ActivityBlockingUpdate>
323                         {
324                             new ActivityBlockingUpdate(this.WorkflowDefinition, this.WorkflowDefinition.Id, updateException.Message)
325                         }.AsReadOnly();
326                     }
327                     this.TrackingProvider.AddRecord(new WorkflowInstanceUpdatedRecord(this.Id, this.WorkflowDefinition.DisplayName, originalDefinitionIdentity, this.DefinitionIdentity, blockingActivities));
328                 }
329                 throw;
330             }
331             finally
332             {
333                 if (updateMap != null && !success)
334                 {
335                     executor.MakeNonSerializable();
336                 }
337             }            
338         }
339
340         void ThrowIfDynamicUpdateErrorExists(Collection<ActivityBlockingUpdate> updateErrors)
341         {
342             if (updateErrors != null && updateErrors.Count > 0)
343             {
344                 // update error found
345                 // exit early
346
347                 throw FxTrace.Exception.AsError(new InstanceUpdateException(updateErrors));
348             }
349         }
350
351         void ValidateWorkflow(WorkflowInstanceExtensionManager extensionManager)
352         {
353             if (!WorkflowDefinition.IsRuntimeReady)
354             {
355                 LocationReferenceEnvironment localEnvironment = this.hostEnvironment;
356                 if (localEnvironment == null)
357                 {
358                     LocationReferenceEnvironment parentEnvironment = null;
359                     if (extensionManager != null && extensionManager.SymbolResolver != null)
360                     {
361                         parentEnvironment = extensionManager.SymbolResolver.AsLocationReferenceEnvironment();
362                     }
363                     localEnvironment = new ActivityLocationReferenceEnvironment(parentEnvironment);
364                 }
365                 IList<ValidationError> validationErrors = null;
366                 ActivityUtilities.CacheRootMetadata(WorkflowDefinition, localEnvironment, ProcessActivityTreeOptions.FullCachingOptions, null, ref validationErrors);
367                 ActivityValidationServices.ThrowIfViolationsExist(validationErrors);
368             }
369         }
370
371         void EnsureDefinitionReady()
372         {
373             if (this.extensions != null)
374             {
375                 this.extensions.Initialize();
376                 if (this.extensions.HasTrackingParticipant)
377                 {
378                     this.HasTrackingParticipant = true;
379                     if (this.trackingProvider == null)
380                     {
381                         this.trackingProvider = new TrackingProvider(this.WorkflowDefinition);
382                     }
383                     else
384                     {
385                         // TrackingProvider could be non-null if an earlier initialization attempt failed.
386                         // This happens when WorkflowApplication calls Abort after a load failure. In this
387                         // case we want to preserve any pending tracking records (e.g. DU failure).
388                         this.trackingProvider.ClearParticipants();
389                     }
390                     foreach (TrackingParticipant trackingParticipant in GetExtensions<TrackingParticipant>())
391                     {
392                         this.trackingProvider.AddParticipant(trackingParticipant);
393                     }
394                 }
395             }
396             else
397             {
398                 // need to ensure the workflow has been validated since the host isn't using extensions (and so didn't register anything)
399                 ValidateWorkflow(null);
400             }
401         }
402
403         void InitializeCore(IDictionary<string, object> workflowArgumentValues, IList<Handle> workflowExecutionProperties)
404         {
405             Fx.Assert(this.WorkflowDefinition.IsRuntimeReady, "EnsureDefinitionReady should have been called");
406             Fx.Assert(this.executor != null, "at this point, we better have an executor");
407
408             // Do Argument validation for root activities
409             WorkflowDefinition.HasBeenAssociatedWithAnInstance = true;
410
411             if (workflowArgumentValues != null)
412             {
413                 IDictionary<string, object> actualInputs = workflowArgumentValues;
414
415                 if (object.ReferenceEquals(actualInputs, ActivityUtilities.EmptyParameters))
416                 {
417                     actualInputs = null;
418                 }
419
420                 if (this.WorkflowDefinition.RuntimeArguments.Count > 0 || (actualInputs != null && actualInputs.Count > 0))
421                 {
422                     ActivityValidationServices.ValidateRootInputs(this.WorkflowDefinition, actualInputs);
423                 }
424
425                 this.executor.ScheduleRootActivity(this.WorkflowDefinition, actualInputs, workflowExecutionProperties);
426             }
427             else
428             {
429                 this.executor.OnDeserialized(this.WorkflowDefinition, this);
430             }
431
432             this.executor.Open(this.SynchronizationContext);
433             this.controller = new WorkflowInstanceControl(this, this.executor);
434             this.isInitialized = true;
435
436             if (this.extensions != null && this.extensions.HasWorkflowInstanceExtensions)
437             {
438                 WorkflowInstanceProxy proxy = new WorkflowInstanceProxy(this);
439
440                 for (int i = 0; i < this.extensions.WorkflowInstanceExtensions.Count; i++)
441                 {
442                     IWorkflowInstanceExtension extension = this.extensions.WorkflowInstanceExtensions[i];
443                     extension.SetInstance(proxy);
444                 }
445             }
446         }
447
448         protected void ThrowIfReadOnly()
449         {
450             if (this.isInitialized)
451             {
452                 throw FxTrace.Exception.AsError(new InvalidOperationException(SR.WorkflowInstanceIsReadOnly(this.Id)));
453             }
454         }
455
456         protected internal abstract IAsyncResult OnBeginResumeBookmark(Bookmark bookmark, object value, TimeSpan timeout, AsyncCallback callback, object state);
457         protected internal abstract BookmarkResumptionResult OnEndResumeBookmark(IAsyncResult result);
458
459         protected internal abstract IAsyncResult OnBeginPersist(AsyncCallback callback, object state);
460         protected internal abstract void OnEndPersist(IAsyncResult result);
461
462         protected internal abstract void OnDisassociateKeys(ICollection<InstanceKey> keys);
463
464         protected internal abstract IAsyncResult OnBeginAssociateKeys(ICollection<InstanceKey> keys, AsyncCallback callback, object state);
465         protected internal abstract void OnEndAssociateKeys(IAsyncResult result);
466
467         internal IAsyncResult BeginFlushTrackingRecordsInternal(AsyncCallback callback, object state)
468         {
469             return OnBeginFlushTrackingRecords(callback, state);
470         }
471
472         internal void EndFlushTrackingRecordsInternal(IAsyncResult result)
473         {
474             OnEndFlushTrackingRecords(result);
475         }
476
477         protected void FlushTrackingRecords(TimeSpan timeout)
478         {
479             if (this.HasTrackingParticipant)
480             {
481                 this.TrackingProvider.FlushPendingRecords(timeout);
482             }
483         }
484
485         protected IAsyncResult BeginFlushTrackingRecords(TimeSpan timeout, AsyncCallback callback, object state)
486         {
487             if (this.HasTrackingParticipant)
488             {
489                 return this.TrackingProvider.BeginFlushPendingRecords(timeout, callback, state);
490             }
491             else
492             {
493                 return new CompletedAsyncResult(callback, state);
494             }
495         }
496
497         protected void EndFlushTrackingRecords(IAsyncResult result)
498         {
499             if (this.HasTrackingParticipant)
500             {
501                 this.TrackingProvider.EndFlushPendingRecords(result);
502             }
503             else
504             {
505                 CompletedAsyncResult.End(result);
506             }
507         }
508
509         protected virtual IAsyncResult OnBeginFlushTrackingRecords(AsyncCallback callback, object state)
510         {
511             return this.Controller.BeginFlushTrackingRecords(ActivityDefaults.TrackingTimeout, callback, state);
512         }
513
514         protected virtual void OnEndFlushTrackingRecords(IAsyncResult result)
515         {
516             this.Controller.EndFlushTrackingRecords(result);
517         }
518
519         internal void NotifyPaused()
520         {
521             if (this.executor.State != ActivityInstanceState.Executing)
522             {
523                 TrackCompletion();
524             }
525
526             OnNotifyPaused();
527         }
528
529         protected abstract void OnNotifyPaused();
530
531         internal void NotifyUnhandledException(Exception exception, Activity source, string sourceInstanceId)
532         {
533             if (this.controller.TrackingEnabled)
534             {
535                 ActivityInfo faultSourceInfo = new ActivityInfo(source.DisplayName, source.Id, sourceInstanceId, source.GetType().FullName);
536                 this.controller.Track(new WorkflowInstanceUnhandledExceptionRecord(this.Id, this.WorkflowDefinition.DisplayName, faultSourceInfo, exception, this.DefinitionIdentity));
537             }
538
539             OnNotifyUnhandledException(exception, source, sourceInstanceId);
540         }
541
542         protected abstract void OnNotifyUnhandledException(Exception exception, Activity source, string sourceInstanceId);
543
544         protected internal abstract void OnRequestAbort(Exception reason);
545
546         internal void OnDeserialized(bool hasTrackedStarted)
547         {
548             this.HasTrackedStarted = hasTrackedStarted;
549         }
550
551         void StartOperation(ref bool resetRequired)
552         {
553             StartReadOnlyOperation(ref resetRequired);
554
555             // isRunning can only flip to true by an operation and therefore
556             // we don't have to worry about this changing under us
557             if (this.executor.IsRunning)
558             {
559                 throw FxTrace.Exception.AsError(new InvalidOperationException(SR.RuntimeRunning));
560             }
561         }
562
563         void StartReadOnlyOperation(ref bool resetRequired)
564         {
565             bool wasPerformingOperation = false;
566             try
567             {
568             }
569             finally
570             {
571                 wasPerformingOperation = Interlocked.CompareExchange(ref this.isPerformingOperation, True, False) == True;
572
573                 if (!wasPerformingOperation)
574                 {
575                     resetRequired = true;
576                 }
577             }
578
579             if (wasPerformingOperation)
580             {
581                 throw FxTrace.Exception.AsError(new InvalidOperationException(SR.RuntimeOperationInProgress));
582             }
583         }
584
585         void FinishOperation(ref bool resetRequired)
586         {
587             if (resetRequired)
588             {
589                 this.isPerformingOperation = False;
590             }
591         }
592
593         internal void Abort(Exception reason)
594         {
595             if (!this.isAborted)
596             {
597                 this.isAborted = true;
598                 if (reason != null)
599                 {
600                     this.abortedException = reason;
601                 }
602
603                 if (this.extensions != null)
604                 {
605                     this.extensions.Cancel();
606                 }
607
608                 if (this.controller.TrackingEnabled)
609                 {
610                     // During abort we only track this one record
611                     if (reason != null)
612                     {
613                         string message = reason.Message;
614                         if (reason.InnerException != null)
615                         {
616                             message = SR.WorkflowAbortedReason(reason.Message, reason.InnerException.Message);
617                         }
618                         this.controller.Track(new WorkflowInstanceAbortedRecord(this.Id, this.WorkflowDefinition.DisplayName, message, this.DefinitionIdentity));
619                     }
620                 }
621 #if DEBUG
622                 if (!Fx.FastDebug)
623                 {
624                     if (reason != null)
625                     {
626                         reason.ToString();
627                     }
628                     this.abortStack = new StackTrace();
629                 }
630 #endif
631             }
632         }
633
634         void ValidatePrepareForSerialization()
635         {
636             ThrowIfAborted();
637             if (!this.Controller.IsPersistable)
638             {
639                 throw FxTrace.Exception.AsError(new InvalidOperationException(SR.PrepareForSerializationRequiresPersistability));
640             }
641         }
642
643         void ValidateScheduleResumeBookmark()
644         {
645             ThrowIfAborted();
646             ThrowIfNotIdle();
647         }
648
649         void ValidateGetBookmarks()
650         {
651             ThrowIfAborted();
652         }
653
654         void ValidateGetMappedVariables()
655         {
656             ThrowIfAborted();
657         }
658
659         void ValidatePauseWhenPersistable()
660         {
661             ThrowIfAborted();
662             if (this.Controller.IsPersistable)
663             {
664                 throw FxTrace.Exception.AsError(new InvalidOperationException(SR.PauseWhenPersistableInvalidIfPersistable));
665             }
666         }
667
668         void Terminate(Exception reason)
669         {
670             // validate we're in an ok state
671             ThrowIfAborted();
672
673             // terminate the runtime
674             this.executor.Terminate(reason);
675
676             // and track if necessary
677             TrackCompletion();
678
679         }
680
681         void TrackCompletion()
682         {
683             if (this.controller.TrackingEnabled && !this.hasTrackedCompletion)
684             {
685                 ActivityInstanceState completionState = this.executor.State;
686
687                 if (completionState == ActivityInstanceState.Faulted)
688                 {
689                     Fx.Assert(this.executor.TerminationException != null, "must have a termination exception if we're faulted");
690                     this.controller.Track(new WorkflowInstanceTerminatedRecord(this.Id, this.WorkflowDefinition.DisplayName, this.executor.TerminationException.Message, this.DefinitionIdentity));
691                 }
692                 else if (completionState == ActivityInstanceState.Closed)
693                 {
694                     this.controller.Track(new WorkflowInstanceRecord(this.Id, this.WorkflowDefinition.DisplayName, WorkflowInstanceStates.Completed, this.DefinitionIdentity));
695                 }
696                 else
697                 {
698                     Fx.AssertAndThrow(completionState == ActivityInstanceState.Canceled, "Cannot be executing a workflow instance when WorkflowState was completed.");
699                     this.controller.Track(new WorkflowInstanceRecord(this.Id, this.WorkflowDefinition.DisplayName, WorkflowInstanceStates.Canceled, this.DefinitionIdentity));
700                 }
701                 this.hasTrackedCompletion = true;
702             }
703         }
704
705         void TrackResumed()
706         {
707             // track if necessary
708             if (!this.hasTrackedResumed)
709             {
710                 if (this.Controller.TrackingEnabled)
711                 {
712                     if (!this.HasTrackedStarted)
713                     {
714                         this.TrackingProvider.AddRecord(new WorkflowInstanceRecord(this.Id, this.WorkflowDefinition.DisplayName, WorkflowInstanceStates.Started, this.DefinitionIdentity));
715                         this.HasTrackedStarted = true;
716                     }
717                     else
718                     {
719                         this.TrackingProvider.AddRecord(new WorkflowInstanceRecord(this.Id, this.WorkflowDefinition.DisplayName, WorkflowInstanceStates.Resumed, this.DefinitionIdentity));
720                     }
721                 }
722                 this.hasTrackedResumed = true;
723             }
724         }
725
726         void Run()
727         {
728             // validate we're in an ok state
729             ThrowIfAborted();
730
731             TrackResumed();
732
733             // and let the scheduler go
734             this.executor.MarkSchedulerRunning();
735         }
736
737         void ScheduleCancel()
738         {
739             // validate we're in an ok state
740             ThrowIfAborted();
741
742             TrackResumed();
743
744             this.executor.CancelRootActivity();
745         }
746
747         BookmarkResumptionResult ScheduleBookmarkResumption(Bookmark bookmark, object value)
748         {
749             // validate we're in an ok state
750             ValidateScheduleResumeBookmark();
751
752             TrackResumed();
753
754             return this.executor.TryResumeHostBookmark(bookmark, value);
755         }
756
757         BookmarkResumptionResult ScheduleBookmarkResumption(Bookmark bookmark, object value, BookmarkScope scope)
758         {
759             // validate we're in an ok state
760             ValidateScheduleResumeBookmark();
761
762             TrackResumed();
763
764             return this.executor.TryResumeBookmark(bookmark, value, scope);
765         }
766
767
768         void ThrowIfAborted()
769         {
770             if (this.isAborted || (this.executor != null && this.executor.IsAbortPending))
771             {
772                 throw FxTrace.Exception.AsError(new InvalidOperationException(SR.WorkflowInstanceAborted(this.Id)));
773             }
774         }
775
776         void ThrowIfNotIdle()
777         {
778             if (!this.executor.IsIdle)
779             {
780                 throw FxTrace.Exception.AsError(new InvalidOperationException(SR.BookmarksOnlyResumableWhileIdle));
781             }
782         }
783
784         [SuppressMessage(FxCop.Category.Design, FxCop.Rule.NestedTypesShouldNotBeVisible,
785             Justification = "these are effectively protected methods, but encapsulated in a struct to avoid naming conflicts")]
786         protected struct WorkflowInstanceControl
787         {
788             ActivityExecutor executor;
789             WorkflowInstance instance;
790
791             internal WorkflowInstanceControl(WorkflowInstance instance, ActivityExecutor executor)
792             {
793                 this.instance = instance;
794                 this.executor = executor;
795             }
796
797             public bool IsPersistable
798             {
799                 get
800                 {
801                     return this.executor.IsPersistable;
802                 }
803             }
804
805             public bool HasPendingTrackingRecords
806             {
807                 get
808                 {
809                     return this.instance.HasTrackingParticipant && this.instance.TrackingProvider.HasPendingRecords;
810                 }
811             }
812
813             public bool TrackingEnabled
814             {
815                 get
816                 {
817                     return this.instance.HasTrackingParticipant && this.instance.TrackingProvider.ShouldTrackWorkflowInstanceRecords;
818                 }
819             }
820
821             public WorkflowInstanceState State
822             {
823                 get
824                 {
825                     WorkflowInstanceState result;
826
827                     if (this.instance.isAborted)
828                     {
829                         result = WorkflowInstanceState.Aborted;
830                     }
831                     else if (!this.executor.IsIdle)
832                     {
833                         result = WorkflowInstanceState.Runnable;
834                     }
835                     else
836                     {
837                         if (this.executor.State == ActivityInstanceState.Executing)
838                         {
839                             result = WorkflowInstanceState.Idle;
840                         }
841                         else
842                         {
843                             result = WorkflowInstanceState.Complete;
844                         }
845                     }
846
847                     return result;
848                 }
849             }
850
851             public override bool Equals(object obj)
852             {
853                 if (!(obj is WorkflowInstanceControl))
854                 {
855                     return false;
856                 }
857
858                 WorkflowInstanceControl other = (WorkflowInstanceControl)obj;
859                 return other.instance == this.instance;
860             }
861
862             public override int GetHashCode()
863             {
864                 return this.instance.GetHashCode();
865             }
866
867             public static bool operator ==(WorkflowInstanceControl left, WorkflowInstanceControl right)
868             {
869                 return left.Equals(right);
870             }
871
872             public static bool operator !=(WorkflowInstanceControl left, WorkflowInstanceControl right)
873             {
874                 return !left.Equals(right);
875             }
876
877             public ReadOnlyCollection<BookmarkInfo> GetBookmarks()
878             {
879                 bool resetRequired = false;
880
881                 try
882                 {
883                     this.instance.StartReadOnlyOperation(ref resetRequired);
884
885                     this.instance.ValidateGetBookmarks();
886
887                     return this.executor.GetAllBookmarks();
888                 }
889                 finally
890                 {
891                     this.instance.FinishOperation(ref resetRequired);
892                 }
893             }
894
895             public ReadOnlyCollection<BookmarkInfo> GetBookmarks(BookmarkScope scope)
896             {
897                 bool resetRequired = false;
898
899                 try
900                 {
901                     this.instance.StartReadOnlyOperation(ref resetRequired);
902
903                     this.instance.ValidateGetBookmarks();
904
905                     return this.executor.GetBookmarks(scope);
906                 }
907                 finally
908                 {
909                     this.instance.FinishOperation(ref resetRequired);
910                 }
911             }
912
913             public IDictionary<string, LocationInfo> GetMappedVariables()
914             {
915                 bool resetRequired = false;
916
917                 try
918                 {
919                     this.instance.StartReadOnlyOperation(ref resetRequired);
920
921                     this.instance.ValidateGetMappedVariables();
922
923                     IDictionary<string, LocationInfo> mappedLocations = this.instance.executor.GatherMappableVariables();
924                     if (mappedLocations != null)
925                     {
926                         mappedLocations = new ReadOnlyDictionaryInternal<string, LocationInfo>(mappedLocations);
927                     }
928                     else
929                     {
930                         mappedLocations = WorkflowInstance.EmptyMappedVariablesDictionary;
931                     }
932                     return mappedLocations;
933                 }
934                 finally
935                 {
936                     this.instance.FinishOperation(ref resetRequired);
937                 }
938             }
939
940             public void Run()
941             {
942                 bool resetRequired = false;
943
944                 try
945                 {
946                     this.instance.StartOperation(ref resetRequired);
947
948                     this.instance.Run();
949                 }
950                 finally
951                 {
952                     this.instance.FinishOperation(ref resetRequired);
953                 }
954
955                 this.executor.Run();
956             }
957
958             public void RequestPause()
959             {
960                 // No validations for this because we do not
961                 // require calls to Pause to be synchronized
962                 // by the caller
963                 this.executor.PauseScheduler();
964             }
965
966             // Calls Pause when IsPersistable goes from false->true
967             public void PauseWhenPersistable()
968             {
969                 bool resetRequired = false;
970
971                 try
972                 {
973                     this.instance.StartOperation(ref resetRequired);
974
975                     this.instance.ValidatePauseWhenPersistable();
976
977                     this.executor.PauseWhenPersistable();
978                 }
979                 finally
980                 {
981                     this.instance.FinishOperation(ref resetRequired);
982                 }
983             }
984
985             public void ScheduleCancel()
986             {
987                 bool resetRequired = false;
988
989                 try
990                 {
991                     this.instance.StartOperation(ref resetRequired);
992
993                     this.instance.ScheduleCancel();
994                 }
995                 finally
996                 {
997                     this.instance.FinishOperation(ref resetRequired);
998                 }
999             }
1000
1001             public void Terminate(Exception reason)
1002             {
1003                 bool resetRequired = false;
1004
1005                 try
1006                 {
1007                     this.instance.StartOperation(ref resetRequired);
1008
1009                     this.instance.Terminate(reason);
1010                 }
1011                 finally
1012                 {
1013                     this.instance.FinishOperation(ref resetRequired);
1014                 }
1015             }
1016
1017             public BookmarkResumptionResult ScheduleBookmarkResumption(Bookmark bookmark, object value)
1018             {
1019                 bool resetRequired = false;
1020
1021                 try
1022                 {
1023                     this.instance.StartOperation(ref resetRequired);
1024
1025                     return this.instance.ScheduleBookmarkResumption(bookmark, value);
1026                 }
1027                 finally
1028                 {
1029                     this.instance.FinishOperation(ref resetRequired);
1030                 }
1031             }
1032
1033             public BookmarkResumptionResult ScheduleBookmarkResumption(Bookmark bookmark, object value, BookmarkScope scope)
1034             {
1035                 bool resetRequired = false;
1036
1037                 try
1038                 {
1039                     this.instance.StartOperation(ref resetRequired);
1040
1041                     return this.instance.ScheduleBookmarkResumption(bookmark, value, scope);
1042                 }
1043                 finally
1044                 {
1045                     this.instance.FinishOperation(ref resetRequired);
1046                 }
1047             }
1048
1049             public void Abort()
1050             {
1051                 bool resetRequired = false;
1052
1053                 try
1054                 {
1055                     this.instance.StartOperation(ref resetRequired);
1056
1057                     // No validations
1058
1059                     this.executor.Dispose();
1060
1061                     this.instance.Abort(null);
1062                 }
1063                 finally
1064                 {
1065                     this.instance.FinishOperation(ref resetRequired);
1066                 }
1067             }
1068
1069             public void Abort(Exception reason)
1070             {
1071                 bool resetRequired = false;
1072
1073                 try
1074                 {
1075                     this.instance.StartOperation(ref resetRequired);
1076
1077                     // No validations
1078
1079                     this.executor.Abort(reason);
1080
1081                     this.instance.Abort(reason);
1082                 }
1083                 finally
1084                 {
1085                     this.instance.FinishOperation(ref resetRequired);
1086                 }
1087             }
1088
1089             [SuppressMessage(FxCop.Category.Design, FxCop.Rule.ConsiderPassingBaseTypesAsParameters,
1090                 Justification = "Only want to allow WorkflowInstanceRecord subclasses for WorkflowInstance-level tracking")]
1091             public void Track(WorkflowInstanceRecord instanceRecord)
1092             {
1093                 if (this.instance.HasTrackingParticipant)
1094                 {
1095                     this.instance.TrackingProvider.AddRecord(instanceRecord);
1096                 }
1097             }
1098
1099             public void FlushTrackingRecords(TimeSpan timeout)
1100             {
1101                 this.instance.FlushTrackingRecords(timeout);
1102             }
1103
1104             public IAsyncResult BeginFlushTrackingRecords(TimeSpan timeout, AsyncCallback callback, object state)
1105             {
1106                 return this.instance.BeginFlushTrackingRecords(timeout, callback, state);
1107             }
1108
1109             public void EndFlushTrackingRecords(IAsyncResult result)
1110             {
1111                 this.instance.EndFlushTrackingRecords(result);
1112             }
1113
1114             public object PrepareForSerialization()
1115             {
1116                 bool resetRequired = false;
1117
1118                 try
1119                 {
1120                     this.instance.StartReadOnlyOperation(ref resetRequired);
1121
1122                     this.instance.ValidatePrepareForSerialization();
1123
1124                     return this.executor.PrepareForSerialization();
1125                 }
1126                 finally
1127                 {
1128                     this.instance.FinishOperation(ref resetRequired);
1129                 }
1130             }
1131
1132             public ActivityInstanceState GetCompletionState()
1133             {
1134                 return this.executor.State;
1135             }
1136
1137             [SuppressMessage(FxCop.Category.Design, FxCop.Rule.AvoidOutParameters,
1138                 Justification = "Arch approved design. Requires the out argument for extra information provided")]
1139             public ActivityInstanceState GetCompletionState(out Exception terminationException)
1140             {
1141                 terminationException = this.executor.TerminationException;
1142                 return this.executor.State;
1143             }
1144
1145             [SuppressMessage(FxCop.Category.Design, FxCop.Rule.AvoidOutParameters,
1146                 Justification = "Arch approved design. Requires the out argument for extra information provided")]
1147             public ActivityInstanceState GetCompletionState(out IDictionary<string, object> outputs, out Exception terminationException)
1148             {
1149                 outputs = this.executor.WorkflowOutputs;
1150                 terminationException = this.executor.TerminationException;
1151                 return this.executor.State;
1152             }
1153
1154             public Exception GetAbortReason()
1155             {
1156                 return this.instance.abortedException;
1157             }
1158         }
1159     }
1160 }