コード例 #1
0
ファイル: Prelude.cs プロジェクト: tanxeel90/echo-process
        /// <summary>
        /// Cancel an already scheduled message
        /// </summary>
        public static Unit cancelScheduled(ProcessId pid, string key)
        {
            var inboxKey = ActorInboxCommon.ClusterScheduleKey(pid);

            LocalScheduler.RemoveExistingScheduledMessage(pid, key);
            return(tell(pid.Take(1).Child("system").Child("scheduler"), Scheduler.Msg.RemoveFromSchedule(inboxKey, key)));
        }
コード例 #2
0
ファイル: Scheduler.cs プロジェクト: tanxeel90/echo-process
        static State Check(State state, ICluster cluster)
        {
            var now = DateTime.UtcNow.Ticks;

            foreach (var map in state.Scheduled)
            {
                foreach (var outerKeyValue in map)
                {
                    foreach (var kv in outerKeyValue.Value)
                    {
                        if (kv.Value.Due < now)
                        {
                            var inboxKey       = ActorInboxCommon.ClusterInboxKey(kv.Value.To, "user");
                            var inboxNotifyKey = ActorInboxCommon.ClusterInboxNotifyKey(kv.Value.To, "user");
                            if (cluster.Enqueue(inboxKey, kv.Value) > 0)
                            {
                                cluster.DeleteHashField(outerKeyValue.Key, kv.Key);
                                state = state.Delete(outerKeyValue.Key, kv.Key);
                                cluster.PublishToChannel(inboxNotifyKey, kv.Value.MessageId);
                            }
                        }
                    }
                }
            }
            return(state);
        }
コード例 #3
0
 public Unit Tell(object message, ProcessId sender, Option <SessionId> sessionId)
 {
     if (message == null)
     {
         throw new ArgumentNullException(nameof(message));
     }
     if (userInbox != null)
     {
         ActorInboxCommon.PreProcessMessage <T>(sender, actor.Id, message, sessionId)
         .IfSome(msg =>
         {
             if (IsPaused)
             {
                 new ActorDispatchRemote(ActorContext.System(actor.Id).Ping, actor.Id, cluster, ActorContext.SessionId, false).Tell(message, Schedule.Immediate, sender, Message.TagSpec.User);
             }
             else
             {
                 try
                 {
                     userInbox?.Post(msg);
                 }
                 catch (QueueFullException)
                 {
                     throw new ProcessInboxFullException(actor.Id, MailboxSize, "user");
                 }
             }
         });
     }
     return(unit);
 }
コード例 #4
0
 public Unit Ask(object message, ProcessId sender, Option <SessionId> sessionId)
 {
     if (userInbox != null)
     {
         ActorInboxCommon.PreProcessMessage <T>(sender, actor.Id, message, sessionId)
         .IfSome(msg =>
         {
             if (IsPaused)
             {
                 new ActorDispatchRemote(ActorContext.System(actor.Id).Ping, actor.Id, cluster, ActorContext.SessionId, false).Ask(message, sender);
             }
             else
             {
                 try
                 {
                     userInbox?.Post(msg);
                 }
                 catch (QueueFullException)
                 {
                     throw new ProcessInboxFullException(actor.Id, MailboxSize, "user");
                 }
             }
         });
     }
     return(unit);
 }
コード例 #5
0
        public void CheckRemoteInbox(string key, ICluster cluster, ProcessId self, PausableBlockingQueue <SystemMessage> sysInbox, PausableBlockingQueue <UserControlMessage> userInbox, bool pausable)
        {
            try
            {
                int count = cluster.QueueLength(key);

                while (count > 0 && (!pausable || !IsPaused))
                {
                    Option <Tuple <RemoteMessageDTO, Message> > pair;
                    lock (sync)
                    {
                        pair = ActorInboxCommon.GetNextMessage(cluster, self, key);
                        pair.IfSome(x => cluster.Dequeue <RemoteMessageDTO>(key));
                    }

                    pair.IfSome(x => iter(x, (dto, msg) =>
                    {
                        switch (msg.MessageType)
                        {
                        case Message.Type.System: sysInbox.Post((SystemMessage)msg); break;

                        case Message.Type.User: userInbox.Post((UserControlMessage)msg); break;

                        case Message.Type.UserControl: userInbox.Post((UserControlMessage)msg); break;
                        }
                    }));
                    count--;
                }
            }
            catch (Exception e)
            {
                logSysErr($"CheckRemoteInbox failed for {self}", e);
            }
        }
