Example #1
0
 internal AsyncOperationContext(TaskOperation op, TWork work, TExecutor executor, Task predecessor,
                                OperationExecutionOptions options, CancellationToken cancellationToken)
     : base(op, work, predecessor, options, cancellationToken)
 {
     this.Executor       = executor;
     this.ExecutorSource = new TaskCompletionSource <TExecutor>(this.ResultSource);
 }
Example #2
0
        /// <summary>
        /// Runs the specified test method.
        /// </summary>
        internal void RunTest(Delegate testMethod, string testName)
        {
            testName = string.IsNullOrEmpty(testName) ? string.Empty : $" '{testName}'";
            this.Logger.WriteLine($"<TestLog> Running test{testName}.");
            this.Assert(testMethod != null, "Unable to execute a null test method.");
            this.Assert(Task.CurrentId != null, "The test must execute inside a controlled task.");

            ulong operationId = this.GetNextOperationId();
            var   op          = new TaskOperation(operationId, this.Scheduler);

            this.Scheduler.RegisterOperation(op);
            op.OnEnabled();

            Task task = new Task(async() =>
            {
                try
                {
                    // Update the current asynchronous control flow with the current runtime instance,
                    // allowing future retrieval in the same asynchronous call stack.
                    AssignAsyncControlFlowRuntime(this);

                    OperationScheduler.StartOperation(op);

                    if (testMethod is Action <IActorRuntime> actionWithRuntime)
                    {
                        actionWithRuntime(this);
                    }
                    else if (testMethod is Action action)
                    {
                        action();
                    }
                    else if (testMethod is Func <IActorRuntime, CoyoteTasks.Task> functionWithRuntime)
                    {
                        await functionWithRuntime(this);
                    }
                    else if (testMethod is Func <CoyoteTasks.Task> function)
                    {
                        await function();
                    }
                    else
                    {
                        throw new InvalidOperationException($"Unsupported test delegate of type '{testMethod.GetType()}'.");
                    }

                    IO.Debug.WriteLine("<ScheduleDebug> Completed operation {0} on task '{1}'.", op.Name, Task.CurrentId);
                    op.OnCompleted();

                    // Task has completed, schedule the next enabled operation.
                    this.Scheduler.ScheduleNextEnabledOperation();
                }
                catch (Exception ex)
                {
                    this.ProcessUnhandledExceptionInOperation(op, ex);
                }
            });

            this.Scheduler.ScheduleOperation(op, task.Id);
            task.Start();
            this.Scheduler.WaitOperationStart(op);
        }
Example #3
0
 /// <summary>
 /// Schedules the specified task operation for execution.
 /// </summary>
 private void ScheduleTaskOperation(TaskOperation op, Task task)
 {
     IO.Debug.WriteLine("<CreateLog> Operation '{0}' was created to execute task '{1}'.", op.Name, task.Id);
     task.Start();
     this.Scheduler.WaitOperationStart(op);
     this.Scheduler.ScheduleNextOperation();
 }
Example #4
0
        /// <summary>
        /// Creates a new task operation.
        /// </summary>
        internal TaskOperation CreateTaskOperation()
        {
            ulong operationId = this.GetNextOperationId();
            var   op          = new TaskOperation(operationId, this.Scheduler);

            this.Scheduler.RegisterOperation(op);
            return(op);
        }
