예제 #1
0
        /// <summary>
        /// Returns the <see cref="V1OwnerReference"/> for the host node.
        /// </summary>
        /// <param name="k8s">The Kubernetes client to be used to query for the node information.</param>
        /// <returns>
        /// The <see cref="V1OwnerReference"/> for the node or <c>null</c> when this couldn't
        /// be determined.
        /// </returns>
        public static async Task <V1OwnerReference> GetOwnerReferenceAsync(IKubernetes k8s)
        {
            Covenant.Requires <ArgumentNullException>(k8s != null, nameof(k8s));

            if (NeonHelper.IsLinux)
            {
                using (await mutex.AcquireAsync())
                {
                    // Return any cached information.

                    if (cachedOwnerReference != null)
                    {
                        return(cachedOwnerReference);
                    }

                    // Query Kubernetes for the node information based on the the node's hostname.

                    cachedNode = await k8s.ReadNodeAsync(Name);

                    cachedOwnerReference = new V1OwnerReference(apiVersion: cachedNode.ApiVersion, name: cachedNode.Name(), kind: cachedNode.Kind, uid: cachedNode.Uid());

                    return(cachedOwnerReference);
                }
            }
            else
            {
                // Emulate without an owner reference.

                return(null);
            }
        }
예제 #2
0
        public async Task Dispose()
        {
            // Create a mutex, acquire it, and then create another task that will
            // attempt to acquire it as well (and will fail because the mutex has
            // already been acquired).  Then dispose the mutex and verify that the
            // waiting task saw the [ObjectDisposedException].

            var mutex    = new AsyncMutex();
            var inTask   = false;
            var acquired = false;
            var disposed = false;

            await mutex.AcquireAsync();

            var task = Task.Run(
                async() =>
            {
                try
                {
                    var acquireTask = mutex.AcquireAsync();

                    inTask = true;

                    await acquireTask;

                    acquired = true;
                }
                catch (ObjectDisposedException)
                {
                    disposed = true;
                }
            });

            // Wait for the task to have called [AcquireAsync()].

            NeonHelper.WaitFor(() => inTask, defaultTimeout);

            // Dispose the mutex, wait for the task to exit and then verify
            // that it caught the [ObjectDisposedException].

            mutex.Dispose();
            task.Wait(defaultTimeout);

            Assert.False(acquired);
            Assert.True(disposed);
        }
예제 #3
0
        /// <summary>
        /// Starts or restarts the handler listening for the [ProxyUpdateMessage] messages.
        /// </summary>
        private static void StartNotifyHandler()
        {
            lock (syncLock)
            {
                // Use the latest settings to reconnect to the [proxy-notify] channel.

                if (proxyNotifyChannel != null)
                {
                    proxyNotifyChannel.Dispose();
                }

                proxyNotifyChannel = hive.HiveMQ.Internal.GetProxyNotifyChannel(useBootstrap: true).Open();

                // Register a handler for [ProxyUpdateMessage] messages that determines
                // whether the message is meant for this service instance and handle it.

                proxyNotifyChannel.ConsumeAsync <ProxyUpdateMessage>(
                    async message =>
                {
                    // We cannot process updates in parallel so we'll use an
                    // AsyncMutex to prevent this.

                    using (await asyncLock.AcquireAsync())
                    {
                        var forThisInstance = false;

                        if (isPublic)
                        {
                            forThisInstance = message.PublicProxy && !isBridge ||
                                              message.PublicBridge && isBridge;
                        }
                        else
                        {
                            forThisInstance = message.PrivateProxy && !isBridge ||
                                              message.PrivateBridge && isBridge;
                        }

                        if (!forThisInstance)
                        {
                            log.LogInfo(() => $"HAPROXY-SHIM: Received but ignorning: {message}");
                            return;
                        }

                        log.LogInfo(() => $"HAPROXY-SHIM: Received: {message}");

                        var jitter = NeonHelper.RandTimespan(HiveConst.MaxJitter);

                        log.LogDebug(() => $"HAPROXY-SHIM: Jitter delay [{jitter}].");
                        await Task.Delay(jitter);

                        await ConfigureHAProxy();
                    }
                });
            }
        }
예제 #4
0
        public async Task Basic()
        {
            // Create a mutex and then several tasks that acquire the mutex for
            // a period of time, verifying that each obtains exclusive
            // access.

            var taskCount = 12;
            var refCount  = 0;
            var error     = false;
            var tasks     = new List <Task>();
            var stopwatch = new Stopwatch();
            var testTime  = defaultTimeout - TimeSpan.FromSeconds(2);

            stopwatch.Start();

            using (var mutex = new AsyncMutex())
            {
                for (int i = 0; i < taskCount; i++)
                {
                    tasks.Add(Task.Run(
                                  async() =>
                    {
                        while (stopwatch.Elapsed < testTime)
                        {
                            using (await mutex.AcquireAsync())
                            {
                                if (refCount > 0)
                                {
                                    // This means that we don't have exclusive access indicating
                                    // that the mutex must be broken.

                                    error = true;
                                }

                                try
                                {
                                    Interlocked.Increment(ref refCount);

                                    await Task.Delay(TimeSpan.FromMilliseconds(250));
                                }
                                finally
                                {
                                    Interlocked.Decrement(ref refCount);
                                }
                            }
                        }
                    }));

                    await NeonHelper.WaitAllAsync(tasks, defaultTimeout);
                }

                Assert.False(error);
            }
        }