コード例 #6
0
        public Unit Startup(IActor process, ActorItem parent, Option <ICluster> cluster, int maxMailboxSize)
        {
            if (cluster.IsNone)
            {
                throw new Exception("Remote inboxes not supported when there's no cluster");
            }
            this.actor   = (Actor <S, T>)process;
            this.cluster = cluster.IfNoneUnsafe(() => null);

            this.maxMailboxSize = maxMailboxSize == -1
                ? ActorContext.System(actor.Id).Settings.GetProcessMailboxSize(actor.Id)
                : maxMailboxSize;

            this.parent = parent;

            userNotify = new PausableBlockingQueue <string>(this.maxMailboxSize);

            var obj = new ThreadObj {
                Actor = actor, Inbox = this, Parent = parent
            };

            userNotify.ReceiveAsync(obj, (state, msg) => { CheckRemoteInbox(ActorInboxCommon.ClusterUserInboxKey(state.Actor.Id), true); return(InboxDirective.Default); });

            SubscribeToSysInboxChannel();
            SubscribeToUserInboxChannel();

            this.cluster.SetValue(ActorInboxCommon.ClusterMetaDataKey(actor.Id), new ProcessMetaData(
                                      new[] { typeof(T).AssemblyQualifiedName },
                                      typeof(S).AssemblyQualifiedName,
                                      typeof(S).GetTypeInfo().ImplementedInterfaces.Map(x => x.AssemblyQualifiedName).ToArray()
                                      ));

            return(unit);
        }
コード例 #7
0
        public Unit Startup(IActor process, ActorItem parent, Option <ICluster> cluster, int maxMailboxSize)
        {
            if (cluster.IsNone)
            {
                throw new Exception("Remote inboxes not supported when there's no cluster");
            }

            this.actor          = (Actor <S, T>)process;
            this.parent         = parent;
            this.cluster        = cluster.IfNoneUnsafe(() => null);
            this.maxMailboxSize = maxMailboxSize == -1
                ? ActorContext.System(actor.Id).Settings.GetProcessMailboxSize(actor.Id)
                : maxMailboxSize;

            userInbox = new PausableBlockingQueue <UserControlMessage>(this.maxMailboxSize);
            sysInbox  = new PausableBlockingQueue <SystemMessage>(this.maxMailboxSize);

            var obj = new ThreadObj {
                Actor = actor, Inbox = this, Parent = parent
            };

            userInbox.ReceiveAsync(obj, (state, msg) => ActorInboxCommon.UserMessageInbox(state.Actor, state.Inbox, msg, state.Parent));
            sysInbox.ReceiveAsync(obj, (state, msg) => ActorInboxCommon.SystemMessageInbox(state.Actor, state.Inbox, msg, state.Parent));

            SubscribeToSysInboxChannel();
            SubscribeToUserInboxChannel();

            return(unit);
        }
コード例 #8
0
 void SubscribeToSysInboxChannel()
 {
     // System inbox is just listening to the notifications, that means that system
     // messages don't persist.
     cluster.UnsubscribeChannel(ActorInboxCommon.ClusterSystemInboxNotifyKey(actor.Id));
     cluster.SubscribeToChannel <RemoteMessageDTO>(ActorInboxCommon.ClusterSystemInboxNotifyKey(actor.Id)).Subscribe(SysInbox);
 }
コード例 #9
0
        Unit DoScheduleNoIO(object message, ProcessId sender, Schedule schedule, Message.Type type, Message.TagSpec tag)
        {
            var inboxKey = ActorInboxCommon.ClusterScheduleKey(ProcessId);
            var id       = schedule.Key ?? Guid.NewGuid().ToString();
            var current  = Cluster.GetHashField <RemoteMessageDTO>(inboxKey, id);

            message = current.Match(
                Some: last =>
            {
                var a = MessageSerialiser.DeserialiseMsg(last, ProcessId) as UserMessage;
                var m = a == null
                        ? message
                        : schedule.Fold(a.Content, message);
                return(m);
            },
                None: () => schedule.Fold(schedule.Zero, message));

            ValidateMessageType(message, sender);

            var dto = RemoteMessageDTO.Create(message, ProcessId, sender, type, tag, SessionId, schedule.Due.Ticks);

            tell(ProcessId.Take(1).Child("system").Child("scheduler"), Scheduler.Msg.AddToSchedule(inboxKey, id, dto));

            //Cluster.HashFieldAddOrUpdate(inboxKey, id, dto);
            return(unit);
        }
