Esempio n. 1
0
        public void Error_WorkflowNonTaskEntryPoint()
        {
            // Workflow entry points methods need to return a Task.

            Assert.Throws <WorkflowTypeException>(() => StubManager.NewWorkflowStub <IErrorNonTaskEntryPointWorkflow1>(client));
            Assert.Throws <WorkflowTypeException>(() => StubManager.NewWorkflowStub <IErrorNonTaskEntryPointWorkflow2>(client));
        }
Esempio n. 2
0
        public void Error_WorkflowDuplicateDuplicateEntryPoints()
        {
            // Verify that we detect duplicate entrypoint methods
            // with explicit names.

            Assert.Throws <WorkflowTypeException>(() => StubManager.NewWorkflowStub <IDuplicateEntryPointsWorkflow>(client));
        }
        public void Error_ActivityDuplicateDuplicateDefaultEntryPoints()
        {
            // Verify that we detect duplicate entrypoint methods
            // with the default name.

            Assert.Throws <ActivityTypeException>(() => StubManager.CreateActivityStub <IDuplicateDefaultEntryPointsActivity>(client, new DummyWorkflow(), "my-activity"));
        }
Esempio n. 4
0
        public void Error_ActivityDuplicateDuplicateEntryPoints()
        {
            // Verify that we detect duplicate entrypoint methods
            // with explicit names.

            Assert.Throws <ActivityTypeException>(() => StubManager.NewActivityStub <IDuplicateEntryPointsActivity>(client, new DummyWorkflow().Workflow));
        }
Esempio n. 5
0
        public StubManagerTests()
        {
            var stubManagerOptions = new StubManagerOptions {
                AutoGenerateUnknown = true, AutoResolveByNaming = true
            };

            _stubManager = new StubManager(stubManagerOptions);
        }
Esempio n. 6
0
        /// <summary>
        /// Creates a typed workflow stub that can be used to start as well as
        /// query and signal the workflow via the type-safe interface methods.
        /// </summary>
        /// <typeparam name="TWorkflowInterface">Identifies the workflow interface.</typeparam>
        /// <param name="options">Optionally specifies the workflow options.</param>
        /// <param name="workflowTypeName">
        /// Optionally specifies the workflow type name by overriding the fully
        /// qualified <typeparamref name="TWorkflowInterface"/> type name or the name
        /// specified by a <see cref="WorkflowAttribute"/>.
        /// </param>
        /// <returns>The dynamically generated stub that implements the workflow methods defined by <typeparamref name="TWorkflowInterface"/>.</returns>
        /// <remarks>
        /// <para>
        /// Unlike activity stubs, a workflow stub may only be used to launch a single
        /// workflow.  You'll need to create a new stub for each workflow you wish to
        /// invoke and then the first method called on a workflow stub must be
        /// the one of the methods tagged by <see cref="WorkflowMethodAttribute"/>.
        /// </para>
        /// <note>
        /// <para>
        /// .NET and Java workflows can implement multiple workflow method using attributes
        /// and annotations to assign unique names to each.  Each workflow method is actually
        /// registered with Cadence as a distinct workflow type.  Workflow methods with a blank
        /// or <c>null</c> name will simply be registered using the workflow type name.
        /// </para>
        /// <para>
        /// Workflow methods with a name will be registered using a combination  of the workflow
        /// type name and the method name, using <b>"::"</b> as the separator, like:
        /// </para>
        /// <code>
        /// WORKFLOW-TYPENAME::METHOD-NAME
        /// </code>
        /// </note>
        /// </remarks>
        public TWorkflowInterface NewWorkflowStub <TWorkflowInterface>(WorkflowOptions options = null, string workflowTypeName = null)
            where TWorkflowInterface : class
        {
            CadenceHelper.ValidateWorkflowInterface(typeof(TWorkflowInterface));
            EnsureNotDisposed();

            return(StubManager.NewWorkflowStub <TWorkflowInterface>(this, options: options, workflowTypeName: workflowTypeName));
        }
