/// <summary>
        /// Schedules the next enabled operation.
        /// </summary>
        internal void ScheduleNextEnabledOperation()
        {
            int?taskId = Task.CurrentId;

            // TODO: figure out if this check is still needed.
            // If the caller is the root task, then return.
            if (taskId != null && taskId == this.Runtime.RootTaskId)
            {
                return;
            }

            AsyncOperation current = this.ScheduledOperation;

            if (!this.IsRunning)
            {
                // TODO: check if this stop is needed.
                this.Stop();

                if (current.Status != AsyncOperationStatus.Completed)
                {
                    // If scheduler is not running, throw exception to force terminate the current operation.
                    throw new ExecutionCanceledException();
                }
            }

            if (current.Status != AsyncOperationStatus.Completed)
            {
                // Checks if concurrency not controlled by the runtime was used.
                this.CheckNoExternalConcurrencyUsed();
            }

            // Checks if the scheduling steps bound has been reached.
            this.CheckIfSchedulingStepsBoundIsReached();

            if (this.Configuration.IsProgramStateHashingEnabled)
            {
                // Update the current operation with the hashed program state.
                current.HashedProgramState = this.Runtime.GetProgramState();
            }

            // Get and order the operations by their id.
            var ops = this.OperationMap.Values.OrderBy(op => op.Id);

            // Try enable any operation that is currently waiting, but has its dependencies already satisfied.
            foreach (var op in ops)
            {
                if (op is AsyncOperation machineOp)
                {
                    machineOp.TryEnable();
                    IO.Debug.WriteLine("<ScheduleDebug> Operation '{0}' has status '{1}'.", op.Id, op.Status);
                }
            }

            if (!this.Strategy.GetNextOperation(current, ops, out IAsyncOperation next))
            {
                // Checks if the program has deadlocked.
                this.CheckIfProgramHasDeadlocked(ops.Select(op => op as AsyncOperation));

                IO.Debug.WriteLine("<ScheduleDebug> Schedule explored.");
                this.HasFullyExploredSchedule = true;
                this.Stop();

                if (current.Status != AsyncOperationStatus.Completed)
                {
                    // The schedule is explored so throw exception to force terminate the current operation.
                    throw new ExecutionCanceledException();
                }
            }

            this.ScheduledOperation = next as AsyncOperation;
            this.ScheduleTrace.AddSchedulingChoice(next.Id);

            IO.Debug.WriteLine($"<ScheduleDebug> Scheduling the next operation of '{next.Name}'.");

            if (current != next)
            {
                current.IsActive = false;
                lock (next)
                {
                    this.ScheduledOperation.IsActive = true;
                    System.Threading.Monitor.PulseAll(next);
                }

                lock (current)
                {
                    if (!current.IsHandlerRunning)
                    {
                        return;
                    }

                    if (!this.ControlledTaskMap.ContainsKey(Task.CurrentId.Value))
                    {
                        this.ControlledTaskMap.TryAdd(Task.CurrentId.Value, current);
                        IO.Debug.WriteLine("<ScheduleDebug> Operation '{0}' is associated with task '{1}'.", current.Id, Task.CurrentId);
                    }

                    while (!current.IsActive)
                    {
                        IO.Debug.WriteLine("<ScheduleDebug> Sleeping the operation of '{0}' on task '{1}'.", current.Name, Task.CurrentId);
                        System.Threading.Monitor.Wait(current);
                        IO.Debug.WriteLine("<ScheduleDebug> Waking up the operation of '{0}' on task '{1}'.", current.Name, Task.CurrentId);
                    }

                    if (current.Status != AsyncOperationStatus.Enabled)
                    {
                        throw new ExecutionCanceledException();
                    }
                }
            }
        }
 /// <summary>
 /// Schedules the specified asynchronous operation to execute on the task with the given id.
 /// </summary>
 /// <param name="op">The operation to schedule.</param>
 /// <param name="taskId">The id of the task to be used to execute the operation.</param>
 internal void ScheduleOperation(AsyncOperation op, int taskId)
 {
     IO.Debug.WriteLine($"<ScheduleDebug> Scheduling operation '{op.Name}' to execute on task '{taskId}'.");
     this.ControlledTaskMap.TryAdd(taskId, op);
 }
Exemple #3
0
        /// <summary>
        /// Tries to get the next enabled operation to schedule.
        /// </summary>
        private bool TryGetNextEnabledOperation(AsyncOperation current, bool isYielding, out AsyncOperation next)
        {
            // Get and order the operations by their id.
            var ops = this.OperationMap.Values.OrderBy(op => op.Id);

            // The scheduler might need to retry choosing a next operation in the presence of uncontrolled
            // concurrency, as explained below. In this case, we implement a simple retry logic.
            int retries = 0;

            do
            {
                // Enable any blocked operation that has its dependencies already satisfied.
                IO.Debug.WriteLine("<ScheduleDebug> Enabling any blocked operation with satisfied dependencies.");
                foreach (var op in ops)
                {
                    this.TryEnableOperation(op);
                    IO.Debug.WriteLine("<ScheduleDebug> Operation '{0}' has status '{1}'.", op.Id, op.Status);
                }

                // Choose the next operation to schedule, if there is one enabled.
                if (!this.Strategy.GetNextOperation(ops, current, isYielding, out next) &&
                    this.Configuration.IsPartiallyControlledTestingEnabled &&
                    ops.Any(op => op.IsBlockedOnUncontrolledDependency()))
                {
                    // At least one operation is blocked due to uncontrolled concurrency. To try defend against
                    // this, retry after an asynchronous delay to give some time to the dependency to complete.
                    Task.Run(async() =>
                    {
                        await Task.Delay(10);
                        lock (this.SyncObject)
                        {
                            Monitor.PulseAll(this.SyncObject);
                        }
                    });

                    // Pause the current operation until the scheduler retries.
                    Monitor.Wait(this.SyncObject);
                    retries++;
                    continue;
                }

                break;
            }while (retries < 5);

            if (next is null)
            {
                // Check if the execution has deadlocked.
                this.Runtime.CheckIfExecutionHasDeadlocked(ops);
                return(false);
            }

            return(true);
        }