// call from the threadProvider about the availability of a thread. internal void RunSome(object ignored) { using (ScheduleWork work = new ScheduleWork(this)) { using (new WorkflowTraceTransfer(this.InstanceId)) { using (new SchedulerLockGuard(this._schedulerLock, this)) { using (new ServiceEnvironment(this.rootActivity)) { // check if this is a valid in-memory instance if (!this.IsInstanceValid) return; // check if instance already done if ((this.rootActivity.ExecutionStatus == ActivityExecutionStatus.Closed) || (WorkflowStatus.Completed == this.WorkflowStatus) || (WorkflowStatus.Terminated == this.WorkflowStatus)) return; bool ignoreFinallyBlock = false; // // For V1 we don't support flow through transaction on the service thread using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Suppress)) { try { FireWorkflowExecutionEvent(this, WorkflowEventInternal.Executing); // run away ... run away... this.RunScheduler(); } catch (Exception e) { if (WorkflowExecutor.IsIrrecoverableException(e)) { ignoreFinallyBlock = true; throw; } else { WorkflowTrace.Runtime.TraceEvent(TraceEventType.Error, 0, "Workflow Runtime: WorkflowExecutor: Fatal exception thrown in the scheduler. Terminating the workflow instance '{0}'. Exception:{1}\n{2}", this.InstanceIdString, e.Message, e.StackTrace); this.TerminateOnIdle(WorkflowExecutor.GetNestedExceptionMessage(e)); this.ThrownException = e; } } finally { if (!ignoreFinallyBlock) { FireWorkflowExecutionEvent(this, WorkflowEventInternal.NotExecuting); } } scope.Complete(); } } } } } }
internal void Start() { using (ScheduleWork work = new ScheduleWork(this)) { using (this.ExecutorLock.Enter()) { if (this.WorkflowStatus != WorkflowStatus.Created) throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, ExecutionStringManager.CannotStartInstanceTwice, this.InstanceId)); // Set a new ServiceEnvironment to establish a current batch in TLS // This is needed for synchronous status change notification at start // (status init->executing) when there is no batch in TLS yet // and there are subscribers like tracking this.WorkflowStatus = WorkflowStatus.Running; using (new ServiceEnvironment(this.rootActivity)) { FireWorkflowExecutionEvent(this, WorkflowEventInternal.Starting); try { using (ActivityExecutionContext executionContext = new ActivityExecutionContext(this.rootActivity, true)) { // make sure the scheduler is able to run this.schedulingContext.CanRun = true; // Since we are actually scheduling work at this point, we should grab // the scheduler lock. This will avoid ----s some operations we schedule // start executing before we are done scheduling all operations. using (new SchedulerLockGuard(this._schedulerLock, this)) { executionContext.ExecuteActivity(this.rootActivity); } } } catch (Exception e) { Terminate(e.Message); throw; } FireWorkflowExecutionEvent(this, WorkflowEventInternal.Started); } } } }
internal void DeliverTimerSubscriptions() { using (ScheduleWork work = new ScheduleWork(this)) { using (this._executorLock.Enter()) { if (this.IsInstanceValid) { using (this.MessageDeliveryLock.Enter()) { using (new ServiceEnvironment(this.rootActivity)) { if (!this.IsInstanceValid) return; TimerEventSubscriptionCollection queue = TimerQueue; bool done = false; while (!done) { lock (queue.SyncRoot) { TimerEventSubscription sub = queue.Peek(); if (sub == null || sub.ExpiresAt > DateTime.UtcNow) { done = true; } else { WorkflowTrace.Host.TraceEvent(TraceEventType.Information, 0, "Delivering timer subscription for instance {0}", this.InstanceIdString); stateChangedSincePersistence = true; lock (qService.SyncRoot) { if (qService.Exists(sub.QueueName)) { qService.EnqueueEvent(sub.QueueName, sub.SubscriptionId); } } queue.Dequeue(); } } } } } } } } }
internal void Registered(bool isActivation) { using (ScheduleWork work = new ScheduleWork(this)) { this.Scheduler.ResumeIfRunnable(); } if (isActivation) FireWorkflowExecutionEvent(this, WorkflowEventInternal.Created); else FireWorkflowExecutionEvent(this, WorkflowEventInternal.Loaded); }
internal void ApplyWorkflowChanges(WorkflowChanges workflowChanges) { // Accessing InstanceId is not thread safe here! //WorkflowTrace.Runtime.TraceEvent(TraceEventType.Information, 0, "Workflow Runtime: WorkflowExecutor: Got a dynamic update request from outside for instance {0}", this.InstanceIdString); DiagnosticStackTrace("dynamic update request"); // check arguments if (workflowChanges == null) throw new ArgumentNullException("workflowChanges"); // check if this is a valid in-memory instance if (!this.IsInstanceValid) throw new InvalidOperationException(ExecutionStringManager.WorkflowNotValid); if (this.currentAtomicActivity != null) throw new InvalidOperationException(ExecutionStringManager.Error_InsideAtomicScope); try { using (ScheduleWork work = new ScheduleWork(this)) { // block other instance operations from outside using (this._executorLock.Enter()) { // check if this is a valid in-memory instance if (!this.IsInstanceValid) throw new InvalidOperationException(ExecutionStringManager.WorkflowNotValid); // get the instance to stop running this.Scheduler.CanRun = false; using (new SchedulerLockGuard(this._schedulerLock, this)) { using (new ServiceEnvironment(this.rootActivity)) { bool localSuspend = false; // check if this is a valid in-memory instance if (!this.IsInstanceValid) throw new InvalidOperationException(ExecutionStringManager.WorkflowNotValid); try { // check the status of the schedule switch (this.WorkflowStatus) { ////case ActivityExecutionStatus.Completed: // case WorkflowStatus.Completed: case WorkflowStatus.Terminated: throw new InvalidOperationException( ExecutionStringManager.InvalidOperationRequest); case WorkflowStatus.Suspended: // instance already suspended localSuspend = false; break; default: // suspend the instance this.SuspendOnIdle(null); localSuspend = true; break; } // apply the changes workflowChanges.ApplyTo(this.rootActivity); } finally { if (localSuspend) { // @undone: for now this will not return till the instance is done // Once Kumar has fixed 4335, we can enable this. this.ResumeOnIdle(true); } } } } // release lock on scheduler } } } catch (Exception e) { WorkflowTrace.Runtime.TraceEvent(TraceEventType.Error, 0, "Workflow Runtime: WorkflowExecutor: dynamic update attempt from outside on instance '{0}' threw an exception '{1}' at {2}", this.InstanceIdString, e.Message, e.StackTrace); throw; } }
// resumes the schedule instance sync // must be called only from outside the instance... the thread running the instance must // never call this method... it should call ResumeOnIdle instead. internal void Resume() { WorkflowTrace.Runtime.TraceEvent(TraceEventType.Information, 0, "Workflow Runtime: WorkflowExecutor: Got a resume request for instance {0}", this.InstanceIdString); try { // check if this is a valid in-memory instance if (!this.IsInstanceValid) throw new InvalidOperationException(ExecutionStringManager.WorkflowNotValid); using (ScheduleWork work = new ScheduleWork(this)) { // Stop threads from outside - message delivery and control operations using (this._executorLock.Enter()) { // check if this is a valid in-memory instance if (!this.IsInstanceValid) throw new InvalidOperationException(ExecutionStringManager.WorkflowNotValid); if ((this.WorkflowStatus != WorkflowStatus.Suspended)) return; using (new SchedulerLockGuard(this._schedulerLock, this)) { //@@Undone-- bmalhi there is one test in bat //which fails here. This check is right thing but im //commenting it out for bat. // Microsoft: this fails because when we load an instance into memory it grabs // the scheduler lock and starts running. By the time the user Resume request // gets the scheduler lock the instance is often done (the AbortBat test case scenario) // Balinder is attempting a fix to separate rehydration from resuming execution. /*if (!this.IsInstanceValid) throw new InvalidOperationException(ExecutionStringManager.WorkflowNotValid); */ using (new ServiceEnvironment(this.rootActivity)) { this.ResumeOnIdle(true); } } } } } catch (Exception e) { WorkflowTrace.Runtime.TraceEvent(TraceEventType.Error, 0, "Workflow Runtime: WorkflowExecutor: Resume attempt on instance '{0}' threw an exception '{1}' at {2}", this.InstanceIdString, e.Message, e.StackTrace); throw; } }
internal void EnqueueItemOnIdle(IComparable queueName, object item, IPendingWork pendingWork, Object workItem) { using (ScheduleWork work = new ScheduleWork(this)) { // prevent other control operations from outside using (this._executorLock.Enter()) { if (!this.IsInstanceValid) throw new InvalidOperationException(ExecutionStringManager.WorkflowNotValid); // take the msg delivery lock to make sure the instance // doesn't persist while the message is being delivered. using (InstanceLock.InstanceLockGuard messageDeliveryLockGuard = this.MessageDeliveryLock.Enter()) { using (new ServiceEnvironment(this.rootActivity)) { if (!this.IsInstanceValid) throw new InvalidOperationException(ExecutionStringManager.WorkflowNotValid); // Wait until the Scheduler is idle. while (!this.IsIdle) { messageDeliveryLockGuard.Wait(); if (!this.IsInstanceValid) throw new InvalidOperationException(ExecutionStringManager.WorkflowNotValid); } // At this point the scheduler is not running and it is // EnqueueItemOnIdle is not valid for suspended workflows if ((this.WorkflowStatus == WorkflowStatus.Suspended) || (!this.Scheduler.CanRun)) throw new InvalidOperationException(ExecutionStringManager.InvalidWaitForIdleOnSuspendedWorkflow); try { // add work items to the current batch if exists if (pendingWork != null) { IWorkBatch batch = (IWorkBatch)this.rootActivity.GetValue(WorkflowExecutor.TransientBatchProperty); batch.Add(pendingWork, workItem); } stateChangedSincePersistence = true; qService.EnqueueEvent(queueName, item); } finally { if (this.IsIdle) messageDeliveryLockGuard.Pulse(); } } } } } }
internal void EnqueueItem(IComparable queueName, object item, IPendingWork pendingWork, Object workItem) { using (ScheduleWork work = new ScheduleWork(this)) { bool lockedScheduler = false; if (!ServiceEnvironment.IsInServiceThread(InstanceId)) lockedScheduler = _schedulerLock.TryEnter(); try { // take the msg delivery lock to make sure the instance // doesn't persist while the message is being delivered. using (this.MessageDeliveryLock.Enter()) { if (!this.IsInstanceValid) throw new InvalidOperationException(ExecutionStringManager.WorkflowNotValid); if (lockedScheduler || ServiceEnvironment.IsInServiceThread(InstanceId)) { using (new ServiceEnvironment(this.RootActivity)) { qService.EnqueueEvent(queueName, item); } } else { if (qService.SafeEnqueueEvent(queueName, item)) { ScheduleWork.NeedsService = true; } } // add work items to the current batch if exists if (pendingWork != null) { IWorkBatch batch = _resourceManager.BatchCollection.GetBatch(this.rootActivity); batch.Add(pendingWork, workItem); } stateChangedSincePersistence = true; } } finally { if (lockedScheduler) _schedulerLock.Exit(); } } }