Esempio n. 7
0
 public void Generate_ActivityEntryVoidWithOptions()
 {
     Assert.NotNull(StubManager.NewActivityStub <IActivityEntryVoidWithArgs>(client, new DummyWorkflow().Workflow, options: new ActivityOptions()
     {
         Domain = "my-domain"
     }));
     Assert.NotNull(StubManager.NewLocalActivityStub <IActivityEntryVoidWithArgs, ActivityEntryVoidWithArgs>(client, new DummyWorkflow().Workflow, options: new LocalActivityOptions()));
 }
Esempio n. 8
0
        /// <summary>
        /// Creates a typed workflow stub connected to a known workflow execution
        /// using IDs.  This can be used to signal and query the workflow via the
        /// type-safe interface methods.
        /// </summary>
        /// <typeparam name="TWorkflowInterface">Identifies the workflow interface.</typeparam>
        /// <param name="workflowId">Specifies the workflow ID.</param>
        /// <param name="runId">Optionally specifies the workflow's run ID.</param>
        /// <param name="domain">Optionally specifies a domain that </param>
        /// <returns>The dynamically generated stub that implements the workflow methods defined by <typeparamref name="TWorkflowInterface"/>.</returns>
        /// <remarks>
        /// Unlike activity stubs, a workflow stub may only be used to launch a single
        /// workflow.  You'll need to create a new stub for each workflow you wish to
        /// invoke and then the first method called on a workflow stub must be
        /// the one of the methods tagged by <see cref="WorkflowMethodAttribute"/>.
        /// </remarks>
        public TWorkflowInterface NewWorkflowStub <TWorkflowInterface>(string workflowId, string runId = null, string domain = null)
            where TWorkflowInterface : class
        {
            Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(workflowId), nameof(workflowId));
            CadenceHelper.ValidateWorkflowInterface(typeof(TWorkflowInterface));
            EnsureNotDisposed();

            return(StubManager.NewWorkflowStub <TWorkflowInterface>(this, workflowId, runId, domain));
        }
Esempio n. 9
0
        public void Generate_WorkflowResultWithOptions()
        {
            var stub = StubManager.NewWorkflowStub <IWorkflowEntryResultWithArgs>(client, options: new StartWorkflowOptions()
            {
                TaskQueue = "my-taskqueue", Namespace = "my-namespace"
            });

            Assert.NotNull(stub);
        }
Esempio n. 10
0
        /// <summary>
        /// Starts the target workflow that returns <typeparamref name="TResult"/>, passing any specified arguments.
        /// </summary>
        /// <typeparam name="TResult">The workflow result type.</typeparam>
        /// <param name="args">The arguments to be passed to the workflow.</param>
        /// <returns>The <see cref="ChildWorkflowFuture{T}"/> with the <see cref="ChildWorkflowFuture{T}.GetAsync"/> that can be used to retrieve the workfow result.</returns>
        /// <exception cref="InvalidOperationException">Thrown when attempting to start a future stub more than once.</exception>
        /// <remarks>
        /// <para>
        /// You must take care to pass parameters that are compatible with the target workflow parameters.
        /// These are checked at runtime but not while compiling.
        /// </para>
        /// <note>
        /// Any given <see cref="ChildWorkflowStub{TWorkflowInterface}"/> may only be executed once.
        /// </note>
        /// </remarks>
        public async Task <ChildWorkflowFuture <TResult> > StartAsync <TResult>(params object[] args)
        {
            await SyncContext.Clear;

            Covenant.Requires <ArgumentNullException>(parentWorkflow != null, nameof(parentWorkflow));
            parentWorkflow.SetStackTrace();

            if (hasStarted)
            {
                throw new InvalidOperationException("Cannot start a future stub more than once.");
            }

            var parameters = targetMethod.GetParameters();

            if (parameters.Length != args.Length)
            {
                throw new ArgumentException($"Invalid number of parameters: [{parameters.Length}] expected but [{args.Length}] were passed.", nameof(parameters));
            }

            hasStarted = true;

            // Cast the input parameters to the target types so that developers won't need to expicitly
            // cast things like integers into longs, floats into doubles, etc.

            for (int i = 0; i < args.Length; i++)
            {
                args[i] = TemporalHelper.ConvertArg(parameters[i].ParameterType, args[i]);
            }

            // Start the child workflow and then construct and return the future.

            var client    = parentWorkflow.Client;
            var execution = await client.StartChildWorkflowAsync(parentWorkflow, workflowTypeName, TemporalHelper.ArgsToBytes(client.DataConverter, args), options);

            // Initialize the type-safe stub property such that developers can call
            // any query or signal methods.

            Stub = StubManager.NewChildWorkflowStub <TWorkflowInterface>(client, parentWorkflow, workflowTypeName, execution);

            // Create and return the future.

            var resultType = targetMethod.ReturnType;

            if (resultType == typeof(Task))
            {
                throw new ArgumentException($"Workflow method [{nameof(TWorkflowInterface)}.{targetMethod.Name}()] does not return [void].", nameof(TWorkflowInterface));
            }

            resultType = resultType.GenericTypeArguments.First();

            if (!resultType.IsAssignableFrom(typeof(TResult)))
            {
                throw new ArgumentException($"Workflow method [{nameof(TWorkflowInterface)}.{targetMethod.Name}()] returns [{resultType.FullName}] which is not compatible with [{nameof(TResult)}].", nameof(TWorkflowInterface));
            }

            return(new ChildWorkflowFuture <TResult>(parentWorkflow, execution));
        }