コード例 #10
0
        public Unit Startup(IActor process, ActorItem parent, Option <ICluster> cluster, int maxMailboxSize)
        {
            if (Active)
            {
                Shutdown();
            }
            this.cluster        = cluster;
            this.parent         = parent;
            this.actor          = (Actor <S, T>)process;
            this.maxMailboxSize = maxMailboxSize == -1
                ? ActorContext.System(actor.Id).Settings.GetProcessMailboxSize(actor.Id)
                : maxMailboxSize;

            userInbox = new PausableBlockingQueue <UserControlMessage>(this.maxMailboxSize);
            sysInbox  = new PausableBlockingQueue <SystemMessage>(this.maxMailboxSize);

            var obj = new ThreadObj {
                Actor = actor, Inbox = this, Parent = parent
            };

            userInbox.ReceiveAsync(obj, (state, msg) => process.CancellationTokenSource.IsCancellationRequested ? InboxDirective.Shutdown : ActorInboxCommon.UserMessageInbox(state.Actor, state.Inbox, msg, state.Parent));
            sysInbox.ReceiveAsync(obj, (state, msg) => ActorInboxCommon.SystemMessageInbox(state.Actor, state.Inbox, msg, state.Parent));

            return(unit);
        }
コード例 #11
0
ファイル: Prelude.cs プロジェクト: tanxeel90/echo-process
        /// <summary>
        /// Re-schedule an already scheduled message
        /// </summary>
        public static Unit reschedule(ProcessId pid, string key, DateTime when)
        {
            var inboxKey = ActorInboxCommon.ClusterScheduleKey(pid);

            LocalScheduler.Reschedule(pid, key, when);
            return(tell(pid.Take(1).Child("system").Child("scheduler"), Scheduler.Msg.Reschedule(inboxKey, key, when)));
        }
コード例 #12
0
 void SubscribeToUserInboxChannel()
 {
     cluster.UnsubscribeChannel(ActorInboxCommon.ClusterUserInboxNotifyKey(actor.Id));
     cluster.SubscribeToChannel <string>(ActorInboxCommon.ClusterUserInboxNotifyKey(actor.Id)).Subscribe(msg => userNotify.Post(msg));
     // We want the check done asyncronously, in case the setup function creates child processes that
     // won't exist if we invoke directly.
     cluster.PublishToChannel(ActorInboxCommon.ClusterUserInboxNotifyKey(actor.Id), Guid.NewGuid().ToString());
 }
コード例 #13
0
 public void Dispose()
 {
     userInbox?.Cancel();
     sysInbox?.Cancel();
     cluster?.UnsubscribeChannel(ActorInboxCommon.ClusterUserInboxNotifyKey(actor.Id));
     cluster?.UnsubscribeChannel(ActorInboxCommon.ClusterSystemInboxNotifyKey(actor.Id));
     userInbox = null;
     sysInbox  = null;
     cluster   = null;
 }
コード例 #14
0
        void SubscribeToScheduleInboxChannel()
        {
            cluster.UnsubscribeChannel(ActorInboxCommon.ClusterScheduleNotifyKey(actor.Id));
            cluster.SubscribeToChannel <string>(ActorInboxCommon.ClusterScheduleNotifyKey(actor.Id)).Subscribe(msg => scheduledItems++);
            // We want the check done asyncronously, in case the setup function creates child processes that
            // won't exist if we invoke directly.

            // TODO: Consider the implications of race condition here --- will probably need some large 'clear out' process that does a query
            //       on the cluster.  Or maybe this internal counter isn't the best approach.
            scheduledItems = cluster.GetHashFields(ActorInboxCommon.ClusterScheduleKey(actor.Id)).Count;
        }
コード例 #15
0
 public Unit TellSystem(SystemMessage message, ProcessId sender) =>
 transactionalIO
         ? ignore(Cluster.PublishToChannel(
                      ActorInboxCommon.ClusterSystemInboxNotifyKey(ProcessId),
                      RemoteMessageDTO.Create(message, ProcessId, sender, Message.Type.System, message.Tag, SessionId, 0)))
         : ProcessOp.IO(() =>
 {
     var clientsReached = Cluster.PublishToChannel(
         ActorInboxCommon.ClusterSystemInboxNotifyKey(ProcessId),
         RemoteMessageDTO.Create(message, ProcessId, sender, Message.Type.System, message.Tag, SessionId, 0));
     return(unit);
 });
