/// <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); }
/// <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); }