Example #5
0
        public CoyoteTasks.Task ScheduleAction(Action action, Task predecessor, CancellationToken cancellationToken)
        {
            // TODO: support cancellations during testing.
            this.Assert(action != null, "The task cannot execute a null action.");

            ulong operationId = this.Runtime.GetNextOperationId();
            var   op          = new TaskOperation(operationId, this.Scheduler);

            this.Scheduler.RegisterOperation(op);
            op.OnEnabled();

            var task = new Task(() =>
            {
                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);

                    OperationScheduler.StartOperation(op);
                    if (predecessor != null)
                    {
                        op.OnWaitTask(predecessor);
                    }

                    action();
                }
                catch (Exception ex)
                {
                    // Report the unhandled exception unless it is our ExecutionCanceledException which is our
                    // way of terminating async task operations at the end of the test iteration.
                    if (!(ex is ExecutionCanceledException))
                    {
                        this.ReportUnhandledExceptionInOperation(op, ex);
                    }

                    // and rethrow it
                    throw;
                }
                finally
                {
                    IO.Debug.WriteLine("<ScheduleDebug> Completed operation '{0}' on task '{1}'.", op.Name, Task.CurrentId);
                    op.OnCompleted();
                }
            });

            // Schedule a task continuation that will schedule the next enabled operation upon completion.
            task.ContinueWith(t => this.Scheduler.ScheduleNextEnabledOperation(), TaskScheduler.Current);

            IO.Debug.WriteLine("<CreateLog> Operation '{0}' was created to execute task '{1}'.", op.Name, task.Id);
            this.Scheduler.ScheduleOperation(op, task.Id);
            task.Start();
            this.Scheduler.WaitOperationStart(op);
            this.Scheduler.ScheduleNextEnabledOperation();

            return(new CoyoteTasks.Task(this, task));
        }
Example #6
0
        public CoyoteTasks.Task <TResult> ScheduleFunction <TResult>(Func <CoyoteTasks.Task <TResult> > function, Task predecessor,
                                                                     CancellationToken cancellationToken)
        {
            // TODO: support cancellations during testing.
            this.Assert(function != null, "The task cannot execute a null function.");

            ulong operationId = this.Runtime.GetNextOperationId();
            var   op          = new TaskOperation(operationId, this.Scheduler);

            this.Scheduler.RegisterOperation(op);
            op.OnEnabled();

            var task = new Task <Task <TResult> >(() =>
            {
                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);

                    OperationScheduler.StartOperation(op);
                    if (predecessor != null)
                    {
                        op.OnWaitTask(predecessor);
                    }

                    CoyoteTasks.Task <TResult> resultTask = function();
                    this.OnWaitTask(operationId, resultTask.UncontrolledTask);
                    return(resultTask.UncontrolledTask);
                }
                catch (Exception ex)
                {
                    // Report the unhandled exception and rethrow it.
                    this.ReportUnhandledExceptionInOperation(op, ex);
                    throw;
                }
                finally
                {
                    IO.Debug.WriteLine("<ScheduleDebug> Completed operation '{0}' on task '{1}'.", op.Name, Task.CurrentId);
                    op.OnCompleted();
                }
            });

            Task <TResult> innerTask = task.Unwrap();

            // Schedule a task continuation that will schedule the next enabled operation upon completion.
            innerTask.ContinueWith(t => this.Scheduler.ScheduleNextEnabledOperation(), TaskScheduler.Current);

            IO.Debug.WriteLine("<CreateLog> Operation '{0}' was created to execute task '{1}'.", op.Name, task.Id);
            this.Scheduler.ScheduleOperation(op, task.Id);
            task.Start();
            this.Scheduler.WaitOperationStart(op);
            this.Scheduler.ScheduleNextEnabledOperation();

            return(new CoyoteTasks.Task <TResult>(this, innerTask));
        }
Example #7
0
 internal OperationContext(TaskOperation op, TWork work, Task predecessor, OperationExecutionOptions options,
                           CancellationToken cancellationToken)
 {
     this.Operation         = op;
     this.Work              = work ?? throw new ArgumentNullException(nameof(work));
     this.Predecessor       = predecessor;
     this.ResultSource      = new TaskCompletionSource <TResult>();
     this.Options           = options;
     this.CancellationToken = cancellationToken;
 }
Example #8
0
        internal Task ScheduleAction(Action action, Task predecessor, bool isYield, bool failException, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();

            TaskOperation             op      = this.Runtime.CreateTaskOperation();
            OperationExecutionOptions options = OperationContext.CreateOperationExecutionOptions(failException, isYield);
            var context = new OperationContext <Action, object>(op, action, predecessor, options, cancellationToken);
            var task    = new Task(this.ExecuteOperation, context, cancellationToken);

            this.ScheduleTaskOperation(op, task);
            return(context.ResultSource.Task);
        }