コード例 #16
0
        Unit TellNoIO(object message, ProcessId sender, string inbox, Message.Type type, Message.TagSpec tag)
        {
            ValidateMessageType(message, sender);
            var dto            = RemoteMessageDTO.Create(message, ProcessId, sender, type, tag, SessionId, 0);
            var inboxKey       = ActorInboxCommon.ClusterInboxKey(ProcessId, inbox);
            var inboxNotifyKey = ActorInboxCommon.ClusterInboxNotifyKey(ProcessId, inbox);

            Cluster.Enqueue(inboxKey, dto);
            var clientsReached = Cluster.PublishToChannel(inboxNotifyKey, dto.MessageId);

            return(unit);
        }
コード例 #17
0
        public void Dispose()
        {
            //tokenSource?.Cancel();
            //tokenSource?.Dispose();
            //tokenSource = null;

            userNotify.Cancel();

            try { cluster?.UnsubscribeChannel(ActorInboxCommon.ClusterUserInboxNotifyKey(actor.Id)); } catch { };
            try { cluster?.UnsubscribeChannel(ActorInboxCommon.ClusterSystemInboxNotifyKey(actor.Id)); } catch { };
            cluster = null;
        }
コード例 #18
0
 public Unit Pause()
 {
     lock (sync)
     {
         if (!IsPaused)
         {
             IsPaused = true;
             cluster?.UnsubscribeChannel(ActorInboxCommon.ClusterUserInboxNotifyKey(actor.Id));
         }
     }
     return(unit);
 }
コード例 #19
0
        void CheckRemoteInbox(string key, bool pausable)
        {
            var inbox = this;
            var count = cluster?.QueueLength(key) ?? 0;

            while (count > 0 && (!pausable || !IsPaused))
            {
                var directive = InboxDirective.Default;

                ActorInboxCommon.GetNextMessage(cluster, actor.Id, key).IfSome(
                    x => iter(x, (dto, msg) =>
                {
                    try
                    {
                        switch (msg.MessageType)
                        {
                        case Message.Type.User:        directive = ActorInboxCommon.UserMessageInbox(actor, inbox, (UserControlMessage)msg, parent); break;

                        case Message.Type.UserControl: directive = ActorInboxCommon.UserMessageInbox(actor, inbox, (UserControlMessage)msg, parent); break;
                        }
                    }
                    catch (Exception e)
                    {
                        var session = msg.SessionId == null
                                ? None
                                : Some(new SessionId(msg.SessionId));

                        ActorContext.System(actor.Id).WithContext(new ActorItem(actor, inbox, actor.Flags), parent, dto.Sender, msg as ActorRequest, msg, session, () => replyErrorIfAsked(e));
                        tell(ActorContext.System(actor.Id).DeadLetters, DeadLetter.create(dto.Sender, actor.Id, e, "Remote message inbox.", msg));
                        logSysErr(e);
                    }
                    finally
                    {
                        if ((directive & InboxDirective.Pause) != 0)
                        {
                            IsPaused  = true;
                            directive = directive & (~InboxDirective.Pause);
                        }

                        if (directive == InboxDirective.Default)
                        {
                            cluster?.Dequeue <RemoteMessageDTO>(key);
                        }
                    }
                }));

                if (directive == InboxDirective.Default)
                {
                    count--;
                }
            }
        }
コード例 #20
0
 void SubscribeToUserInboxChannel()
 {
     cluster.UnsubscribeChannel(ActorInboxCommon.ClusterUserInboxNotifyKey(actor.Id));
     cluster.SubscribeToChannel <string>(ActorInboxCommon.ClusterUserInboxNotifyKey(actor.Id)).Subscribe(
         msg =>
     {
         if (userInbox.Count == 0)
         {
             CheckRemoteInbox(ActorInboxCommon.ClusterUserInboxKey(actor.Id), cluster, actor.Id, sysInbox, userInbox, true);
         }
     });
     cluster.PublishToChannel(ActorInboxCommon.ClusterUserInboxNotifyKey(actor.Id), Guid.NewGuid().ToString());
 }