Esempio n. 11
0
        public void Generate_WorkflowResultWithOptions()
        {
            var stub = StubManager.NewWorkflowStub <IWorkflowEntryResultWithArgs>(client, options: new WorkflowOptions()
            {
                TaskList = "my-tasklist", Domain = "my-domain"
            });

            Assert.NotNull(stub);
        }
Esempio n. 12
0
        public StubManagerTests()
        {
            var stubManagerOptions = new StubManagerOptions()
            {
                AutoGenerateUnknown = true, AutoResolveByNaming = true
            };

            _stubManager = new StubManager(stubManagerOptions,
                                           new StubTypeCacheManager(new StubTypeMemoryCache(new DefaultStubTypeCacheKeyGenerator())),
                                           new DefaultStubDataMappingProfile());
        }
Esempio n. 13
0
        /// <summary>
        /// Creates a typed workflow stub connected to a known workflow execution
        /// using a <see cref="WorkflowExecution"/>.  This can be used to signal and
        /// query the workflow via the type-safe interface methods.
        /// </summary>
        /// <typeparam name="TWorkflowInterface">Identifies the workflow interface.</typeparam>
        /// <param name="execution">Specifies the <see cref="WorkflowExecution"/>.</param>
        /// <returns>The dynamically generated stub that implements the workflow methods defined by <typeparamref name="TWorkflowInterface"/>.</returns>
        /// <remarks>
        /// Unlike activity stubs, a workflow stub may only be used to launch a single
        /// workflow.  You'll need to create a new stub for each workflow you wish to
        /// invoke and then the first method called on a workflow stub must be
        /// the one of the methods tagged by <see cref="WorkflowMethodAttribute"/>.
        /// </remarks>
        public TWorkflowInterface NewWorkflowStub <TWorkflowInterface>(WorkflowExecution execution)
            where TWorkflowInterface : class
        {
            Covenant.Requires <ArgumentNullException>(execution != null, nameof(execution));
            Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(execution.WorkflowId), nameof(execution.WorkflowId));
            Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(execution.RunId), nameof(execution.RunId));
            CadenceHelper.ValidateWorkflowInterface(typeof(TWorkflowInterface));
            EnsureNotDisposed();

            return(StubManager.NewWorkflowStub <TWorkflowInterface>(this, execution.WorkflowId, execution.RunId));
        }
