/// <summary> /// Initializes a new instance of the <see cref="OperationScheduler"/> class. /// </summary> internal OperationScheduler(ControlledRuntime runtime, ISchedulingStrategy strategy, ScheduleTrace trace, Configuration configuration) { this.Configuration = configuration; this.Runtime = runtime; this.Strategy = strategy; this.OperationMap = new Dictionary <ulong, AsyncOperation>(); this.ScheduleTrace = trace; this.SyncObject = new object(); this.CompletionSource = new TaskCompletionSource <bool>(); this.IsAttached = true; this.BugFound = false; this.HasFullyExploredSchedule = false; }
/// <summary> /// Constructs a reproducable trace. /// </summary> private void ConstructReproducableTrace(ControlledRuntime runtime) { StringBuilder stringBuilder = new StringBuilder(); if (this.Strategy.IsFair()) { stringBuilder.Append("--fair-scheduling").Append(Environment.NewLine); } if (this.Configuration.IsLivenessCheckingEnabled) { stringBuilder.Append("--liveness-temperature-threshold:" + this.Configuration.LivenessTemperatureThreshold). Append(Environment.NewLine); } if (!string.IsNullOrEmpty(this.Configuration.TestMethodName)) { stringBuilder.Append("--test-method:" + this.Configuration.TestMethodName). Append(Environment.NewLine); } for (int idx = 0; idx < runtime.Scheduler.ScheduleTrace.Count; idx++) { ScheduleStep step = runtime.Scheduler.ScheduleTrace[idx]; if (step.Type == ScheduleStepType.SchedulingChoice) { stringBuilder.Append($"({step.ScheduledOperationId})"); } else if (step.BooleanChoice != null) { stringBuilder.Append(step.BooleanChoice.Value); } else { stringBuilder.Append(step.IntegerChoice.Value); } if (idx < runtime.Scheduler.ScheduleTrace.Count - 1) { stringBuilder.Append(Environment.NewLine); } } this.ReproducableTrace = stringBuilder.ToString(); }
/// <summary> /// Gathers the exploration strategy statistics from the specified runtimne. /// </summary> private void GatherTestingStatistics(ControlledRuntime runtime) { TestReport report = runtime.Scheduler.GetReport(); if (this.Configuration.ReportActivityCoverage) { report.CoverageInfo.CoverageGraph = this.Graph; } var coverageInfo = runtime.GetCoverageInfo(); report.CoverageInfo.Merge(coverageInfo); this.TestReport.Merge(report); // Also save the graph snapshot of the last iteration, if there is one. this.Graph = coverageInfo.CoverageGraph; }
/// <summary> /// Take care of handling the <see cref="Configuration"/> settings for <see cref="Configuration.CustomActorRuntimeLogType"/>, /// <see cref="Configuration.IsDgmlGraphEnabled"/>, and <see cref="Configuration.ReportActivityCoverage"/> by setting up the /// LogWriters on the given <see cref="ControlledRuntime"/> object. /// </summary> private void InitializeCustomLogging(ControlledRuntime runtime) { if (!string.IsNullOrEmpty(this.Configuration.CustomActorRuntimeLogType)) { var log = this.Activate <IActorRuntimeLog>(this.Configuration.CustomActorRuntimeLogType); if (log != null) { runtime.RegisterLog(log); } } if (this.Configuration.IsDgmlGraphEnabled || this.Configuration.ReportActivityCoverage) { // Registers an activity coverage graph builder. runtime.RegisterLog(new ActorRuntimeLogGraphBuilder(false) { CollapseMachineInstances = this.Configuration.ReportActivityCoverage }); } if (this.Configuration.ReportActivityCoverage) { // Need this additional logger to get the event coverage report correct runtime.RegisterLog(new ActorRuntimeLogEventCoverage()); } if (this.Configuration.IsXmlLogEnabled) { this.XmlLog = new StringBuilder(); runtime.RegisterLog(new ActorRuntimeLogXmlFormatter(XmlWriter.Create(this.XmlLog, new XmlWriterSettings() { Indent = true, IndentChars = " ", OmitXmlDeclaration = true }))); } }
/// <summary> /// Runs the next testing iteration. /// </summary> private void RunNextIteration(int iteration) { if (!this.IsReplayModeEnabled && this.ShouldPrintIteration(iteration + 1)) { this.Logger.WriteLine($"..... Iteration #{iteration + 1}"); // Flush when logging to console. if (this.Logger is ConsoleLogger) { Console.Out.Flush(); } } // Runtime used to serialize and test the program in this iteration. ControlledRuntime runtime = null; // Logger used to intercept the program output if no custom logger // is installed and if verbosity is turned off. InMemoryLogger runtimeLogger = null; // Gets a handle to the standard output and error streams. var stdOut = Console.Out; var stdErr = Console.Error; try { // Creates a new instance of the controlled runtime. runtime = new ControlledRuntime(this.Configuration, this.Strategy, this.RandomValueGenerator); // If verbosity is turned off, then intercept the program log, and also redirect // the standard output and error streams to a nul logger. if (!this.Configuration.IsVerbose) { runtimeLogger = new InMemoryLogger(); runtime.SetLogger(runtimeLogger); var writer = TextWriter.Null; Console.SetOut(writer); Console.SetError(writer); } this.InitializeCustomLogging(runtime); // Runs the test and waits for it to terminate. runtime.RunTest(this.TestMethodInfo.Method, this.TestMethodInfo.Name); runtime.WaitAsync().Wait(); // Invokes the user-specified iteration disposal method. this.TestMethodInfo.DisposeCurrentIteration(); // Invoke the per iteration callbacks, if any. foreach (var callback in this.PerIterationCallbacks) { callback(iteration); } // Checks that no monitor is in a hot state at termination. Only // checked if no safety property violations have been found. if (!runtime.Scheduler.BugFound) { runtime.CheckNoMonitorInHotStateAtTermination(); } if (runtime.Scheduler.BugFound) { this.ErrorReporter.WriteErrorLine(runtime.Scheduler.BugReport); } runtime.LogWriter.LogCompletion(); this.GatherTestingStatistics(runtime); if (!this.IsReplayModeEnabled && this.TestReport.NumOfFoundBugs > 0) { if (runtimeLogger != null) { this.ReadableTrace = runtimeLogger.ToString(); this.ReadableTrace += this.TestReport.GetText(this.Configuration, "<StrategyLog>"); } this.ConstructReproducableTrace(runtime); } } finally { if (!this.Configuration.IsVerbose) { // Restores the standard output and error streams. Console.SetOut(stdOut); Console.SetError(stdErr); } if (!this.IsReplayModeEnabled && this.Configuration.PerformFullExploration && runtime.Scheduler.BugFound) { this.Logger.WriteLine($"..... Iteration #{iteration + 1} " + $"triggered bug #{this.TestReport.NumOfFoundBugs} " + $"[task-{this.Configuration.TestingProcessId}]"); } // Cleans up the runtime before the next iteration starts. runtimeLogger?.Dispose(); runtime?.Dispose(); } }
/// <summary> /// Initializes a new instance of the <see cref="TaskController"/> class. /// </summary> internal TaskController(ControlledRuntime runtime, OperationScheduler scheduler) { this.Runtime = runtime; this.Scheduler = scheduler; }
/// <summary> /// Execute the (asynchronous) operation with the specified context. /// </summary> private TResult ExecuteOperation <TWork, TExecutor, TResult>(object state) { // Extract the expected operation context from the task state. var context = state as AsyncOperationContext <TWork, TExecutor, TResult> ?? state as OperationContext <TWork, TResult>; TaskOperation op = context.Operation; CancellationToken ct = context.CancellationToken; TResult result = default; Exception exception = null; // The operation execution logic uses two task completion sources: (1) an executor TCS and (2) a result TCS. // We do this to model the execution of tasks in the .NET runtime. For example, the `Task.Factory.StartNew` // method has different semantics from `Task.Run`, e.g. the returned task from `Task.Factory.StartNew(Func<T>)` // completes at the start of an asynchronous operation, so someone cannot await on it for the completion of // the operation. Instead, someone needs to first use `task.Unwrap()`, and then await on the unwrapped task. // To model this, the executor TCS completes at the start of the operation, and contains in its `task.AsyncState` // a reference to the result TCS. This approach allows us to implement `task.Unwrap` in a way that gives access // to the result TCS, which someone can then await for the asynchronous completion of the operation. try { // Update the current asynchronous control flow with the current runtime instance, // allowing future retrieval in the same asynchronous call stack. CoyoteRuntime.AssignAsyncControlFlowRuntime(this.Runtime); // Notify the scheduler that the operation started. This will yield execution until // the operation is ready to get scheduled. this.Scheduler.StartOperation(op); if (context is AsyncOperationContext <TWork, TExecutor, TResult> asyncContext) { // If the operation is asynchronous, then set the executor task completion source, which // can be used by `UnwrapTask` to unwrap and return the task executing this operation. this.SetTaskCompletionSource(asyncContext.ExecutorSource, asyncContext.Executor, null, ct); } if (context.Predecessor != null) { // If there is a predecessor task, then wait until the predecessor completes. ct.ThrowIfCancellationRequested(); op.OnWaitTask(context.Predecessor); } // Check if the operation must be canceled before starting the work. ct.ThrowIfCancellationRequested(); // Start executing the (asynchronous) work. Task executor = null; if (context.Work is Func <Task <TResult> > funcWithTaskResult) { executor = funcWithTaskResult(); } else if (context.Work is Func <Task> funcWithTask) { executor = funcWithTask(); } else if (context.Work is Func <CoyoteTasks.Task <TResult> > funcWithCoyoteTaskResult) { // TODO: temporary until we remove the custom task type. executor = funcWithCoyoteTaskResult().UncontrolledTask; } else if (context.Work is Func <CoyoteTasks.Task> funcWithCoyoteTask) { // TODO: temporary until we remove the custom task type. executor = funcWithCoyoteTask().UncontrolledTask; } else if (context.Work is Func <TResult> func) { result = func(); } else { throw new NotSupportedException($"Unable to execute work with unsupported type {context.Work.GetType()}."); } if (executor != null) { // If the work is asynchronous, then wait until it completes. this.OnWaitTask(op.Id, executor); if (executor.IsFaulted) { // Propagate the failing exception by rethrowing it. ExceptionDispatchInfo.Capture(executor.Exception).Throw(); } else if (executor.IsCanceled) { if (op.Exception != null) { // An exception has been already captured, so propagate it. ExceptionDispatchInfo.Capture(op.Exception).Throw(); } else { // Wait the canceled executor (which is non-blocking as it has already completed) // to throw the generated `OperationCanceledException`. executor.Wait(); } } // Safely get the result without blocking as the work has completed. result = executor is Task <TResult> resultTask ? resultTask.Result : executor is TResult r ? r : default; } } catch (Exception ex) { // Unwrap and cache the exception to propagate it. exception = ControlledRuntime.UnwrapException(ex); this.ReportThrownException(exception); } finally { IO.Debug.WriteLine("<ScheduleDebug> Completed operation '{0}' on task '{1}'.", op.Name, Task.CurrentId); op.OnCompleted(); // Set the result task completion source to notify to the awaiters that the operation // has been completed, and schedule the next enabled operation. this.SetTaskCompletionSource(context.ResultSource, result, exception, default); this.Scheduler.ScheduleNextOperation(); } return(result); }
/// <summary> /// Execute the operation with the specified context. /// </summary> private void ExecuteOperation(object state) { // Extract the expected operation context from the task state. var context = state as OperationContext <Action, object>; TaskOperation op = context.Operation; CancellationToken ct = context.CancellationToken; Exception exception = null; try { // Update the current asynchronous control flow with the current runtime instance, // allowing future retrieval in the same asynchronous call stack. CoyoteRuntime.AssignAsyncControlFlowRuntime(this.Runtime); // Notify the scheduler that the operation started. This will yield execution until // the operation is ready to get scheduled. this.Scheduler.StartOperation(op); if (context.Predecessor != null) { // If there is a predecessor task, then wait until the predecessor completes. ct.ThrowIfCancellationRequested(); op.OnWaitTask(context.Predecessor); } if (context.Options.HasFlag(OperationExecutionOptions.YieldAtStart)) { // Try yield execution to the next operation. this.Scheduler.ScheduleNextOperation(true); } // Check if the operation must be canceled before starting the work. ct.ThrowIfCancellationRequested(); // Start executing the work. context.Work(); } catch (Exception ex) { if (context.Options.HasFlag(OperationExecutionOptions.FailOnException)) { this.Assert(false, "Unhandled exception. {0}", ex); } else { // Unwrap and cache the exception to propagate it. exception = ControlledRuntime.UnwrapException(ex); this.ReportThrownException(exception); } } finally { IO.Debug.WriteLine("<ScheduleDebug> Completed operation '{0}' on task '{1}'.", op.Name, Task.CurrentId); op.OnCompleted(); // Set the result task completion source to notify to the awaiters that the operation // has been completed, and schedule the next enabled operation. this.SetTaskCompletionSource(context.ResultSource, null, exception, default); this.Scheduler.ScheduleNextOperation(); } }
/// <summary> /// Initializes a new instance of the <see cref="Resource"/> class. /// </summary> internal Resource() { this.Runtime = ControlledRuntime.Current; this.AwaitingOperations = new HashSet <AsyncOperation>(); }