/// <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); }
/// <summary> /// Signals Cadence that the application is capable of executing activities for a specific /// domain and task list. /// </summary> /// <param name="taskList">Optionally specifies the target task list (defaults to <b>"default"</b>).</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="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> /// <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 these restart attempts /// are made. /// </note> /// </remarks> private async Task <Worker> StartWorkerAsync(string taskList = "default", WorkerOptions options = null, string domain = null) { Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(taskList)); options = options ?? new WorkerOptions(); 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(jeff.lill): // // 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(); } } return(worker); }