コード例 #21
0
        void SetupRemoteSubscriptions(Option <ICluster> cluster, ProcessFlags flags)
        {
            if (remoteSubsAcquired)
            {
                return;
            }

            cluster.IfSome(c =>
            {
                // Watches for local state-changes and persists them
                if ((flags & ProcessFlags.PersistState) == ProcessFlags.PersistState)
                {
                    try
                    {
                        stateSubject.Subscribe(state => c.SetValue(StateKey, state));
                    }
                    catch (Exception e)
                    {
                        logSysErr(e);
                    }
                }

                // Watches for local state-changes and publishes them remotely
                if ((flags & ProcessFlags.RemoteStatePublish) == ProcessFlags.RemoteStatePublish)
                {
                    try
                    {
                        stateSubject.Subscribe(state => c.PublishToChannel(ActorInboxCommon.ClusterStatePubSubKey(Id), state));
                    }
                    catch (Exception e)
                    {
                        logSysErr(e);
                    }
                }

                // Watches for publish events and remotely publishes them
                if ((flags & ProcessFlags.RemotePublish) == ProcessFlags.RemotePublish)
                {
                    try
                    {
                        publishSubject.Subscribe(msg => c.PublishToChannel(ActorInboxCommon.ClusterPubSubKey(Id), msg));
                    }
                    catch (Exception e)
                    {
                        logSysErr(e);
                    }
                }
            });

            remoteSubsAcquired = true;
        }
コード例 #22
0
 /// <summary>
 /// Clear all settings for the process (or role if outside of the message-loop of a Process)
 /// </summary>
 public static Unit clear(SystemName system = default(SystemName))
 {
     if (InMessageLoop)
     {
         ActorContext.System(Self)
         .Settings
         .ClearSettingsOverride(ActorInboxCommon.ClusterSettingsKey(Self), ActorContext.Request.ProcessFlags);
         return(unit);
     }
     else
     {
         return(ActorContext.System(system).Settings.ClearSettingsOverride($"role-{Role.Current.Value}@settings", ProcessFlags.PersistState));
     }
 }
コード例 #23
0
 public Either <string, bool> CanAccept <T>()
 {
     if (Cluster.Exists(ActorInboxCommon.ClusterMetaDataKey(ProcessId)))
     {
         var meta = Cluster.GetValue <ProcessMetaData>(ActorInboxCommon.ClusterMetaDataKey(ProcessId));
         return(meta == null
             ? true
             : TypeHelper.IsMessageValidForProcess(typeof(T), meta.MsgTypeNames).Map(_ => true));
     }
     else
     {
         return(true);
     }
 }
コード例 #24
0
 /// <summary>
 /// Write a setting
 /// </summary>
 /// <param name="name">Name of the setting</param>
 /// <param name="prop">If the setting is a complex value (like a map or record), then
 /// this selects the property of the setting to access</param>
 /// <param name="value">Value to set</param>
 public static Unit write(string name, string prop, object value, SystemName system = default(SystemName))
 {
     if (InMessageLoop)
     {
         ActorContext.System(Self)
         .Settings
         .WriteSettingOverride(ActorInboxCommon.ClusterSettingsKey(Self), value, name, prop, ActorContext.Request.ProcessFlags);
         return(unit);
     }
     else
     {
         return(ActorContext.System(system).Settings.WriteSettingOverride($"role-{Role.Current.Value}@settings", value, name, prop, ProcessFlags.PersistState));
     }
 }
コード例 #25
0
        void ValidateMessageType(object message, ProcessId sender)
        {
            if (Cluster.Exists(ActorInboxCommon.ClusterMetaDataKey(ProcessId)))
            {
                var meta = Cluster.GetValue <ProcessMetaData>(ActorInboxCommon.ClusterMetaDataKey(ProcessId));
                if (meta == null)
                {
                    return;
                }

                TypeHelper.IsMessageValidForProcess(message, meta.MsgTypeNames).IfLeft((string err) =>
                {
                    throw new ProcessException($"{err} for Process ({ProcessId}).", ProcessId.Path, sender.Path, null);
                });
            }
        }
