/// <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();
public Unit DispatchWatch(ProcessId pid) { sys.GetDispatcher(pid).Watch(Id); return(ActorContext.System(Id).AddWatcher(pid, Id)); }
public ProcessId ActorCreate <S, T>( ActorItem parent, ProcessName name, Func <S, T, S> actorFn, Func <IActor, S> setupFn, Func <S, ProcessId, S> termFn, State <StrategyContext, Unit> strategy, ProcessFlags flags, int maxMailboxSize, bool lazy) { var actor = new Actor <S, T>(cluster, parent, name, actorFn, setupFn, termFn, strategy, flags, ActorContext.System(parent.Actor.Id).Settings, this); IActorInbox inbox = null; if ((actor.Flags & ProcessFlags.ListenRemoteAndLocal) == ProcessFlags.ListenRemoteAndLocal && cluster.IsSome) { inbox = new ActorInboxDual <S, T>(); } else if ((actor.Flags & ProcessFlags.PersistInbox) == ProcessFlags.PersistInbox && cluster.IsSome) { inbox = new ActorInboxRemote <S, T>(); } else { inbox = new ActorInboxLocal <S, T>(); } var item = new ActorItem(actor, inbox, actor.Flags); parent.Actor.LinkChild(item); // Auto register if there are config settings and we // have the variable name it was assigned to. ActorContext.System(actor.Id).Settings.GetProcessRegisteredName(actor.Id).Iter(regName => { // Also check if 'dispatch' is specified in the config, if so we will // register the Process as a role dispatcher PID instead of just its // PID. ActorContext.System(actor.Id).Settings.GetProcessDispatch(actor.Id) .Match( Some: disp => Process.register(regName, Disp[$"role-{disp}"][Role.Current].Append(actor.Id.Skip(1))), None: () => Process.register(regName, actor.Id) ); }); try { inbox.Startup(actor, parent, cluster, maxMailboxSize); if (!lazy) { TellSystem(actor.Id, SystemMessage.StartupProcess); } } catch { item?.Actor?.ShutdownProcess(false); throw; } return(item.Actor.Id); }
/// <summary> /// Send a message to a process /// </summary> /// <param name="pid">Process ID to send to</param> /// <param name="message">Message to send</param> /// <param name="sender">Optional sender override. The sender is handled automatically if you do not provide one.</param> public static Unit tell <T>(ProcessId pid, T message, ProcessId sender = default(ProcessId)) => message is UserControlMessage ? ActorContext.System(pid).TellUserControl(pid, message as UserControlMessage) : ActorContext.System(pid).Tell(pid, message, Schedule.Immediate, sender);
/// <summary> /// Send a message at a specified time in the future /// </summary> /// <returns>IDisposable that you can use to cancel the operation if necessary. You do not need to call Dispose /// for any other reason.</returns> /// <param name="pid">Process ID to send to</param> /// <param name="message">Message to send</param> /// <param name="schedule">A structure that defines the method of delivery of the scheduled message</param> /// <param name="sender">Optional sender override. The sender is handled automatically if you do not provide one.</param> public static Unit tell <T>(ProcessId pid, T message, Schedule schedule, ProcessId sender = default(ProcessId)) => ActorContext.System(pid).Tell(pid, message, schedule, sender);
/// <summary> /// Register a named process (a kind of DNS for Processes). /// /// If the Process is visible to the cluster (PersistInbox) then the /// registration becomes a permanent named look-up until Process.deregister /// is called. /// /// See remarks. /// </summary> /// <remarks> /// Multiple Processes can register under the same name. You may use /// a dispatcher to work on them collectively (wherever they are in the /// cluster). i.e. /// /// var regd = register("proc", pid); /// tell(Dispatch.Broadcast[regd], "Hello"); /// tell(Dispatch.First[regd], "Hello"); /// tell(Dispatch.LeastBusy[regd], "Hello"); /// tell(Dispatch.Random[regd], "Hello"); /// tell(Dispatch.RoundRobin[regd], "Hello"); /// /// </remarks> /// <param name="name">Name to register under</param> /// <param name="process">Process to be registered</param> /// <returns>A ProcessId that allows dispatching to the process(es). The result /// would look like /disp/reg/name</returns> public static ProcessId register(ProcessName name, ProcessId process) => ActorContext.System(process).Register($"{Role.Current.Value}-{name.Value}", process);
/// <summary> /// Deregister all Processes associated with a name. NOTE: Be very careful /// with usage of this function if you didn't handle the registration you /// are potentially disconnecting many Processes from their registered name. /// /// See remarks. /// </summary> /// <remarks> /// Any Process (or dispatcher, or role, etc.) can be registered by a name - /// a kind of DNS for ProcessIds. There can be multiple names associated /// with a single ProcessId and multiple ProcessIds associated with a name. /// /// This function removes all registered ProcessIds for a specific name. /// If you wish to deregister all names registered for specific Process then /// use Process.deregisterById(pid) /// </remarks> /// <param name="name">Name of the process to deregister</param> public static Unit deregisterByName(ProcessName name, SystemName system = default(SystemName)) => ActorContext.System(system).DeregisterByName($"{Role.Current.Value}-{name.Value}");
/// <summary> /// Finds all *persistent* registered names in a role /// </summary> /// <param name="role">Role to limit search to</param> /// <param name="keyQuery">Key query. * is a wildcard</param> /// <returns>Registered names</returns> public static IEnumerable <ProcessName> queryRegistered(ProcessName role, string keyQuery, SystemName system = default(SystemName)) => ActorContext.System(system).Cluster .Map(c => c.QueryRegistered(role.Value, keyQuery)) .IfNone(List.empty <ProcessName>());
/// <summary> /// Finds all *persistent* processes based on the search pattern provided. Note the returned /// ProcessIds may contain processes that aren't currently active. You can still post /// to them however. /// </summary> /// <param name="keyQuery">Key query. * is a wildcard</param> /// <returns>Matching ProcessIds</returns> public static IEnumerable <ProcessId> queryProcesses(string keyQuery, SystemName system = default(SystemName)) => ActorContext.System(system).Cluster .Map(c => c.QueryProcesses(keyQuery)) .IfNone(new ProcessId[0]);
public override Unit Run(ProcessId pid) => ActorContext.System(pid).Settings.ClearSettingsOverride(ActorInboxCommon.ClusterSettingsKey(pid), Flags);
public static ProcessOpTransaction Start(ProcessId pid) => new ProcessOpTransaction(pid, Que <ProcessOp> .Empty, ActorContext.System(pid).Settings.GetProcessSettingsOverrides(pid));
public override Unit Run(ProcessId pid) => ActorContext.System(pid).Settings.WriteSettingOverride(ActorInboxCommon.ClusterSettingsKey(pid), Value, Name, Prop, Flags);
public static InboxDirective SystemMessageInbox <S, T>(Actor <S, T> actor, IActorInbox inbox, SystemMessage msg, ActorItem parent) { var session = msg.SessionId == null ? None : Some(new SessionId(msg.SessionId)); return(ActorContext.System(actor.Id).WithContext(new ActorItem(actor, inbox, actor.Flags), parent, ProcessId.NoSender, null, msg, session, () => { switch (msg.Tag) { case Message.TagSpec.Restart: actor.Restart(inbox.IsPaused); break; case Message.TagSpec.LinkChild: var lc = msg as SystemLinkChildMessage; actor.LinkChild(lc.Child); break; case Message.TagSpec.UnlinkChild: var ulc = (msg as SystemUnLinkChildMessage).SetSystem(actor.Id.System); actor.UnlinkChild(ulc.Child); break; case Message.TagSpec.ChildFaulted: var cf = (msg as SystemChildFaultedMessage).SetSystem(actor.Id.System); return actor.ChildFaulted(cf.Child, cf.Sender, cf.Exception, cf.Message); case Message.TagSpec.StartupProcess: var startupProcess = msg as StartupProcessMessage; var inboxDirective = actor.Startup(); // get feedback whether startup will somehow trigger Unpause itself (i.e. error => strategy => restart) if (startupProcess.UnpauseAfterStartup && !inboxDirective.HasFlag(InboxDirective.Pause)) { inbox.Unpause(); } break; case Message.TagSpec.ShutdownProcess: var shutdownProcess = msg as ShutdownProcessMessage; actor.ShutdownProcess(shutdownProcess.MaintainState); break; case Message.TagSpec.Unpause: inbox.Unpause(); break; case Message.TagSpec.Pause: inbox.Pause(); break; // do not return InboxDirective.Pause because system queue should never pause case Message.TagSpec.Watch: var awm = msg as SystemAddWatcherMessage; actor.AddWatcher(awm.Id); break; case Message.TagSpec.UnWatch: var rwm = msg as SystemRemoveWatcherMessage; actor.RemoveWatcher(rwm.Id); break; case Message.TagSpec.DispatchWatch: var dwm = msg as SystemDispatchWatchMessage; actor.DispatchWatch(dwm.Id); break; case Message.TagSpec.DispatchUnWatch: var duwm = msg as SystemDispatchUnWatchMessage; actor.DispatchUnWatch(duwm.Id); break; } return InboxDirective.Default; })); }
public static Tuple <long, Dictionary <long, AskActorReq> > Inbox(Tuple <long, Dictionary <long, AskActorReq> > state, object msg) { var reqId = state.Item1; var dict = state.Item2; if (msg is AskActorReq) { reqId++; var req = (AskActorReq)msg; ActorContext.System(req.To).Ask(req.To, new ActorRequest(req.Message, req.To, Self, reqId), Self); dict.Add(reqId, req); } else { var res = (ActorResponse)msg; if (dict.ContainsKey(res.RequestId)) { var req = dict[res.RequestId]; try { if (res.IsFaulted) { Exception ex = null; // Let's be reeeally safe here and do everything we can to get some valid information out of // the response to report to the process doing the 'ask'. try { var msgtype = req.ReplyType; if (msgtype == res.Message.GetType() && typeof(Exception).GetTypeInfo().IsAssignableFrom(msgtype.GetTypeInfo())) { // Type is fine, just return it as an error ex = (Exception)res.Message; } else { if (res.Message is string) { ex = (Exception)Deserialise.Object(res.Message.ToString(), msgtype); } else { ex = (Exception)Deserialise.Object(JsonConvert.SerializeObject(res.Message), msgtype); } } } catch { ex = new Exception(res.Message?.ToString() ?? $"An unknown error was thrown by {req.To}"); } req.Complete(new AskActorRes(new ProcessException($"Process issue: {ex.Message}", req.To.Path, req.ReplyTo.Path, ex), req.ReplyType)); } else { req.Complete(new AskActorRes(res.Message, req.ReplyType)); } } catch (Exception e) { req.Complete(new AskActorRes(new ProcessException($"Process issue: {e.Message}", req.To.Path, req.ReplyTo.Path, e), req.ReplyType)); logSysErr(e); } finally { dict.Remove(res.RequestId); } } else { logWarn($"Request ID doesn't exist: {res.RequestId}"); } } return(new Tuple <long, Dictionary <long, AskActorReq> >(reqId, dict)); }
private IEnumerable <IActorDispatch> GetWorkers() => group.Map(pid => ActorContext.System(pid).GetDispatcher(pid));
/// <summary> /// Finds all *persistent* processes based on the search pattern provided and then returns the /// meta-data associated with them. /// </summary> /// <param name="keyQuery">Key query. * is a wildcard</param> /// <returns>Map of ProcessId to ProcessMetaData</returns> public static HashMap <ProcessId, ProcessMetaData> queryProcessMetaData(string keyQuery, SystemName system = default(SystemName)) => ActorContext.System(system).Cluster .Map(c => c.QueryProcessMetaData(keyQuery)) .IfNone(HashMap.empty <ProcessId, ProcessMetaData>());
/// <summary> /// Register a named process (a kind of DNS for Processes). /// /// If the Process is visible to the cluster (PersistInbox) then the /// registration becomes a permanent named look-up until Process.deregister /// is called. /// /// See remarks. /// </summary> /// <remarks> /// Multiple Processes can register under the same name. You may use /// a dispatcher to work on them collectively (wherever they are in the /// cluster). i.e. /// /// var regd = register("proc"); /// tell(Dispatch.Broadcast[regd], "Hello"); /// tell(Dispatch.First[regd], "Hello"); /// tell(Dispatch.LeastBusy[regd], "Hello"); /// tell(Dispatch.Random[regd], "Hello"); /// tell(Dispatch.RoundRobin[regd], "Hello"); /// /// This should be used from within a process' message loop only /// </remarks> /// <param name="name">Name to register under</param> /// <returns>A ProcessId that allows dispatching to the process via the name. The result /// would look like /disp/reg/name</returns> public static ProcessId register(ProcessName name, SystemName system = default(SystemName)) => InMessageLoop ? ActorContext.System(system).Register($"{Role.Current.Value}-{name.Value}", Self) : raiseUseInMsgLoopOnlyException <ProcessId>(nameof(name));
private static void StartFromConfig(ProcessSystemConfig config) { lock (sync) { InitLocalScheduler(); config.Cluster.Match( Some: _ => { // Extract cluster settings var provider = config.GetClusterSetting("provider", "value", "redis"); var role = config.GetClusterSetting("role", "value", name => clusterSettingMissing <string>(name)); var clusterConn = config.GetClusterSetting("connection", "value", "localhost"); var clusterDb = config.GetClusterSetting("database", "value", "0"); var env = config.SystemName; var userEnv = config.GetClusterSetting <string>("user-env", "value"); var appProfile = new AppProfile( config.NodeName, role, clusterConn, clusterDb, env, userEnv ); // Look for an existing actor-system with the same system name var current = ActorContext.Systems.Filter(c => c.Value == env).HeadOrNone(); // Work out if something significant has changed that will cause us to restart var restart = current.Map(ActorContext.System) .Map(c => c.AppProfile.NodeName != appProfile.NodeName || c.AppProfile.Role != appProfile.Role || c.AppProfile.ClusterConn != appProfile.ClusterConn || c.AppProfile.ClusterDb != appProfile.ClusterDb); // Either restart / update settings / or start new restart.Match( Some: r => { if (r) { // Restart try { ActorContext.StopSystem(env); } catch (Exception e) { logErr(e); } StartFromConfig(config); } else { // Update settings ActorContext.System(env).UpdateSettings(config, appProfile); var cluster = from systm in current.Map(ActorContext.System) from clstr in systm.Cluster select clstr; } }, None: () => { // Start new ICluster cluster = Cluster.connect( provider, config.NodeName, clusterConn, clusterDb, role ); ActorContext.StartSystem(env, Optional(cluster), appProfile, config); config.PostConnect(); }); }, None: () => { ActorContext.StartSystem(new SystemName(""), None, AppProfile.NonClustered, config); }); } }
/// <summary> /// Deregister a Process from any names it's been registered as. /// /// See remarks. /// </summary> /// <remarks> /// Any Process (or dispatcher, or role, etc.) can be registered by a name - /// a kind of DNS for ProcessIds. There can be multiple names associated /// with a single ProcessId. /// /// This function removes all registered names for a specific ProcessId. /// If you wish to deregister all ProcessIds registered under a name then /// use Process.deregisterByName(name) /// </remarks> /// <param name="process">Process to be deregistered</param> public static Unit deregisterById(ProcessId process) => ActorContext.System(process).DeregisterById(process);
/// <summary> /// Access a setting /// If in a Process message loop, then this accesses the configuration settings /// for the Process from the the configuration file, or stored in the cluster. /// If not in a Process message loop, then this accesses 'global' configuration /// settings. /// </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> /// <returns>Optional configuration setting value</returns> public static T read <T>(string name, string prop, T defaultValue, SystemName system = default(SystemName)) => InMessageLoop ? ActorContext.System(Self).Settings.GetProcessSetting(Self, name, prop, defaultValue, ActorContext.Request.ProcessFlags) : ActorContext.System(system).Settings.GetRoleSetting(name, prop, defaultValue);
/// <summary> /// Find a process by its *registered* name (a kind of DNS for Processes) in the /// role specified. /// /// See remarks. /// </summary> /// <remarks> /// Multiple Processes can register under the same name. You may use /// a dispatcher to work on them collectively (wherever they are in the /// cluster). i.e. /// /// var regd = register("proc", pid); /// tell(Dispatch.Broadcast[regd], "Hello"); /// tell(Dispatch.First[regd], "Hello"); /// tell(Dispatch.LeastBusy[regd], "Hello"); /// tell(Dispatch.Random[regd], "Hello"); /// tell(Dispatch.RoundRobin[regd], "Hello"); /// /// </remarks> /// <param name="role">Process role</param> /// <param name="name">Process name</param> /// <returns>A ProcessId that allows dispatching to the process(es). The result /// would look like /disp/reg/name</returns> public static ProcessId find(ProcessName role, ProcessName name) => ActorContext.System(default(SystemName)).Disp["reg"][$"{role.Value}-{name.Value}"];
/// <summary> /// Starts a new session in the Process system with the specified /// session ID /// </summary> /// <param name="sid">Session ID</param> /// <param name="timeout">Session timeout</param> public static SessionId sessionStart(SessionId sid, Time timeout, SystemName system) { ActorContext.System(system).Sessions.Start(sid, (int)(timeout / 1.Seconds())); ActorContext.SessionId = sid; return(sid); }
/// <summary> /// Send a message to a process /// </summary> /// <param name="pid">Process ID to send to</param> /// <param name="message">Message to send</param> /// <param name="sender">Optional sender override. The sender is handled automatically if you do not provide one.</param> internal static Unit tellSystem <T>(ProcessId pid, T message, ProcessId sender = default(ProcessId)) => ActorContext.System(pid).TellSystem(pid, message as SystemMessage);
/// <summary> /// Ask a process for a reply /// </summary> /// <param name="pid">Process to ask</param> /// <param name="message">Message to send</param> /// <param name="sender">Sender process</param> /// <returns>The response to the request</returns> public static T ask <T>(ProcessId pid, object message, ProcessId sender) => ActorContext.System(pid).Ask <T>(pid, message, sender);
/// <summary> /// Send a message at a specified time in the future /// </summary> /// <remarks> /// It is advised to use the variant that takes a TimeSpan, this will fail to be accurate across a Daylight Saving /// Time boundary or if you use non-UTC dates /// </remarks> /// <returns>IDisposable that you can use to cancel the operation if necessary. You do not need to call Dispose /// for any other reason.</returns> /// <param name="pid">Process ID to send to</param> /// <param name="message">Message to send</param> /// <param name="delayUntil">Date and time to send</param> /// <param name="sender">Optional sender override. The sender is handled automatically if you do not provide one.</param> public static Unit tell <T>(ProcessId pid, T message, DateTime delayUntil, ProcessId sender = default(ProcessId)) => ActorContext.System(pid).Tell(pid, message, Schedule.Ephemeral(delayUntil), sender);
/// <summary> /// Ask a process for a reply (if the process is running). If the process isn't running /// then None is returned /// </summary> /// <param name="pid">Process to ask</param> /// <param name="message">Message to send</param> /// <param name="sender">Sender process</param> /// <returns>The response to the request or None if the process isn't running</returns> public static Option <T> askIfAlive <T>(ProcessId pid, object message, ProcessId sender) => ping(pid) ? Optional(ActorContext.System(pid).Ask <T>(pid, message, sender)) : None;
public Unit DispatchUnWatch(ProcessId pid) { sys.GetDispatcher(pid).UnWatch(Id); return(ActorContext.System(Id).RemoveWatcher(pid, Id)); }
/// <summary> /// Ask children the same message /// </summary> /// <param name="message">Message to send</param> /// <returns></returns> public static IEnumerable <T> askChildren <T>(object message, int take = Int32.MaxValue) => ActorContext.System(default(SystemName)).AskMany <T>(Children.Values, message, take);
/// <summary> /// Not advised to use this directly, but allows access to the underlying data-store. /// </summary> public static Option <ICluster> SystemCluster(SystemName system = default(SystemName)) => ActorContext.System(system).Cluster;
/// <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);