Example #9
0
        internal Task <TResult> ScheduleFunction <TResult>(Func <TResult> function, Task predecessor, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();

            TaskOperation op      = this.Runtime.CreateTaskOperation();
            var           context = new OperationContext <Func <TResult>, TResult>(op, function, predecessor,
                                                                                   OperationExecutionOptions.None, cancellationToken);
            var task = new Task <TResult>(this.ExecuteOperation <Func <TResult>, TResult, TResult>, context, cancellationToken);

            this.ScheduleTaskOperation(op, task);
            return(context.ResultSource.Task);
        }
Example #10
0
        internal Task <Task> ScheduleFunction(Func <Task> function, Task predecessor, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();

            Task <Task>   task    = null;
            TaskOperation op      = this.Runtime.CreateTaskOperation();
            var           context = new AsyncOperationContext <Func <Task>, Task, Task>(op, function, task, predecessor,
                                                                                        OperationExecutionOptions.None, cancellationToken);

            task = new Task <Task>(this.ExecuteOperation <Func <Task>, Task, Task>, context, cancellationToken);

            this.ScheduleTaskOperation(op, task);
            return(context.ExecutorSource.Task);
        }
Example #11
0
        public CoyoteTasks.Task <TResult> ScheduleDelegate <TResult>(Delegate work, Task predecessor, CancellationToken cancellationToken)
        {
            // TODO: support cancellations during testing.
            this.Assert(work != null, "The task cannot execute a null delegate.");

            ulong operationId = this.Runtime.GetNextOperationId();
            var   op          = new TaskOperation(operationId, this.Scheduler);

            this.Scheduler.RegisterOperation(op);
            op.OnEnabled();

            var task = new Task <TResult>(() =>
            {
                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);

                    OperationScheduler.StartOperation(op);
                    if (predecessor != null)
                    {
                        op.OnWaitTask(predecessor);
                    }

                    if (work is Func <Task> funcWithTaskResult)
                    {
                        Task resultTask = funcWithTaskResult();
                        this.OnWaitTask(operationId, resultTask);
                        if (resultTask is TResult typedResultTask)
                        {
                            return(typedResultTask);
                        }
                    }
                    else if (work is Func <Task <TResult> > funcWithGenericTaskResult)
                    {
                        Task <TResult> resultTask = funcWithGenericTaskResult();
                        this.OnWaitTask(operationId, resultTask);
                        return(resultTask.Result);
                    }
                    else if (work is Func <TResult> funcWithGenericResult)
                    {
                        return(funcWithGenericResult());
                    }

                    return(default);
Example #12
0
        /// <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);
        }
Example #13
0
        /// <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();
            }
        }