コード例 #26
0
        public IEnumerable <Type> GetValidMessageTypes()
        {
            if (Cluster.Exists(ActorInboxCommon.ClusterMetaDataKey(ProcessId)))
            {
                var meta = Cluster.GetValue <ProcessMetaData>(ActorInboxCommon.ClusterMetaDataKey(ProcessId));
                if (meta == null)
                {
                    return(new Type[0]);
                }

                return(meta.MsgTypeNames.Map(Type.GetType).ToArray());
            }
            else
            {
                return(new Type[0]);
            }
        }
コード例 #27
0
        public Either <string, bool> HasStateTypeOf <T>()
        {
            if (Cluster.Exists(ActorInboxCommon.ClusterMetaDataKey(ProcessId)))
            {
                var meta = Cluster.GetValue <ProcessMetaData>(ActorInboxCommon.ClusterMetaDataKey(ProcessId));
                if (meta == null)
                {
                    return(true);
                }

                return(TypeHelper.HasStateTypeOf(typeof(T), meta.StateTypeInterfaces));
            }
            else
            {
                return(true);
            }
        }
コード例 #28
0
 public Unit Tell(object message, ProcessId sender)
 {
     if (message == null)
     {
         throw new ArgumentNullException(nameof(message));
     }
     if (userInbox != null)
     {
         try
         {
             return(ActorInboxCommon.PreProcessMessage <T>(sender, actor.Id, message).IfSome(msg => userInbox.Post(msg)));
         }
         catch (QueueFullException)
         {
             throw new ProcessInboxFullException(actor.Id, MailboxSize, "user");
         }
     }
     return(unit);
 }
コード例 #29
0
        /// <summary>
        /// TODO: This is a combination of code in ActorCommon.GetNextMessage and
        ///       CheckRemoteInbox.  Some factoring is needed.
        /// </summary>
        void SysInbox(RemoteMessageDTO dto)
        {
            try
            {
                if (dto == null)
                {
                    // Failed to deserialise properly
                    return;
                }
                if (dto.Tag == 0 && dto.Type == 0)
                {
                    // Message is bad
                    tell(ActorContext.System(actor.Id).DeadLetters, DeadLetter.create(dto.Sender, actor.Id, null, "Failed to deserialise message: ", dto));
                    return;
                }
                var msg = MessageSerialiser.DeserialiseMsg(dto, actor.Id);

                try
                {
                    lock (sync)
                    {
                        ActorInboxCommon.SystemMessageInbox(actor, this, (SystemMessage)msg, parent);
                    }
                }
                catch (Exception e)
                {
                    var session = msg.SessionId == null
                        ? None
                        : Some(new SessionId(msg.SessionId));

                    ActorContext.System(actor.Id).WithContext(new ActorItem(actor, this, actor.Flags), parent, dto.Sender, msg as ActorRequest, msg, session, () => replyErrorIfAsked(e));
                    tell(ActorContext.System(actor.Id).DeadLetters, DeadLetter.create(dto.Sender, actor.Id, e, "Remote message inbox.", msg));
                    logSysErr(e);
                }
            }
            catch (Exception e)
            {
                logSysErr(e);
            }
        }
コード例 #30
0
        /// <summary>
        /// Shutdown everything from this node down
        /// </summary>
        public Unit Shutdown(bool maintainState)
        {
            cancellationTokenSource.Cancel(); // this will signal other operations not to start processing more messages (ProcessMessage) or startup again (Startup), even if they already received something from the queue
            lock (sync)
            {
                if (maintainState == false && Flags != ProcessFlags.Default)
                {
                    cluster.IfSome(c =>
                    {
                        // TODO: Make this transactional
                        // {
                        c.DeleteMany(
                            StateKey,
                            ActorInboxCommon.ClusterUserInboxKey(Id),
                            ActorInboxCommon.ClusterSystemInboxKey(Id),
                            ActorInboxCommon.ClusterMetaDataKey(Id),
                            ActorInboxCommon.ClusterSettingsKey(Id));

                        sys.DeregisterById(Id);
                        // }

                        sys.Settings.ClearInMemorySettingsOverride(ActorInboxCommon.ClusterSettingsKey(Id));
                    });
                }

                RemoveAllSubscriptions();
                publishSubject.OnCompleted();
                stateSubject.OnCompleted();
                remoteSubsAcquired = false;
                strategyState      = StrategyState.Empty;
                state.IfSome(shutdownFn);
                DisposeState();

                sys.DispatchTerminate(Id);

                return(unit);
            }
        }