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)); }
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")); }
public void Error_ActivityDuplicateDuplicateEntryPoints() { // Verify that we detect duplicate entrypoint methods // with explicit names. Assert.Throws <ActivityTypeException>(() => StubManager.NewActivityStub <IDuplicateEntryPointsActivity>(client, new DummyWorkflow().Workflow)); }
public StubManagerTests() { var stubManagerOptions = new StubManagerOptions { AutoGenerateUnknown = true, AutoResolveByNaming = true }; _stubManager = new StubManager(stubManagerOptions); }
/// <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)); }
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())); }
/// <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)); }
public void Generate_WorkflowResultWithOptions() { var stub = StubManager.NewWorkflowStub <IWorkflowEntryResultWithArgs>(client, options: new StartWorkflowOptions() { TaskQueue = "my-taskqueue", Namespace = "my-namespace" }); Assert.NotNull(stub); }
/// <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)); }
public void Generate_WorkflowResultWithOptions() { var stub = StubManager.NewWorkflowStub <IWorkflowEntryResultWithArgs>(client, options: new WorkflowOptions() { TaskList = "my-tasklist", Domain = "my-domain" }); Assert.NotNull(stub); }
public StubManagerTests() { var stubManagerOptions = new StubManagerOptions() { AutoGenerateUnknown = true, AutoResolveByNaming = true }; _stubManager = new StubManager(stubManagerOptions, new StubTypeCacheManager(new StubTypeMemoryCache(new DefaultStubTypeCacheKeyGenerator())), new DefaultStubDataMappingProfile()); }
/// <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)); }
/// <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)); }
public void Generate_ActivityMultiMethods() { Assert.NotNull(StubManager.NewActivityStub <IActivityMultiMethods>(client, new DummyWorkflow().Workflow)); Assert.NotNull(StubManager.NewLocalActivityStub <IActivityMultiMethods, ActivityMultiMethods>(client, new DummyWorkflow().Workflow)); }
public void Generate_WorkflowResultWithOptions() { var stub = StubManager.CreateWorkflowStub <IWorkflowEntryResultWithArgs>(client, taskList: "my-tasklist", options: new WorkflowOptions(), domain: "my-domain"); Assert.NotNull(stub); }
public void Generate_WorkflowResultWithArgs() { var stub = StubManager.CreateWorkflowStub <IWorkflowEntryResultWithArgs>(client); Assert.NotNull(stub); }
public void Generate_WorkflowMultiMethods() { var stub = StubManager.CreateWorkflowStub <IWorkflowMultiMethods>(client); Assert.NotNull(stub); }
public void Generate_WorkflowQueryVoidWithArgs() { var stub = StubManager.CreateWorkflowStub <IWorkflowQueryVoidWithArgs>(client); Assert.NotNull(stub); }
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); }
public void Error_ActivityGenericsNotAllowed() { // We don't support activity interfaces with generic parameters. Assert.Throws <ActivityTypeException>(() => StubManager.NewActivityStub <IErrorGenericActivity <int> >(client, new DummyWorkflow().Workflow)); }
public void Error_ActivityNotPublic() { // Activity interfaces must be public. Assert.Throws <ActivityTypeException>(() => StubManager.NewActivityStub <IErrorNotPublicActivity>(client, new DummyWorkflow().Workflow)); }
public void Error_ActivityNonTaskEntryPoint() { // Activity entry points methods need to return a Task. Assert.Throws <ActivityTypeException>(() => StubManager.NewActivityStub <IErrorNonTaskEntryPoint1Activity>(client, new DummyWorkflow().Workflow)); }
public void Error_ActivityNotInterface() { // Only activity interfaces are allowed. Assert.Throws <ActivityTypeException>(() => StubManager.NewActivityStub <IErrorNotInterfaceActivity>(client, new DummyWorkflow().Workflow)); }
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())); }
public void Generate_ActivityEntryVoidWithArgs() { Assert.NotNull(StubManager.NewActivityStub <IActivityEntryVoidWithArgs>(client, new DummyWorkflow().Workflow)); Assert.NotNull(StubManager.NewLocalActivityStub <IActivityEntryVoidWithArgs, ActivityEntryVoidWithArgs>(client, new DummyWorkflow().Workflow)); }
public void Error_ActivityNoEntryPoint() { // Activities need to have at least one entry point. Assert.Throws <ActivityTypeException>(() => StubManager.NewActivityStub <IErrorNoEntryPointActivity>(client, new DummyWorkflow().Workflow)); }
/// <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())); }