예제 #5
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);
        }
예제 #6
0
        /// <summary>
        /// Handles requests to the emulated <b>cadence-proxy</b> root <b>"/"</b> endpoint path.
        /// </summary>
        /// <param name="context">The request context.</param>
        /// <returns>The tracking <see cref="Task"/>.</returns>
        private async Task OnEmulatedRootRequestAsync(HttpContext context)
        {
            var request      = context.Request;
            var response     = context.Response;
            var proxyMessage = ProxyMessage.Deserialize <ProxyMessage>(request.Body);

            if (EmulatedLibraryClient == null && proxyMessage.Type != InternalMessageTypes.InitializeRequest)
            {
                response.StatusCode = StatusCodes.Status400BadRequest;
                await response.WriteAsync($"Unexpected Message: Waiting for an [{nameof(InitializeRequest)}] message to specify the [cadence-client] network endpoint.");

                return;
            }

            // Handle proxy reply messages by completing any pending [CallClientAsync()] operation.

            var reply = proxyMessage as ProxyReply;

            if (reply != null)
            {
                Operation operation;

                using (await emulationMutex.AcquireAsync())
                {
                    emulatedOperations.TryGetValue(reply.RequestId, out operation);
                }

                if (operation != null)
                {
                    if (reply.Type != operation.Request.ReplyType)
                    {
                        response.StatusCode = StatusCodes.Status400BadRequest;
                        await response.WriteAsync($"[cadence-emulation] has a request [type={operation.Request.Type}, requestId={operation.RequestId}] pending but reply [type={reply.Type}] is not valid and will be ignored.");
                    }
                    else
                    {
                        operation.SetReply(reply);
                        response.StatusCode = StatusCodes.Status200OK;
                    }
                }
                else
                {
                    log.LogWarn(() => $"[cadence-emulation] reply [type={reply.Type}, requestId={reply.RequestId}] does not map to a pending operation and will be ignored.");

                    response.StatusCode = StatusCodes.Status400BadRequest;
                    await response.WriteAsync($"[cadence-emulation] does not have a pending operation with [requestId={reply.RequestId}].");
                }

                return;
            }

            // Handle proxy request messages.

            switch (proxyMessage.Type)
            {
            //-------------------------------------------------------------
            // Client messages

            case InternalMessageTypes.CancelRequest:

                await OnEmulatedCancelRequestAsync((CancelRequest)proxyMessage);

                break;

            case InternalMessageTypes.DomainDescribeRequest:

                await OnEmulatedDomainDescribeRequestAsync((DomainDescribeRequest)proxyMessage);

                break;

            case InternalMessageTypes.DomainRegisterRequest:

                await OnEmulatedDomainRegisterRequestAsync((DomainRegisterRequest)proxyMessage);

                break;

            case InternalMessageTypes.DomainUpdateRequest:

                await OnEmulatedDomainUpdateRequestAsync((DomainUpdateRequest)proxyMessage);

                break;

            case InternalMessageTypes.HeartbeatRequest:

                await OnEmulatedHeartbeatRequestAsync((HeartbeatRequest)proxyMessage);

                break;

            case InternalMessageTypes.InitializeRequest:

                await OnEmulatedInitializeRequestAsync((InitializeRequest)proxyMessage);

                break;

            case InternalMessageTypes.ConnectRequest:

                await OnEmulatedConnectRequestAsync((ConnectRequest)proxyMessage);

                break;

            case InternalMessageTypes.TerminateRequest:

                await OnEmulatedTerminateRequestAsync((TerminateRequest)proxyMessage);

                break;

            case InternalMessageTypes.NewWorkerRequest:

                await OnEmulatedNewWorkerRequestAsync((NewWorkerRequest)proxyMessage);

                break;

            case InternalMessageTypes.StopWorkerRequest:

                await OnEmulatedStopWorkerRequestAsync((StopWorkerRequest)proxyMessage);

                break;

            case InternalMessageTypes.PingRequest:

                await OnEmulatedPingRequestAsync((PingRequest)proxyMessage);

                break;

            //-------------------------------------------------------------
            // Workflow messages

            case InternalMessageTypes.WorkflowExecuteRequest:

                await OnEmulatedWorkflowExecuteRequestAsync((WorkflowExecuteRequest)proxyMessage);

                break;

            case InternalMessageTypes.WorkflowRegisterRequest:

                await OnEmulatedWorkflowRegisterRequestAsync((WorkflowRegisterRequest)proxyMessage);

                break;

            case InternalMessageTypes.WorkflowSetCacheSizeRequest:

                await OnEmulatedWorkflowSetCacheSizeRequestAsync((WorkflowSetCacheSizeRequest)proxyMessage);

                break;

            case InternalMessageTypes.WorkflowGetResultRequest:

                await OnEmulatedWorkflowGetResultRequestAsync((WorkflowGetResultRequest)proxyMessage);

                break;

            //-------------------------------------------------------------

            default:

                response.StatusCode = StatusCodes.Status400BadRequest;
                await response.WriteAsync($"EMULATION: Message [{proxyMessage.Type}] is not supported.");

                break;
            }

            await Task.CompletedTask;
        }
예제 #7
0
        /// <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);
        }