Example #14
0
        public CoyoteTasks.Task<TResult> ScheduleDelegate<TResult>(Delegate work, Task predecessor, CancellationToken cancellationToken)
        {
            // TODO: support cancellations during testing.
            this.Assert(work != null, "The task cannot execute a null delegate.");

            ulong operationId = this.Runtime.GetNextOperationId();
            var op = new TaskOperation(operationId, this.Scheduler);
            this.Scheduler.RegisterOperation(op);
            op.OnEnabled();

            var task = new Task<TResult>(() =>
            {
                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);

                    OperationScheduler.StartOperation(op);
                    if (predecessor != null)
                    {
                        op.OnWaitTask(predecessor);
                    }

                    if (work is Func<Task> funcWithTaskResult)
                    {
                        Task resultTask = funcWithTaskResult();
                        this.OnWaitTask(operationId, resultTask);
                        if (resultTask is TResult typedResultTask)
                        {
                            return typedResultTask;
                        }
                    }
                    else if (work is Func<Task<TResult>> funcWithGenericTaskResult)
                    {
                        Task<TResult> resultTask = funcWithGenericTaskResult();
                        this.OnWaitTask(operationId, resultTask);
                        return resultTask.Result;
                    }
                    else if (work is Func<TResult> funcWithGenericResult)
                    {
                        return funcWithGenericResult();
                    }

                    return default;
                }
                catch (Exception ex)
                {
                    // Report the unhandled exception and rethrow it.
                    ReportUnhandledExceptionInOperation(op, ex);
                    throw;
                }
                finally
                {
                    IO.Debug.WriteLine("<ScheduleDebug> Completed operation '{0}' on task '{1}'.", op.Name, Task.CurrentId);
                    op.OnCompleted();
                }
            }, cancellationToken);

            // Schedule a task continuation that will schedule the next enabled operation upon completion.
            task.ContinueWith(t => this.Scheduler.ScheduleNextEnabledOperation(), TaskScheduler.Current);

            IO.Debug.WriteLine("<CreateLog> Operation '{0}' was created to execute task '{1}'.", op.Name, task.Id);
            this.Scheduler.ScheduleOperation(op, task.Id);
            task.Start();
            this.Scheduler.WaitOperationStart(op);
            this.Scheduler.ScheduleNextEnabledOperation();

            return new CoyoteTasks.Task<TResult>(this, task);
        }
Example #15
0
        internal void RunTest(Delegate testMethod, string testName)
        {
            testName = string.IsNullOrEmpty(testName) ? string.Empty : $" '{testName}'";
            this.Logger.WriteLine($"<TestLog> Running test{testName}.");
            this.Assert(testMethod != null, "Unable to execute a null test method.");
            this.Assert(Task.CurrentId != null, "The test must execute inside a controlled task.");

            TaskOperation op   = this.CreateTaskOperation();
            Task          task = new Task(() =>
            {
                try
                {
                    // Update the current asynchronous control flow with the current runtime instance,
                    // allowing future retrieval in the same asynchronous call stack.
                    AssignAsyncControlFlowRuntime(this);

                    this.Scheduler.StartOperation(op);

                    Task testMethodTask = null;
                    if (testMethod is Action <IActorRuntime> actionWithRuntime)
                    {
                        actionWithRuntime(this);
                    }
                    else if (testMethod is Action action)
                    {
                        action();
                    }
                    else if (testMethod is Func <IActorRuntime, Task> functionWithRuntime)
                    {
                        testMethodTask = functionWithRuntime(this);
                    }
                    else if (testMethod is Func <Task> function)
                    {
                        testMethodTask = function();
                    }
                    else if (testMethod is Func <IActorRuntime, CoyoteTasks.Task> functionWithRuntime2)
                    {
                        testMethodTask = functionWithRuntime2(this).UncontrolledTask;
                    }
                    else if (testMethod is Func <CoyoteTasks.Task> function2)
                    {
                        testMethodTask = function2().UncontrolledTask;
                    }
                    else
                    {
                        throw new InvalidOperationException($"Unsupported test delegate of type '{testMethod.GetType()}'.");
                    }

                    if (testMethodTask != null)
                    {
                        // The test method is asynchronous, so wait on the task to complete.
                        op.OnWaitTask(testMethodTask);
                        if (testMethodTask.Exception != null)
                        {
                            // The test method failed with an unhandled exception.
                            ExceptionDispatchInfo.Capture(testMethodTask.Exception).Throw();
                        }
                        else if (testMethodTask.IsCanceled)
                        {
                            throw new TaskCanceledException(testMethodTask);
                        }
                    }

                    IO.Debug.WriteLine("<ScheduleDebug> Completed operation {0} on task '{1}'.", op.Name, Task.CurrentId);
                    op.OnCompleted();

                    // Task has completed, schedule the next enabled operation, which terminates exploration.
                    this.Scheduler.ScheduleNextOperation();
                }
                catch (Exception ex)
                {
                    this.ProcessUnhandledExceptionInOperation(op, ex);
                }
            });

            task.Start();
            this.Scheduler.WaitOperationStart(op);
        }