StrategyContext( StrategyState global, Exception exception, object message, ProcessId sender, ProcessId failedProcess, ProcessId parentProcess, IEnumerable <ProcessId> siblings, IEnumerable <ProcessId> affects, Time pause, Option <Directive> directive, Option <MessageDirective> messageDirective ) { bool isStop = directive == Echo.Directive.Stop; Global = isStop ? StrategyState.Empty : global; Exception = exception; Message = message; Sender = sender; Self = failedProcess; ParentProcess = parentProcess; Siblings = siblings ?? Siblings; Affects = affects ?? Affects; Pause = isStop ? 0 * s : pause; Directive = directive; MessageDirective = messageDirective; }
public InboxDirective ProcessTerminated(ProcessId pid) { if (termFn == null) { return(InboxDirective.Default); } lock (sync) { var savedReq = ActorContext.Request.CurrentRequest; var savedFlags = ActorContext.Request.ProcessFlags; var savedMsg = ActorContext.Request.CurrentMsg; try { ActorContext.Request.CurrentRequest = null; ActorContext.Request.ProcessFlags = flags; ActorContext.Request.CurrentMsg = pid; //ActorContext.AssertSession(); var stateIn = GetState(); var stateOut = termFn(stateIn, pid); state = stateOut; try { if (!default(EqDefault <S>).Equals(stateOut, stateIn)) { stateSubject.OnNext(stateOut); } } catch (Exception ue) { // Not our errors, so just log and move on logErr(ue); } strategyState = strategyState.With( Failures: 0, LastFailure: DateTime.MaxValue, BackoffAmount: 0 * seconds ); ActorContext.Request.RunOps(); } catch (Exception e) { return(DefaultErrorHandler(pid, e)); } finally { ActorContext.Request.CurrentRequest = savedReq; ActorContext.Request.ProcessFlags = savedFlags; ActorContext.Request.CurrentMsg = savedMsg; } return(InboxDirective.Default); } }
/// <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); } }
/// <summary> /// Shutdown everything from this node down /// </summary> public Unit Shutdown(bool maintainState) { 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; DisposeState(); sys.DispatchTerminate(Id); return(unit); } }
public StrategyContext With( StrategyState Global = null, Exception Exception = null, object Message = null, ProcessId?Sender = null, ProcessId?FailedProcess = null, ProcessId?ParentProcess = null, IEnumerable <ProcessId> Siblings = null, IEnumerable <ProcessId> Affects = null, Time?Pause = null, Option <Directive> Directive = default(Option <Directive>), Option <MessageDirective> MessageDirective = default(Option <MessageDirective>) ) => new StrategyContext( Global ?? this.Global, Exception ?? this.Exception, Message ?? this.Message, Sender ?? this.Sender, FailedProcess ?? this.Self, ParentProcess ?? this.ParentProcess, Siblings ?? this.Siblings, Affects ?? this.Affects, Pause ?? this.Pause, // The following may look like bugs, but it's intentional that when // a Directive or MessageDirective is set, they stay set to their first // concrete value. That means the State strategy expression that runs // has the equivalent of an 'early out'. The whole expression is still // processed, but you can't override the earlier value. this.Directive.IsSome ? this.Directive : Directive, this.MessageDirective.IsSome ? this.MessageDirective : MessageDirective );
public InboxDirective RunStrategy( ProcessId pid, ProcessId parent, ProcessId sender, IEnumerable <ProcessId> siblings, Exception ex, object message, State <StrategyContext, Unit> strategy ) { try { // Build a strategy specifically for this event var failureStrategy = strategy.Failure( pid, parent, sender, siblings, ex, message ); // Invoke the strategy with the running state var result = failureStrategy.Run(strategyState); return(result.Value.Match( Some: decision => { // Save the strategy state back to the actor strategyState = result.State; if (decision.ProcessDirective.Type != DirectiveType.Stop && decision.Pause > 0 * seconds) { decision.Affects.Iter(p => pause(p)); safedelay( () => RunProcessDirective(pid, sender, ex, message, decision, true), decision.Pause ); return InboxDirective.Pause | RunMessageDirective(pid, sender, decision, ex, message); } else { // Run the instruction for the Process (stop/restart/etc.) try { RunProcessDirective(pid, sender, ex, message, decision, false); } catch (Exception e) { // Error in RunProcessDirective => log and still run RunMessageDirective logErr("Strategy exception (RunProcessDirective) in " + Id, e); } // Run the instruction for the message (dead-letters/send-to-self/...) return RunMessageDirective(pid, sender, decision, ex, message); } }, None: () => { logErr("Strategy failed (no decision) in " + Id); return InboxDirective.Default; }, Fail: e => { logErr("Strategy exception (decision error) " + Id, e); return InboxDirective.Default; })); } catch (Exception e) { logErr("Strategy exception in " + Id, e); return(InboxDirective.Default); } }
/// <summary> /// Process an inbox message /// </summary> /// <param name="message"></param> /// <returns></returns> public InboxDirective ProcessMessage(object message) { lock (sync) { if (cancellationTokenSource.IsCancellationRequested) { return(InboxDirective.Shutdown); } var savedReq = ActorContext.Request.CurrentRequest; var savedFlags = ActorContext.Request.ProcessFlags; var savedMsg = ActorContext.Request.CurrentMsg; try { ActorContext.Request.CurrentRequest = null; ActorContext.Request.ProcessFlags = flags; ActorContext.Request.CurrentMsg = message; if (message is T) { var stateIn = GetState(); var stateOut = actorFn(stateIn, (T)message); state = stateOut; try { if (!default(EqDefault <S>).Equals(stateOut, stateIn)) { stateSubject.OnNext(stateOut); } } catch (Exception ue) { // Not our errors, so just log and move on logErr(ue); } } else if (typeof(T) != typeof(string) && message is string) { state = PreProcessMessageContent(message).Match( Some: tmsg => { var stateIn = GetState(); var stateOut = actorFn(stateIn, tmsg); try { if (!default(EqDefault <S>).Equals(stateOut, stateIn)) { stateSubject.OnNext(stateOut); } } catch (Exception ue) { // Not our errors, so just log and move on logErr(ue); } return(stateOut); }, None: () => state ); } else if (message is Message) { ProcessSystemMessage((Message)message); } else { logErr($"Can't tell {Id.Path}, message is not {typeof(T).GetTypeInfo().Name} : {message}"); return(InboxDirective.Default); } strategyState = strategyState.With( Failures: 0, LastFailure: DateTime.MaxValue, BackoffAmount: 0 * seconds ); ActorContext.Request.RunOps(); } catch (Exception e) { return(DefaultErrorHandler(message, e)); } finally { ActorContext.Request.CurrentRequest = savedReq; ActorContext.Request.ProcessFlags = savedFlags; ActorContext.Request.CurrentMsg = savedMsg; } return(InboxDirective.Default); } }
public InboxDirective ProcessAsk(ActorRequest request) { lock (sync) { if (cancellationTokenSource.IsCancellationRequested) { replyError(new AskException( $"Can't ask {Id.Path} because actor shutdown in progress")); return(InboxDirective.Shutdown); } var savedMsg = ActorContext.Request.CurrentMsg; var savedFlags = ActorContext.Request.ProcessFlags; var savedReq = ActorContext.Request.CurrentRequest; try { ActorContext.Request.CurrentRequest = request; ActorContext.Request.ProcessFlags = flags; ActorContext.Request.CurrentMsg = request.Message; //ActorContext.AssertSession(); if (typeof(T) != typeof(string) && request.Message is string) { state = PreProcessMessageContent(request.Message).Match( Some: tmsg => { var stateIn = GetState(); var stateOut = actorFn(stateIn, tmsg); try { if (!default(EqDefault <S>).Equals(stateOut, stateIn)) { stateSubject.OnNext(stateOut); } } catch (Exception ue) { // Not our errors, so just log and move on logErr(ue); } return(stateOut); }, None: () => { replyError(new AskException( $"Can't ask {Id.Path}, message is not {typeof(T).GetTypeInfo().Name} : {request.Message}")); return(state); } ); } else if (request.Message is T msg) { var stateIn = GetState(); var stateOut = actorFn(stateIn, msg); try { if (!default(EqDefault <S>).Equals(stateOut, stateIn)) { stateSubject.OnNext(stateOut); } } catch (Exception ue) { // Not our errors, so just log and move on logErr(ue); } state = stateOut; } else if (request.Message is Message m) { ProcessSystemMessage(m); } else { // Failure to deserialise is not our problem, its the sender's // so we don't throw here. replyError(new AskException( $"Can't ask {Id.Path}, message is not {typeof(T).GetTypeInfo().Name} : {request.Message}")); return(InboxDirective.Default); } strategyState = strategyState.With( Failures: 0, LastFailure: DateTime.MaxValue, BackoffAmount: 0 * seconds ); ActorContext.Request.RunOps(); } catch (Exception e) { ActorContext.Request.SetOps(ProcessOpTransaction.Start(Id)); replyError(e); ActorContext.Request.RunOps(); return(DefaultErrorHandler(request, e)); } finally { ActorContext.Request.CurrentMsg = savedMsg; ActorContext.Request.ProcessFlags = savedFlags; ActorContext.Request.CurrentRequest = savedReq; } return(InboxDirective.Default); } }