Esempio n. 14
0
        /// <summary>
        /// Starts the target workflow that returns <c>void</c>, passing any specified arguments.
        /// </summary>
        /// <param name="args">The arguments to be passed to the workflow.</param>
        /// <returns>The <see cref="ChildWorkflowFuture{T}"/> with the <see cref="ChildWorkflowFuture{T}.GetAsync"/> than can be used to retrieve the workfow result.</returns>
        /// <exception cref="InvalidOperationException">Thrown when attempting to start a future stub more than once.</exception>
        /// <remarks>
        /// <para>
        /// You must take care to pass parameters that are compatible with the target workflow parameters.
        /// These are checked at runtime but not while compiling.
        /// </para>
        /// <note>
        /// Any given <see cref="ChildWorkflowStub{TWorkflowInterface}"/> may only be executed once.
        /// </note>
        /// </remarks>
        public async Task <ChildWorkflowFuture> StartAsync(params object[] args)
        {
            await SyncContext.ClearAsync;

            Covenant.Requires <ArgumentNullException>(parentWorkflow != null, nameof(parentWorkflow));
            parentWorkflow.SetStackTrace();

            if (hasStarted)
            {
                throw new InvalidOperationException("Cannot start a future stub more than once.");
            }

            var parameters = targetMethod.GetParameters();

            if (parameters.Length != args.Length)
            {
                throw new ArgumentException($"Invalid number of parameters: [{parameters.Length}] expected but [{args.Length}] were passed.", nameof(parameters));
            }

            hasStarted = true;

            // Cast the input parameters to the target types so that developers won't need to expicitly
            // cast things like integers into longs, floats into doubles, etc.

            for (int i = 0; i < args.Length; i++)
            {
                args[i] = TypeDescriptor.GetConverter(parameters[i].ParameterType).ConvertTo(args[i], parameters[i].ParameterType);
            }

            // Start the child workflow and then construct the future.

            var client    = parentWorkflow.Client;
            var execution = await client.StartChildWorkflowAsync(parentWorkflow, workflowTypeName, client.DataConverter.ToData(args), options);

            // Initialize the type-safe stub property such that developers can call
            // any query or signal methods.

            Stub = StubManager.NewChildWorkflowStub <TWorkflowInterface>(client, parentWorkflow, workflowTypeName, execution);

            // Create and return the future.

            return(new ChildWorkflowFuture(parentWorkflow, execution));
        }
Esempio n. 15
0
 public void Generate_ActivityMultiMethods()
 {
     Assert.NotNull(StubManager.NewActivityStub <IActivityMultiMethods>(client, new DummyWorkflow().Workflow));
     Assert.NotNull(StubManager.NewLocalActivityStub <IActivityMultiMethods, ActivityMultiMethods>(client, new DummyWorkflow().Workflow));
 }
Esempio n. 16
0
        public void Generate_WorkflowResultWithOptions()
        {
            var stub = StubManager.CreateWorkflowStub <IWorkflowEntryResultWithArgs>(client, taskList: "my-tasklist", options: new WorkflowOptions(), domain: "my-domain");

            Assert.NotNull(stub);
        }
Esempio n. 17
0
        public void Generate_WorkflowResultWithArgs()
        {
            var stub = StubManager.CreateWorkflowStub <IWorkflowEntryResultWithArgs>(client);

            Assert.NotNull(stub);
        }
Esempio n. 18
0
        public void Generate_WorkflowMultiMethods()
        {
            var stub = StubManager.CreateWorkflowStub <IWorkflowMultiMethods>(client);

            Assert.NotNull(stub);
        }
Esempio n. 19
0
        public void Generate_WorkflowQueryVoidWithArgs()
        {
            var stub = StubManager.CreateWorkflowStub <IWorkflowQueryVoidWithArgs>(client);

            Assert.NotNull(stub);
        }
Esempio n. 20
0
        public void Error_ActivityNullClient()
        {
            // A non-NULL client is required.

            Assert.Throws <ArgumentNullException>(() => StubManager.NewActivityStub <IErrorNoEntryPointActivity>(null, new DummyWorkflow().Workflow));
        }
 public void SetUp()
 {
     theSession = SessionUtilities.CreateEmptySession();
     theManager = new StubManager();
     theAdapter = new ClarifySessionAdapter(theSession, theManager);
 }
Esempio n. 22
0
        public void Error_ActivityGenericsNotAllowed()
        {
            // We don't support activity interfaces with generic parameters.

            Assert.Throws <ActivityTypeException>(() => StubManager.NewActivityStub <IErrorGenericActivity <int> >(client, new DummyWorkflow().Workflow));
        }
