/// <summary> /// Reply to an ask /// </summary> /// <remarks> /// This should be used from within a process' message loop only /// </remarks> public static Unit reply <T>(T message) => (message is IReturn) && !((IReturn)message).HasValue ? unit : InMessageLoop ? ActorContext.Request.CurrentRequest == null ? failwith <Unit>("You can't reply to this message. It wasn't an 'ask'. Use isAsk to confirm whether something is an 'ask' or a 'tell'") : ActorContext.System(default(SystemName)).Tell( ActorContext.Request.CurrentRequest.ReplyTo, new ActorResponse( message, ActorContext.Request.CurrentRequest.ReplyTo, ActorContext.Request.Self.Actor.Id, ActorContext.Request.CurrentRequest.RequestId, typeof(T).AssemblyQualifiedName ), ActorContext.Request.Self.Actor.Id ) : raiseUseInMsgLoopOnlyException <Unit>(nameof(reply));
public static object Invoke(ProcessId pid, string method, Type rettyp, params object[] args) { if (args.Filter(a => a == null).Any()) { throw new ArgumentNullException(); } return(ActorContext.System(pid).Ask(pid, new ProxyMsg { Method = method, Args = args.Map(JsonConvert.SerializeObject).ToArray(), ArgTypes = args.Map(a => a.GetType().GetTypeInfo().AssemblyQualifiedName).ToArray(), ReturnType = rettyp.GetTypeInfo().AssemblyQualifiedName }, rettyp, Process.Self )); }
internal static IDisposable safedelay(Action f, TimeSpan delayFor) { var savedContext = ActorContext.Request; var savedSession = ActorContext.SessionId; var stackTrace = new System.Diagnostics.StackTrace(true); return(Observable.Timer(delayFor).Do(_ => { if (savedContext == null) { f(); } else { ActorSystem system; try { system = ActorContext.System(savedContext.Self.Actor.Id); } catch (Exception e) { throw new ProcessSystemException(e, stackTrace); } system.WithContext( savedContext.Self, savedContext.Parent, savedContext.Sender, savedContext.CurrentRequest, savedContext.CurrentMsg, savedSession, () => { f(); // Run the operations that affect the settings and sending of tells // in the order which they occured in the actor ActorContext.Request?.Ops?.Run(); }); } }).Subscribe(onNext: _ => { }, onCompleted: () => { }, onError: logErr)); }
public static State Inbox(State state, Msg msg) { try { state = ActorContext.System(Self).Cluster.Fold(state, (s, cluster) => { if (s.Scheduled.IsNone) { // Lazily load any items in the persistent store, once s = new State(GetScheduled(cluster)); } switch (msg) { case Msg.CheckMsg m: return(Check(s, cluster)); case Msg.AddToScheduleMsg m: return(AddToSchedule(s, m, cluster)); case Msg.RescheduleMsg m: return(Reschedule(s, m, cluster)); case Msg.RemoveFromScheduleMsg m: return(RemoveFromSchedule(s, m, cluster)); default: return(s); } }); } catch (Exception e) { try { logErr(e); } catch { } } if (msg is Msg.CheckMsg) { tellSelf(Msg.Check, schedule); } return(state); }
/// <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); } }
/// <summary> /// Spawn a router using the settings in the config /// </summary> /// <example> /// <para> /// router broadcast1: /// pid: /root/user/broadcast1 /// route: broadcast /// worker-count: 10 /// </para> /// <para> /// router broadcast2: /// pid: /root/user/broadcast2 /// route: broadcast /// workers: [hello, world] /// </para> /// <para> /// router least: /// pid: /role/user/least /// route: least-busy /// workers: [one, two, three] /// </para> /// </example> /// <param name="name">Name of the child process that will be the router</param> /// <returns>ProcessId of the router</returns> public static ProcessId fromConfig <T>(ProcessName name) { var id = Self[name]; var type = ActorContext.System(id).Settings.GetRouterDispatch(id); var workers = ActorContext.System(id).Settings.GetRouterWorkers(id) .Map(p => p.ProcessId.IfNone(ProcessId.None)) .Filter(pid => pid != ProcessId.None); var flags = ActorContext.System(id).Settings.GetProcessFlags(id); var mbs = ActorContext.System(id).Settings.GetProcessMailboxSize(id); return(type.Map(t => { // TODO: Consider the best approach to generalise this, so that bespoke router // types can make use of the config system too. switch (t) { case "broadcast": return broadcast <T>(name, workers, RouterOption.Default, flags, mbs); case "hash": return hash <T>(name, workers, null, RouterOption.Default, flags, mbs); case "least-busy": return leastBusy <T>(name, workers, RouterOption.Default, flags, mbs); case "random": return random <T>(name, workers, RouterOption.Default, flags, mbs); case "round-robin": return roundRobin <T>(name, workers, RouterOption.Default, flags, mbs); default: throw new Exception($"Unsupported router type (for config system setup): {t} "); } }) .IfNone(() => failwith <ProcessId>($"'route' not specified for {id}"))); }
internal static IDisposable safedelay(Action f, TimeSpan delayFor) { var savedContext = ActorContext.Request; var savedSession = ActorContext.SessionId; return((IDisposable)Task.Delay(delayFor).ContinueWith(_ => { try { if (savedContext == null) { f(); } else { ActorContext.System(savedContext.Self.Actor.Id).WithContext( savedContext.Self, savedContext.Parent, savedContext.Sender, savedContext.CurrentRequest, savedContext.CurrentMsg, savedSession, () => { f(); // Run the operations that affect the settings and sending of tells // in the order which they occured in the actor ActorContext.Request?.Ops?.Run(); }); } } catch (Exception e) { logErr(e); } })); }
/// <summary> /// Spawn a router using the settings in the config /// </summary> /// <example> /// <para> /// router broadcast1: /// pid: /root/user/broadcast1 /// route: broadcast /// worker-count: 10 /// </para> /// <para> /// router broadcast2: /// pid: /root/user/broadcast2 /// route: broadcast /// workers: [hello, world] /// </para> /// <para> /// router least: /// pid: /role/user/least /// route: least-busy /// workers: [one, two, three] /// </para> /// </example> /// <typeparam name="T"></typeparam> /// <param name="name">Name of the child process that will be the router</param> /// <returns>ProcessId of the router</returns> public static ProcessId fromConfig <S, T>(ProcessName name, Func <S> Setup, Func <S, T, S> Inbox) { var id = Self[name]; var type = ActorContext.System(id).Settings.GetRouterDispatch(id); var workers = ActorContext.System(id).Settings.GetRouterWorkerCount(id); var flags = ActorContext.System(id).Settings.GetProcessFlags(id); var mbs = ActorContext.System(id).Settings.GetProcessMailboxSize(id); var strategy = ActorContext.System(id).Settings.GetProcessStrategy(id); var wrkrName = ActorContext.System(id).Settings.GetRouterWorkerName(id); return(type.Map(t => { // TODO: Consider the best approach to generalise this, so that bespoke router // types can make use of the config system too. switch (t) { case "broadcast": return broadcast(name, workers.IfNone(2), Setup, Inbox, flags, strategy, mbs, wrkrName); case "hash": return hash(name, workers.IfNone(2), Setup, Inbox, null, flags, strategy, mbs, wrkrName); case "least-busy": return leastBusy(name, workers.IfNone(2), Setup, Inbox, flags, strategy, mbs, wrkrName); case "random": return random(name, workers.IfNone(2), Setup, Inbox, flags, strategy, mbs, wrkrName); case "round-robin": return roundRobin(name, workers.IfNone(2), Setup, Inbox, flags, strategy, mbs, wrkrName); default: throw new Exception($"Unsupported router type (for config system setup): {t} "); } }) .IfNone(() => failwith <ProcessId>($"'route' not specified for {id}"))); }
public Unit TellUserControl(UserControlMessage msg, Option <SessionId> sessionId) { if (msg == null) { throw new ArgumentNullException(nameof(msg)); } if (userInbox != null) { msg.SessionId = msg.SessionId ?? sessionId.Map(s => s.Value).IfNoneUnsafe(msg.SessionId); if (IsPaused) { // We're paused, so send to our persistent queue new ActorDispatchRemote( ActorContext.System(actor.Id).Ping, actor.Id, cluster, msg.SessionId == null ? None : Some(new SessionId(msg.SessionId)), false) .TellUserControl(msg, ProcessId.None); } else { try { userInbox?.Post(msg); } catch (QueueFullException) { throw new ProcessInboxFullException(actor.Id, MailboxSize, "user"); } } } return(unit); }
public static Option <UserControlMessage> PreProcessMessage <T>(ProcessId sender, ProcessId self, object message) { if (message == null) { var emsg = $"Message is null for tell (expected {typeof(T)})"; tell(ActorContext.System(self).DeadLetters, DeadLetter.create(sender, self, emsg, message)); return(None); } if (message is ActorRequest req) { if (!(req.Message is T) && !(req.Message is Message)) { var emsg = $"Invalid message type for ask (expected {typeof(T)})"; tell(ActorContext.System(self).DeadLetters, DeadLetter.create(sender, self, emsg, message)); ActorContext.System(self).Tell( sender, new ActorResponse(new Exception($"Invalid message type for ask (expected {typeof(T)})"), sender, self, req.RequestId, typeof(Exception).AssemblyQualifiedName, true ), Schedule.Immediate, self ); return(None); } return(Optional((UserControlMessage)message)); } return(new UserMessage(message, sender, sender)); }
/// <summary> /// Find out if a process exists and is alive /// /// Rules: /// * Local processes - the process must actually be running /// * Remote processes - the process must actually be running /// * Dispatchers/roles - at least one process in the collection must be running /// * JS processes - not current supported /// </summary> /// <param name="pid">Process ID to check</param> /// <returns>True if exists</returns> public static bool ping(ProcessId pid) => ActorContext.System(pid).GetDispatcher(pid).Ping();
/// <summary> /// Find out if a process exists /// /// Rules: /// * Local processes - the process must actually be alive and in-memory /// * Remote processes - the process must have an inbox to receive messages /// and may be active, but it's not required. /// * Dispatchers/roles - at least one process in the collection must exist(pid) /// * JS processes - not current supported /// </summary> /// <param name="pid">Process ID to check</param> /// <returns>True if exists</returns> public static bool exists(ProcessId pid) => ActorContext.System(pid).GetDispatcher(pid).Exists;
/// <summary> /// Un-pauses a paused process. Messages that have built-up in the inbox whilst /// the Process was paused will be Processed immediately. /// </summary> /// <param name="pid">Process to un-pause</param> public static Unit unpause(ProcessId pid) => ActorContext.System(pid).TellSystem(pid, SystemMessage.Unpause);
/// <summary> /// Forces a running process to restart. This will reset its state and drop /// any subscribers, or any of its subscriptions. /// </summary> public static Unit restart(ProcessId pid) => ActorContext.System(pid).TellSystem(pid, SystemMessage.Restart);
/// <summary> /// Shutdown all processes on all process-systems /// </summary> public static Unit shutdownAll() => ActorContext.StopAllSystems();
/// <summary> /// Shutdown all processes on the specified process-system /// </summary> public static Unit shutdownSystem(SystemName system) => ActorContext.StopSystem(system);
private IEnumerable <IActorDispatch> GetWorkers() => group.Map(pid => ActorContext.System(pid).GetDispatcher(pid));
/// <summary> /// Get the types of messages that the provided ProcessId accepts. Returns /// an empty list if it can't be resolved for whatever reason (process doesn't /// exist/JS process/etc.). /// </summary> /// <param name="pid">Process ID to query</param> /// <returns>List of types</returns> public static IEnumerable <Type> validMessageTypes(ProcessId pid) => ActorContext.System(pid).GetDispatcher(pid).GetValidMessageTypes();
/// <summary> /// Get a list of cluster nodes that are online /// </summary> public static Map <ProcessName, ClusterNode> ClusterNodes(SystemName system = default(SystemName)) => ActorContext.System(system).ClusterState?.Members ?? Map <ProcessName, ClusterNode>();
/// <summary> /// Errors process /// Subscribe to it to monitor the errors thrown /// </summary> public static ProcessId Errors(SystemName system = default(SystemName)) => ActorContext.System(system).Errors;
/// <summary> /// Dead letters process /// Subscribe to it to monitor the failed messages (<see cref="subscribe(ProcessId)"/>) /// </summary> public static ProcessId DeadLetters(SystemName system = default(SystemName)) => ActorContext.System(system).DeadLetters;
/// <summary> /// User process ID /// The User process is the default entry process, your first process spawned /// will be a child of this process. /// </summary> public static ProcessId User(SystemName system = default(SystemName)) => ActorContext.System(system).User;
/// <summary> /// Root process ID /// The Root process is the parent of all processes /// </summary> public static ProcessId Root(SystemName system = default(SystemName)) => ActorContext.System(system).Root;
/// <summary> /// Stop watching for the death of the watching process /// </summary> /// <param name="watcher">Watcher</param> /// <param name="watching">Watched</param> public static Unit unwatch(ProcessId watcher, ProcessId watching) => ActorContext.System(watcher).GetDispatcher(watcher).DispatchUnWatch(watching);
/// <summary> /// Find the number of items in the Process inbox /// </summary> /// <param name="pid">Process</param> /// <returns>Number of items in the Process inbox</returns> public static int inboxCount(ProcessId pid) => ActorContext.System(pid).GetDispatcher(pid).GetInboxCount();
/// <summary> /// Forward a message /// </summary> /// <param name="pid">Process ID to send to</param> public static Unit fwd(ProcessId pid) => ActorContext.Request.CurrentRequest == null ? tell(pid, ActorContext.Request.CurrentMsg, Sender) : tell(pid, ActorContext.Request.CurrentRequest, ActorContext.System(pid).AskId);
/// <summary> /// Resolves a ProcessId into the absolute ProcessIds that it represents /// This allows live resolution of role-based ProcessIds to their real node /// ProcessIds. /// </summary> /// <remarks>Mostly useful for debugging, but could be useful for layering /// additional logic to any message dispatch. /// </remarks> /// <param name="pid"></param> /// <returns>Enumerable of resolved ProcessIds - could be zero length</returns> public static IEnumerable <ProcessId> resolve(ProcessId pid) => ActorContext.System(pid).ResolveProcessIdSelection(pid);
/// <summary> /// Kill a specified running process. /// Forces the specified Process to shutdown. The kill message jumps /// ahead of any messages already in the process's queue. Any Process /// that has a persistent inbox or state will also have its persistent /// data wiped. If the Process is registered it will also its /// registration revoked. /// If you wish for the data to be maintained for future /// spawns then call Process.shutdown(pid); /// </summary> public static Unit kill(ProcessId pid) => ActorContext.System(pid).Kill(pid, false);
/// <summary> /// Get the child processes of the process ID provided /// </summary> public static Map <string, ProcessId> children(ProcessId pid) => ActorContext.System(pid).GetChildren(pid);
/// <summary> /// Shutdown a specified running process. /// Forces the specified Process to shutdown. The shutdown message jumps /// ahead of any messages already in the process's queue. Any Process /// that has a persistent inbox or state will have its state maintained /// for future spawns. If you wish for the data to be dropped then call /// Process.kill(pid) /// </summary> public static Unit shutdown(ProcessId pid) => ActorContext.System(pid).Kill(pid, true);