internal Actor( Option <ICluster> cluster, ActorItem parent, ProcessName name, Func <S, T, S> actor, Func <IActor, S> setup, Func <S, ProcessId, S> term, State <StrategyContext, Unit> strategy, ProcessFlags flags, ProcessSystemConfig settings, IActorSystem sys ) { setupFn = setup ?? throw new ArgumentNullException(nameof(setup)); actorFn = actor ?? throw new ArgumentNullException(nameof(actor)); this.sys = sys; Id = parent.Actor.Id[name]; this.cluster = cluster; this.flags = flags == ProcessFlags.Default ? settings.GetProcessFlags(Id) : flags; termFn = term; Parent = parent; Name = name; Strategy = strategy; SetupRemoteSubscriptions(cluster, flags); }
public ActorSystemBootstrap(IActorSystem system, Option <ICluster> cluster, ProcessId rootId, IActor rootProcess, IActorInbox rootInbox, ProcessName rootProcessName, ActorSystemConfig config, ProcessSystemConfig settings, SessionSync sync) { System = system; Sync = sync; var parent = new ActorItem(new NullProcess(system.Name), new NullInbox(), ProcessFlags.Default); rootProcess = new Actor <ActorSystemBootstrap, Unit>( cluster, parent, rootProcessName, SystemInbox, _ => this, null, Process.DefaultStrategy, ProcessFlags.Default, settings, system ); root = new ActorItem(rootProcess, rootInbox, rootProcess.Flags); Config = config; Settings = settings; Cluster = cluster; RootProcess = rootProcess; RootInbox = rootInbox; RootProcessName = rootProcessName; }
public Unit UpdateSettings(ProcessSystemConfig settings, AppProfile profile) { this.settings = settings; this.appProfile = profile; // TODO: Consider notification system for Processes return(unit); }
/// <summary> /// Resets the configuration system to all default settings (i.e. empty). Use this call followed by /// one of the ProcessConfig.initialise(...) variants to reload new configuration settings live. /// </summary> public static Unit unload() { lock (sync) { appProfile = null; config = new ProcessSystemConfig(""); processSettings = Map.empty <string, object>(); return(unit); } }
/// <summary> /// Process system configuration initialisation /// This will parse the configuration text, you should call this /// function from within Application_BeginRequest of Global.asax. It can run multiple times, once the config /// has loaded the system won't re-load the config until you call ProcessConfig.unload() followed by /// ProcessConfig.initialiseFileSystem(...), so it's safe to not surround it with ifs. /// /// NOTE: If a cluster is specified in the cluster.conf and its 'node-name' matches nodeName, then those settings /// will be used to connect to the cluster. This allows for different staging environments to be setup. /// </summary> /// <param name="nodeName">If a cluster is specified in the cluster.conf and its 'node-name' matches nodeName, then /// those settings will be used to connect to the cluster. This allows for different staging environments to be /// setup.</param> /// <param name="setup">A setup function to call on successful loading of the configuration files - this will /// happen once only.</param> /// <param name="strategyFuncs">Plugin extra strategy behaviours by passing in a list of FuncSpecs.</param> public static AppProfile initialise(string configText, Option <string> nodeName, Action <AppProfile> setup = null, IEnumerable <FuncSpec> strategyFuncs = null) { if (appProfile != null) { return(appProfile); } lock (sync) { if (appProfile != null) { return(appProfile); } config = new ProcessSystemConfig(nodeName.IfNone(""), strategyFuncs); config.ParseConfigText(configText); appProfile = AppProfile.NonClustered; nodeName.Iter(node => { 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.GetClusterSetting <string>("env", "value"); var userEnv = config.GetClusterSetting <string>("user-env", "value"); appProfile = new AppProfile( node, role, clusterConn, clusterDb, env, userEnv ); Cluster.connect(provider, node, clusterConn, clusterDb, role); }); config.PostConnect(); setup(appProfile); return(appProfile); } }
public static Unit StartSystem(SystemName system, Option<ICluster> cluster, AppProfile appProfile, ProcessSystemConfig config) { lock (sync) { if (systems.ContainsKey(system)) { throw new InvalidOperationException($"Process-system ({system}) already started"); } var asystem = new ActorSystem(system, cluster, appProfile, config); systems.AddOrUpdate(system, asystem, (_, __) => asystem); try { asystem.Initialise(); // Set the default system if the 'default: yes' setting is in the ProcessSystemConfig defaultSystem = defaultSystem.IsValid ? (from c in config.Cluster where c.Default select system) .IfNone(defaultSystem) : system; } catch { systems.TryRemove(system, out asystem); try { asystem.Dispose(); } catch { } throw; } return unit; } }
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); }); } }
public ActorSystem(SystemName systemName, Option <ICluster> cluster, AppProfile appProfile, ProcessSystemConfig settings) { var name = GetRootProcessName(cluster); if (name.Value == "root" && cluster.IsSome) { throw new ArgumentException("Cluster node name cannot be 'root', it's reserved for local use only."); } if (name.Value == "disp" && cluster.IsSome) { throw new ArgumentException("Cluster node name cannot be 'disp', it's reserved for internal use."); } if (name.Value == "js") { throw new ArgumentException("Node name cannot be 'js', it's reserved for ProcessJS."); } SystemName = systemName; this.appProfile = appProfile; this.settings = settings; this.cluster = cluster; Ping = new Ping(this); startupTimestamp = DateTime.UtcNow.Ticks; sessionManager = new SessionManager(cluster, SystemName, appProfile.NodeName, VectorConflictStrategy.Branch); watchers = Map <ProcessId, Set <ProcessId> >(); watchings = Map <ProcessId, Set <ProcessId> >(); startupSubscription = NotifyCluster(cluster, startupTimestamp); Dispatch.init(); Role.init(cluster.Map(r => r.Role).IfNone("local")); Reg.init(); var root = ProcessId.Top.Child(GetRootProcessName(cluster)); var rootInbox = new ActorInboxLocal <ActorSystemBootstrap, Unit>(); var parent = new ActorItem(new NullProcess(SystemName), new NullInbox(), ProcessFlags.Default); var state = new ActorSystemBootstrap( this, cluster, root, null, rootInbox, cluster.Map(x => x.NodeName).IfNone(ActorSystemConfig.Default.RootProcessName), ActorSystemConfig.Default, Settings, sessionManager.Sync ); var rootProcess = state.RootProcess; state.Startup(); rootItem = new ActorItem(rootProcess, rootInbox, ProcessFlags.Default); Root = rootItem.Actor.Id; RootJS = Root["js"]; System = Root[ActorSystemConfig.Default.SystemProcessName]; User = Root[ActorSystemConfig.Default.UserProcessName]; Errors = System[ActorSystemConfig.Default.ErrorsProcessName]; DeadLetters = System[ActorSystemConfig.Default.DeadLettersProcessName]; NodeName = cluster.Map(c => c.NodeName).IfNone("user"); AskId = System[ActorSystemConfig.Default.AskProcessName]; Disp = ProcessId.Top["disp"].SetSystem(SystemName); userContext = new ActorRequestContext( this, rootProcess.Children["user"], ProcessId.NoSender, rootItem, null, null, ProcessFlags.Default, null); rootInbox.Startup(rootProcess, parent, cluster, settings.GetProcessMailboxSize(rootProcess.Id)); }
public static Unit StartSystem(SystemName system, Option <ICluster> cluster, AppProfile appProfile, ProcessSystemConfig config) { lock (sync) { if (systems.ContainsKey(system)) { throw new InvalidOperationException($"Process-system ({system}) already started"); } var asystem = new ActorSystem(system, cluster, appProfile, config); systems.AddOrUpdate(system, asystem, (_, __) => asystem); try { asystem.Initialise(); // Set the default system if the 'default: yes' setting is in the ProcessSystemConfig defaultSystem = defaultSystem.IsValid ? (from c in config.Cluster where c.Default select system) .IfNone(defaultSystem) : system; } catch { systems.TryRemove(system, out asystem); try { asystem.Dispose(); } catch { } throw; } return(unit); } }
public static Unit StartSystem(SystemName system, Option <ICluster> cluster, AppProfile appProfile, ProcessSystemConfig config) { lock (sync) { if (SystemExists(system)) { throw new InvalidOperationException($"Process-system ({system}) already started"); } var asystem = new ActorSystem(system, cluster, appProfile, config); AddOrUpdateSystem(asystem); try { asystem.Initialise(); // Set the default system if the 'default: yes' setting is in the ProcessSystemConfig defaultSystem = defaultSystem.IsValid ? (from c in config.Cluster where c.Default select system) .IfNone(defaultSystem) : system; } catch { systems = systems.Filter(a => a.SystemName != system).ToArray(); try { asystem.Dispose(); } catch { } throw; } return(unit); } }
public void ProcessesSettingsParserTest() { var text = @" time timeout: 30 seconds time session-timeout: 60 seconds int mailbox-size: 10000 strategy my-strategy: one-for-one: retries: count = 5, duration=30 seconds backoff: min = 2 seconds, max = 1 hour, step = 5 seconds match | System.NotImplementedException -> stop | System.ArgumentNullException -> escalate | _ -> restart redirect when | restart -> forward-to-parent | escalate -> forward-to-self | stop -> forward-to-process /root/test/567 | _ -> forward-to-dead-letters strategy my-other-strategy: one-for-one: pause: 1 second always: resume redirect: forward-to-process /root/test/567 process abc: pid: /root/test/123 flags: [persist-inbox, persist-state, remote-publish] mailbox-size: 1000 strategy: my-strategy process def: pid: /root/test/567 flags: [persist-inbox, persist-state] mailbox-size: 100 strategy: my-other-strategy "; var config = new ProcessSystemConfig("test"); var res = parse(config.Parser, text); // TODO: Restore tests //Assert.False(res.IsFaulted); //var result = res.Reply.Result; //Assert.True(result.Count == 7); //var timeout = result["timeout"]; //var session = result["session-timeout"]; //var mailbox= result["mailbox-size"]; // Load process settings //var processes = M.createRange(from val in result.Values // where val.Spec.Args.Length > 0 && val.Spec.Args[0].Type.Tag == ArgumentTypeTag.Process // let p = (ProcessToken)val.Values.Values.First().Value // where p.ProcessId.IsSome // let id = p.ProcessId.IfNone(ProcessId.None) // select Tuple(id, p)); //var strats = M.createRange(from val in result.Values // where val.Spec.Args.Length > 0 && val.Spec.Args[0].Type.Tag == ArgumentTypeTag.Strategy // let s = (StrategyToken)val.Values.Values.First().Value // select Tuple(val.Name, s)); //Assert.True(timeout.Name == "timeout"); //Assert.True(timeout.Attributes.Count == 1); //Assert.True(timeout.Attributes["value"].Type.Tag == ArgumentTypeTag.Time); //Assert.True((Time)timeout.Attributes["value"].Value == 30*seconds); //Assert.True(session.Name == "session-timeout"); //Assert.True(session.Attributes.Count == 1); //Assert.True(session.Attributes["value"].Type.Tag == ArgumentTypeTag.Time); //Assert.True((Time)session.Attributes["value"].Value == 60 * seconds); //Assert.True(mailbox.Name == "mailbox-size"); //Assert.True(mailbox.Attributes.Count == 1); //Assert.True(mailbox.Attributes["value"].Type.Tag == ArgumentTypeTag.Int); //Assert.True((int)mailbox.Attributes["value"].Value == 10000); //Assert.True(strats.Count == 2); //Assert.True(processes.Count == 2); }
static ProcessConfig() { config = new ProcessSystemConfig(""); }