Esempio n. 23
0
        public void Error_ActivityNotPublic()
        {
            // Activity interfaces must be public.

            Assert.Throws <ActivityTypeException>(() => StubManager.NewActivityStub <IErrorNotPublicActivity>(client, new DummyWorkflow().Workflow));
        }
Esempio n. 24
0
        public void Error_ActivityNonTaskEntryPoint()
        {
            // Activity entry points methods need to return a Task.

            Assert.Throws <ActivityTypeException>(() => StubManager.NewActivityStub <IErrorNonTaskEntryPoint1Activity>(client, new DummyWorkflow().Workflow));
        }
Esempio n. 25
0
        public void Error_ActivityNotInterface()
        {
            // Only activity interfaces are allowed.

            Assert.Throws <ActivityTypeException>(() => StubManager.NewActivityStub <IErrorNotInterfaceActivity>(client, new DummyWorkflow().Workflow));
        }
Esempio n. 26
0
 public void SetUp()
 {
     theSession = SessionUtilities.CreateEmptySession();
     theManager = new StubManager();
     theAdapter = new ClarifySessionAdapter(theSession, theManager);
 }
 public void Generate_ActivityEntryVoidWithArgs()
 {
     Assert.NotNull(StubManager.CreateActivityStub <IActivityEntryVoidWithArgs>(client, new DummyWorkflow(), "my-activity"));
     Assert.NotNull(StubManager.CreateLocalActivityStub <IActivityEntryVoidWithArgs, ActivityEntryVoidWithArgs>(client, new DummyWorkflow()));
 }
Esempio n. 28
0
 public void Generate_ActivityEntryVoidWithArgs()
 {
     Assert.NotNull(StubManager.NewActivityStub <IActivityEntryVoidWithArgs>(client, new DummyWorkflow().Workflow));
     Assert.NotNull(StubManager.NewLocalActivityStub <IActivityEntryVoidWithArgs, ActivityEntryVoidWithArgs>(client, new DummyWorkflow().Workflow));
 }
Esempio n. 29
0
        public void Error_ActivityNoEntryPoint()
        {
            // Activities need to have at least one entry point.

            Assert.Throws <ActivityTypeException>(() => StubManager.NewActivityStub <IErrorNoEntryPointActivity>(client, new DummyWorkflow().Workflow));
        }
Esempio n. 30
0
        /// <summary>
        /// Signals Cadence that the application is capable of executing workflows and/or activities for a specific
        /// domain and task list.
        /// </summary>
        /// <param name="taskList">Specifies the task list implemented by the worker.  This must not be empty.</param>
        /// <param name="options">Optionally specifies additional worker options.</param>
        /// <param name="domain">Optionally overrides the default <see cref="CadenceClient"/> domain.</param>
        /// <returns>A <see cref="Worker"/> identifying the worker instance.</returns>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="taskList"/> is <c>null</c> or empty.</exception>
        /// <exception cref="InvalidOperationException">
        /// Thrown when an attempt is made to recreate a worker with the
        /// same properties on a given client.  See the note in the remarks.
        /// </exception>
        /// <remarks>
        /// <note>
        /// <see cref="CadenceClient"/> for more information on task lists.
        /// </note>
        /// <para>
        /// Your workflow application will need to call this method so that Cadence will know
        /// that it can schedule activities to run within the current process.  You'll need
        /// to specify the target Cadence domain and task list.
        /// </para>
        /// <para>
        /// You may also specify an optional <see cref="WorkerOptions"/> parameter as well
        /// as customize the name used to register the activity, which defaults to the
        /// fully qualified name of the activity type.
        /// </para>
        /// <para>
        /// This method returns a <see cref="Worker"/> which implements <see cref="IDisposable"/>.
        /// It's a best practice to call <see cref="Dispose()"/> just before the a worker process
        /// terminates, but this is optional.  Advanced worker implementation that need to change
        /// their configuration over time can also call <see cref="Dispose()"/> to stop workers
        /// for specific domains and task lists.
        /// </para>
        /// <note>
        /// The Cadence GOLANG client does not appear to support starting a worker with a given
        /// set of parameters, stopping that workflow, and then restarting another worker
        /// with the same parameters on the same client.  This method detects this situation
        /// and throws an <see cref="InvalidOperationException"/> when a restart is attempted.
        /// </note>
        /// </remarks>
        public async Task <Worker> StartWorkerAsync(string taskList, WorkerOptions options = null, string domain = null)
        {
            await SyncContext.ClearAsync;

            Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(taskList), nameof(taskList), "Workers must be started with a non-empty workflow.");
            EnsureNotDisposed();

            options = options ?? new WorkerOptions();
            domain  = ResolveDomain(domain);

            WorkerMode mode = options.Mode;
            Worker     worker;

            try
            {
                using (await workerRegistrationMutex.AcquireAsync())
                {
                    // Ensure that we haven't already registered a worker for the
                    // specified activity, domain, and task list.  We'll just increment
                    // the reference count for the existing worker and return it
                    // in this case.
                    //
                    // I know that this is a linear search but the number of activity
                    // registrations per service will generally be very small and
                    // registrations will happen infrequently (typically just once
                    // per service, when it starts).

                    // $note(jefflill):
                    //
                    // If the worker exists but its RefCount==0, then we're going to
                    // throw an exception because Cadence doesn't support recreating
                    // a worker with the same parameters on the same client.

                    worker = workers.Values.SingleOrDefault(wf => wf.Mode == mode && wf.Domain == domain && wf.Tasklist == taskList);

                    if (worker != null)
                    {
                        if (worker.RefCount < 0)
                        {
                            throw new InvalidOperationException("A worker with these same parameters has already been started and stopped on this Cadence client.  Cadence does not support recreating workers for a given client instance.");
                        }

                        Interlocked.Increment(ref worker.RefCount);
                        return(worker);
                    }

                    options = options ?? new WorkerOptions();

                    var reply = (NewWorkerReply)(await CallProxyAsync(
                                                     new NewWorkerRequest()
                    {
                        Domain = ResolveDomain(domain),
                        TaskList = taskList,
                        Options = options.ToInternal()
                    }));

                    reply.ThrowOnError();

                    worker = new Worker(this, mode, reply.WorkerId, domain, taskList);
                    workers.Add(reply.WorkerId, worker);
                }
            }
            finally
            {
                switch (mode)
                {
                case WorkerMode.Activity:

                    activityWorkerStarted = true;
                    break;

                case WorkerMode.Workflow:

                    workflowWorkerStarted = true;
                    break;

                case WorkerMode.Both:

                    activityWorkerStarted = true;
                    workflowWorkerStarted = true;
                    break;

                default:

                    throw new NotImplementedException();
                }
            }

            // Fetch the stub for each registered workflow and activity type so that
            // they'll be precompiled so compilation won't impact workflow and activity
            // performance including potentially intruducing enough delay to cause
            // decision tasks or activity heartbeats to fail (in very rare situations).
            //
            // Note that the compiled stubs are cached, so we don't need to worry
            // about compiling stubs for types more than once causing a problem.

            lock (registeredWorkflowTypes)
            {
                foreach (var workflowInterface in registeredWorkflowTypes)
                {
                    // Workflows, we're going to compile both the external and child
                    // versions of the stubs.

                    StubManager.GetWorkflowStub(workflowInterface, isChild: false);
                    StubManager.GetWorkflowStub(workflowInterface, isChild: true);
                }
            }

            lock (registeredActivityTypes)
            {
                foreach (var activityInterface in registeredActivityTypes)
                {
                    StubManager.GetActivityStub(activityInterface);
                }
            }

            return(worker);
        }
 public void Generate_ActivityEntryVoidWithOptions()
 {
     Assert.NotNull(StubManager.CreateActivityStub <IActivityEntryVoidWithArgs>(client, new DummyWorkflow(), "my-activity", options: new ActivityOptions(), domain: "my-domain"));
     Assert.NotNull(StubManager.CreateLocalActivityStub <IActivityEntryVoidWithArgs, ActivityEntryVoidWithArgs>(client, new DummyWorkflow(), options: new LocalActivityOptions()));
 }