Giter VIP home page Giter VIP logo

statemachine's Introduction

I'm working on moving the documention from www.appccelerate.com (where it is not up-to-date :-( ) to here because it is less time consuming to update the documentation.

Features

  • use enums, ints, strings or your own class for states and events - resulting in single class state machines.
  • transition, entry and exit actions.
  • transition guards
  • hierarchical states with history behavior to initialize state always to same state or last active state.
  • supports async/await
  • fluent definition syntax.
  • passive state machine handles state transitions synchronously.
  • active state machine handles state transitions asynchronously on the worker thread of the state machine.
  • active state machine is thread-safe
  • current state, queued events and history states can be persisted
  • extension support to extend functionality of state machine.
  • extensible thorough logging simplifies debugging.
  • state machine reports as text, csv or yEd diagram. Or write your own report.

StateMachine

Appccelerate contains four different state machines:

  • Passive State Machine
  • Active State Machine
  • Async Passive State Machine
  • Async Active State Machine

Both async and not-async variants implement an interface called IStateMachine. For better testability and flexibility, I suggest that you reference your state machine only by the interface and use dependency injection to get either of the implementations. You can use then a passive state machine as a replacement for an active one in your tests to simplify the tests because everything is run on the same thread.

States and Events

A state machine is defined using States and Events. States and events have to be IComparables (enum, int, string, ...). If you have a well known set of states and events then I suggest you use enums which make the code much more readable. If you plan to build some flexibility into your state machine (e.g. add states, transitions in base classes) then you better use an "open" type like string or integer.

Transitions

Transitions are state switches that are executed in response to an event that was fired onto the state machine. You can define per state and event which transition is taken and therefore which state to go to.

Actions

You can define actions either on transitions or on entry or exit of a state. Transition actions are executed when the transition is taken as the response to an event. The entry and exit actions of a state are excuted when the state machine enters or exits the state due to a taken transition. In case of hierarchical states, multiple entry and exit actions can be executed.

Guards

Guards give you the possibility to decide which transition is executed depending on a boolean criteria. When an event is fired onto the state machine, it takes all transitions defined in the current state for the fired event and executes the first transition with a guard returning true.

Extensions

Extensions can be used to extend the functionality of the state machine. They provide for example a simple way to write loggers.

Reports

Out of the box, Appccelerate provides a textual report, a csv report and a yEd diagram reporter. You can add your own reports by just implementing IStateMachineReport.

Simple Sample State Machine

public class SimpleStateMachine
{
    private enum States
    {
        On,
        Off
    }

    private enum Events
    {
        TurnOn,
        TurnOff
    }

    private readonly PassiveStateMachine<States, Events> machine;

    public SimpleStateMachine()
    {
        var builder = new StateMachineDefinitionBuilder<States, Events>();

        builder
            .In(States.Off)
            .On(Events.TurnOn)
            .Goto(States.On)
            .Execute(SayHello);

        builder
            .In(States.On)
            .On(Events.TurnOff)
            .Goto(States.Off)
            .Execute(SayBye);

        builder
            .WithInitialState(States.Off);

        machine = builder
            .Build()
            .CreatePassiveStateMachine();

        machine.Start();
    }

    public void TurnOn()
    {
        machine
            .Fire(
                Events.TurnOn);
    }

    public void TurnOff()
    {
        machine
            .Fire(
                Events.TurnOff);
    }

    private void SayHello()
    {
        Console.WriteLine("hello");
    }

    private void SayBye()
    {
        Console.WriteLine("bye");
    }
}

More Documentation

statemachine's People

Contributors

philippdolder avatar ursenzler avatar wtjerry avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

statemachine's Issues

Question: treat two events the same way

Is it possible to react to two different events in the same way?

Example:

_stateMachine.In(States.A)
     .On(Events.B).Or(Events.C)
     .If(Something).Goto(States.C).Execute(ComplicatedStuff)
     .If(SomethingElse).Goto(States.D).Execute(MoreStuff)
     .Otherwise().Goto(States.E).Execute(Cleanup);

I want to not copy the whole if-otherwise-lines for the second event

Compile your awsome library to target .NET 4.6 and Silverlight 5

Hi,
First of all I want to say that, after searching for libraries that implement State Machines, your library is awesome! The API is simple and fluent and the functionality very powerful.
We want to use your library in a Lightswitch project. To achieve this we must compile your library targeting .NET 4.6 and Silverlight 5 (Portable Class Library).
Is this possible?

PassiveStateMachine fails to execute events fired by different callers on their respective threads. Is this expected?

While one caller is executing an event if different fires event it is returned after adding it to queue. Meanwhile, the first caller tries to execute the newly added event in the queue before it returns.

This creates an issue with Stop behavior as while one caller is busy with executing events in the queue if other callers go on adding events to queue still the state machine will be running till all events have been executed.

This is different from the description given for PassiveStateMachine related to State Machine Events Execution.

Async/await support for Guard/Transition methods

Hi,
Does the PassiveStateMachine support async/await as guard and/or transition methods? Will it work correctly if async/await is used?

// guard method
private async bool GuardThis()
{
return Task.FromResult(bool);
}

//transition method
private async void TransitionThere()
{
try
{
await MyAwaitableMethod();
}
catch (Exception e)
{
exceptions.Add(e);
}
}

Thanks,
David

Error Building Source Pull (Requires CheckHintPathTask 1.7.0)

When I tried to build a source pull of Appccelerate.StateMachine, NuGet reports an error when I try to download dependencies.

NuGet reports that the project depends on Appccelerate.StateMachine requires Appccelerate.CheckHintPathTask 1.7.0; however, Appccelerate.CheckHintPathTask 1.4.0 is the latest version available from NuGet.

I am primarily looking at the source code, so I can ignore this issue; however, others may not be so fortunate. :)

Malfunctioning transition events?

Hello, it appears that the transition completed event refers to the state you transitioned from. Is this intentional? Am I misunderstanding something? These are my events:

        private void TransitionBeginEvent(object sender, TransitionEventArgs<States, Events> e)
        {
            Log.Logger.Debug("{Event} satisfied, transitioning from {State}", e.EventId, e.StateId);
        }

        private void TransitionCompletedEvent(object sender, TransitionCompletedEventArgs<States, Events> e)
        {
            Log.Logger.Debug("TransitionCompleted Entered state {State}", e.StateId);
        }

This is a print function I added to each state to get the actual state I transitioned into:

            builder.In(States.Idle)
                .ExecuteOnEntry(SetIdle)
        private void SetIdle()
        {
            Log.Logger.Debug("Entered State {0}", States.Idle);
        }

This is the result:

[10:53:29 DBG] Entered State Idle
[10:53:29 DBG] Start satisfied, transitioning from Idle
[10:53:29 DBG] Leaving idle
[10:53:29 DBG] Entered State Disconnected
[10:53:29 DBG] Firing Connect
[10:53:29 DBG] TransitionCompleted Entered state Idle

Somewhat related, there doesn't appear to be a way to query the machine to get the state it's currently in. Again if I'm just wrong and someone can point me in the right direction I would really appreciate it.

It works great, it just seems a little difficult to get in flight logs about what it's doing. Thanks for your time.

Event catching weird behaviour

Hello.
I have being using the library for quite long, and I am just noticing an issue I did not encounter before as far as I remember.

I have tried with both AsyncPassiveStateMachine and with AsyncActive and have same result.

this state machine has states similar to this: NotConnected, Connected, Disconnected, Connecting.
and event triggers like: ConnectionDetected, DisconnectionDetected, ElapsedTime, CheckStatusFailed

the elapsed time event is being fired by a system.Timers.Timer
when elapsed time is fired a call is done to a service. When this call is done and it fails it both fires DisconnectionDetected and CheckStatusFailed (I have tried also with only one of them but still happens)

When DisconnectionDetected is fired, I can see that the code

await _machine.Fire(EventTrigger.DisconnectionDetected) is called.

The code continues, it does not hang in previous line. Previous line is executed fast.
DeclinedEvent is not fired, Exception not fired either.

The state machine just continues working as if no event was ever triggered.

Finally, after several minutes, 2-3 minutes, it eventually executes the fired trigger. In the meantime, multiple calls to the service and multiple DisconnectionDetected triggers.

I have tried all I can think of and I am not sure what is wrong, or why the trigger is left waiting somewhere.

thanks in advance, any help would be really appretiated.

Stop/abort passive state machine

Question: Is there a way to abort the state machine to prevent any further transitions? I used the Stop() method, but it doesn't stop it. I understand it may not be recommended to do so, but there's no need to keep transitioning if there's an transient exceptions and whatnot... Could you please suggest a recommended way to deal with such cases?

Events fire with ref or out may not be passed

Hey,

I was implementing a function that executes an event in which i want to have a variable set for the caller to read. I need this because i need to know which state i'm in outside of the state machine.

So if i could fire an event with a return value or with a ref/ out parameter that would be nice :)

FindOfferEntry is not answered by the server on Windows 10

image

Configuration:

{
    "unicast" : "fe80::60a1:4ef:4234:888e",
    "logging" :
    { 
        "level" : "debug",
        "console" : "true"
    },
    "applications" : 
    [
        {
            "name" : "service-sample",
            "id" : "0x1277"
        },
        {    
            "name" : "client-sample",
            "id" : "0x1344"
        }
    ],
    "services" :
    [
        {
            "service" : "0x600E",
            "instance" : "0x0001",
            "unreliable" : "30509",
            "events" : 
            [
                {
                    "event" : "0x0777",
                    "is_field" : "true",
                    "update-cycle" : 5000
                }
            ],
            "eventgroups" :
            [
                {
                    "eventgroup" : "0xC010",
                    "events" : ["0x0777"]
                }
            ]
        }
    ],
    "routing" : "service-sample",
    "service-discovery" :
    {
        "enable" : "true",
        "multicast" : "ff14::4:0",
        "port" : "30490",
        "protocol" : "udp",
        "initial_delay_min" : "10",
        "initial_delay_max" : "100",
        "repetitions_base_delay" : "200",
        "repetitions_max" : "3",
        "ttl" : "3",
        "cyclic_offer_delay" : "2000",
        "request_response_delay" : "1500"
    }
}

Why does the server didn't answer the clients FindService request? I expect an OfferService Reply from fe80::60a1:4ef:4234:888e to fe80::60a1:4ef:4234:8888.

Enhancement: Serialize/Deserialize definition

Our app talks to a back-end API and we wanted to have the back-end "drive" the application by sending down a state definition object. This all worked great in theory (locally, with local unit tests etc). But as soon as we tried to serialize the definition object we got the following exception:

'Appccelerate.StateMachine, Version=5.0.0.0, Culture=neutral, PublicKeyToken=917bca444d1f2b4c' is not marked as serializable.

How hard would it be to add this to the next release?

To be clear, here is a very simple example:

var builder = new StateMachineDefinitionBuilder<State, StateEvent>();

builder.In(State.InitialState)
.On(StateEvent.NextState)
.Goto(State.Login);

< code omitted for brevity>

            BinaryFormatter binaryFormatter = new BinaryFormatter();
            outStream = new MemoryStream();
            binaryFormatter.Serialize(outStream, builder);

The idea being that we'd send down just the definition object. The app would be in charge of Build() and starting the state machine. Again, this all works great locally (passing the definition object around the application) as long as we don't try to serialize it.

Move IState, IStateMachineReport, Transitions to Infrastructure (or other) namespace.

I am writing my own Report Generator. I want it to be able to report on AsyncMachines as well as Non Async Machines. Now I need to copy/paste the class because the only thing that differs between them is the namespace. I think some stuff needs to be moved around so that Async and non Async Machine uses the same IState, IStateMachineReport, Transitions and maybe others I forgot.

What do you think?

Stateless operation?

I'm experiencing issues with the current implementation related to the storage of internal state:
The state machine should ideally be stateless, as the only state present within a state machine is the state itself, so it makes sense that the engine be assignable the current state, then an event fired, and the resultant state read back out. The IStateMachineSaver almost extracts that functionality, but is a bit messy, and would probably be better implemented as an external property accessor so the machine can acquire the current state itself when an event is fired, and obviously update the resultant state through the setter when a transition has occurred.
In situations where you might have many FSMs representing a collection of 'users' but all associated with the same FSM graph, you may well want to pass an event to lots of instances synchronously. The system appears to require you recreate the machine for each user, call Load with a newed-up IStateMachineSaver, then call Fire, then call Save to read the resultant state.
To check an event for the next user, it appears we have to dispose of that machine, and restart the process from scratch.
I'm not sure if this is all related to the 'history' feature, which seems a bit superfluous given that we have logging, but I'd be interested to know if there are any workarounds for these limitations, other than rewriting the functionality in the source code.

As an example of the non-working code to implement a fast call on behalf of multiple instances:

        public string FireEvent(string startstate, string eventname, IFireEventArgs args)
        {
            _fsm.Load(_persistor.SetState(startstate ?? "START"));
            _fsm.Fire(eventname, args.Arguments);
            _fsm.Save(_persistor);
            return _persistor.State;
        }

Here you can see the messy use of the persistor, which of course doesn't work because Load can't be called twice. A shared FSM is a pretty common requirement, so am I missing something here?

Question: Intended usage pattern for Actions with multiple machines

If I employ the StateMachineDefinitionBuilder (as I believe is intended) by using CreatePassiveStateMachine more than once, so as to have multiple instances of my state machine created at any one moment, how can I tell which instance was the source of a call to Execute()?

To reference the example in the documentation, if I expand upon it to create a collection of Elevators, when the fsm executes this.AnnounceOverload, I want to know which of my many elevator FSM objects was the source of the execution.

Thank you!

Extension.EnteredInitialState has context.State == null

This prevents getting the IState<TState, TEvent> of the initial status.

Example extension code:

private class CurrentStatusExtension : AsyncExtensionBase<States, Triggers>
{
	private readonly AppccelerateTestData _instance;

	public CurrentStatusExtension(AppccelerateTestData instance)
	{
		_instance = instance;
	}

	public override void EnteredInitialState(
		IStateMachineInformation<States, Triggers> stateMachine,
		States state,
		ITransitionContext<States, Triggers> context)
	{
		// context.State is null ... so there is no way to get the initial state?
		_instance.CurrentStatus = context.State;
	}
}

Guards after Goto

Let's consider following machine:

        var ms = new PassiveStateMachine<string, string>();
        ms.In("a")
            .On("go")
            .If(() => {
                Console.WriteLine("guard1");
                return true;
            })
            .Goto("b")
            .If(() => {
                Console.WriteLine("guard2");
                return true;
            })
            .Execute(() => Console.WriteLine("transition"));
        ms.Initialize("a");
        ms.Start();
        ms.Fire("go");
        ms.Stop();

What is expected outcome from this? Intuitively, Goto is guarded by guard1 and Execute by guard2 - however, guard2 is never evaluated and Execute action never called. Looks like bug to me.

Implementing unconditioned transition

I have to write a state machine having a special transition for all states. Let's call it reset. If this event is fired, the machine has to go in a predefined state, no matter what the current state is. Of course, I could define this transition for each and every state, but this is what I want to avoid.

I tried to implement it using custom state class, like this (LinqPad query):

void Main()
{
	var fsm = new PassiveStateMachine<MyState, Events>()	;
		
	fsm.In(MyState.Any)
		.On(Events.R)
		.Goto(MyState.A);
		
	fsm.In(MyState.A)
		.On(Events.E2)
		.Goto(MyState.B);
		
	fsm.In(MyState.B)
		.On(Events.E1)
		.Goto(MyState.A);

	fsm.TransitionCompleted += (s, a) => $"{a.EventId} : {a.StateId.State} => {a.NewStateId.State}".Dump();
	
	fsm.Initialize(MyState.A);	
	fsm.Start();
	
	fsm.Fire(Events.E2); // E2 : A => B
	fsm.Fire(Events.E1); // E1 : B => A
	fsm.Fire(Events.E2); // E2 : A => B
	fsm.Fire(Events.R); // nothing happens
}

enum Events { R, E1, E2 };

public class MyState: IComparable
{
    public enum States { Any, A, B };
	
	public static MyState A => new MyState(States.A);
	public static MyState B => new MyState(States.B);
	public static MyState Any => new MyState(States.Any);
	
	private MyState(States state)
    {
        this.State = state;
    }
 
    public States State { get; private set; }
 
    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj))
        {
            return false;
        }
         
        if (ReferenceEquals(this, obj))
        {
            return true;
        }
         
        if (obj.GetType() != this.GetType())
        {
            return false;
        }
 
        return this.Equals((MyState)obj);
    }
 
    public override int GetHashCode()
    {
        return this.State.GetHashCode();
    }
 
    public int CompareTo(object obj)
	{
		throw new InvalidOperationException("state machine should not use CompareTo");
	}

	protected bool Equals(MyState other)
	{
		return this.State == States.Any || other.State == States.Any || this.State == other.State;
	}
}

The universal state Any would be equal to any state. Still, in the last statement, when R is fired, the MyState class is not touched at all. None of the overridden methods is called.

Q1) Why is that?
Q2) Is there any other way to achieve my goal?

Call superstate executes for the same event as the child is in

Currently the super states won't react to an event fired. Is there any elegant way to have all the super states of the current states be called on the same event if it is defined like that?

so for instance i have this function that is defined for every state. I want it to be called for every superstate including the current state:
builder.In(state).On(Events.Do).Execute(state.Do);
but it only executes for the current state and not the super states when firing the event Do,

the state machines are missing a StateEntered event

From Wikipedia:

Every state in a UML statechart can have optional entry actions, which are executed upon entry to a state

uml_state_machine_fig5

With the current implementation, it looks like I don't have a way to to report (e.g. to the UI) that the current state is "heating" while the entry action is still executing. The TransitionCompleted is only raised after the enter action was executed and in the TransitionBegin event, I don't have a way to tell what the target state of the transition will be.

To stay with the example from Wikipedia, I would like to report to the user that state door_open was entered so the user knows that all the stuff is going to happen that needs to be done in this state (i.e. turning the lamp on - which could be a complex procedure in itself).

For that we'd need something like StateEntered or StateChanged event.

No documentation, no unit tests

Version 5.0 is out and I wanted to use it. However, the documentation site is still referencing the old code and in the repository, there are no unit tests to give me a feel of how to use 5.0.

Is there a documentation site somewhere ? I want to use a passive machine from command line.

Thanks

State machine initialisation

Hi Urs,

I'm in the preliminary stages of working with your library, and very nice it is too. I'm using it to simplify the account registration process for my app, which has a number of different states and ways of transitioning between them.

The question I have is: is there any way around the initial state firing when the machine is started? In my case, the state machine is not long running: it's initialised for every account API interaction (registration, transfer, cancellation, verification etc), and then discarded immediately. This means that the initial state has already fired and I'm interested in the transition to the next state triggered by the current request. Obviously, I'm persisting the state in between requests, but outside of the state machine. To put this in code perspective:

            _accountMachine = new PassiveStateMachine<AccountStateType, AccountStateTrigger>("Account");

            _accountMachine.In(AccountStateType.Unregistered)
                .On(AccountStateTrigger.RegisterAccount)
                    .If(() => stateCheck_registrationsSuspended()).Goto(AccountStateType.AwaitingSiteRegistrationsTurnedOn)
                    .If(() => !(stateCheck_registrationsSuspended()) && stateCheck_paidRegistrations()).Goto(AccountStateType.AwaitingPayment)
                    .If(() => !(stateCheck_registrationsSuspended()) && !(stateCheck_paidRegistrations())).Goto(AccountStateType.AwaitingVerification);

            _accountMachine.In(AccountStateType.AwaitingVerification)
                .ExecuteOnEntry<RegistrationRequest>(async (RegistrationRequest r) => await this.stateFired_AwaitingVerification_viaRegistration(r))
                .ExecuteOnEntry<VerificationRequest>(async (VerificationRequest r) => await this.stateFired_AwaitingVerification_viaVerification(r))
                .ExecuteOnEntry(() => ())
                .On(AccountStateTrigger.RegisterAccount)
                    .If(() => stateCheck_registrationsSuspended()).Goto(AccountStateType.AwaitingSiteRegistrationsTurnedOn)
                    .If(() => !(stateCheck_registrationsSuspended()) && stateCheck_paidRegistrations()).Goto(AccountStateType.AwaitingPayment)
                    .If(() => !(stateCheck_registrationsSuspended()) && !(stateCheck_paidRegistrations())).Goto(AccountStateType.AwaitingVerification)
                .On(AccountStateTrigger.VerifyAccount)
                    .If(() => !stateCheck_verificationAttemptsExhausted() && !stateCheck_verificationCodesMatch()).Goto(AccountStateType.AwaitingVerification)
                    .If(() => !stateCheck_verificationAttemptsExhausted() && stateCheck_verificationCodesMatch()).Goto(AccountStateType.Active)
                    .If(() => stateCheck_verificationAttemptsExhausted()).Goto(AccountStateType.VerificationFailed);

            _accountMachine.Initialize(AccountStateType.Unregistered);
            _accountMachine.Start();
            _accountMachine.Fire(AccountStateTrigger.RegisterAccount, req);  // Passing RegistrationRequest 

I get error: Cannot cast argument to match action method. Argument = ...Contracts.RegistrationRequest, Action = anonymous because I think the state machine doesn't have an ExecuteOnEntry() method defined for AccountStateType.Unregistered and is trying to match the RegistrationRequest signature when I fire the RegisterAccount trigger. Essentially, I'd like the state machine to consider the state passed in Initialize() as a bootstrap directly to that state without seeking to trigger any events associated to that state. I hope this makes sense.

Null argument issue with ArgumentActionHolder

There is a problem with ArgumentActionHolder. We can't provide null as argument because of missing check for null. Now

if (argument != Missing.Value && !(argument is T))

should be:

if (argument != Missing.Value && argument != null && !(argument is T))

Microservices orchestrator

Hi,

I'm interested in building something like Azure logic app or Netflix microservice orchestrator. For this I need to load workflow from a JSON file or XML file from a workflow designer. Is that possible with this library

YEdStateMachineReportGenerator produce an invalid xml/graphml file (namespace SchemaLocation)

There is an invalid xml namespace in the generated output from the YEdStateMachineReportGenerator

The value of SchemaLocation is wrong :
private static readonly XNamespace SchemaLocation = "http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd";

The first part is probably a wrong paste from the namespace N and the value should be :
private static readonly XNamespace SchemaLocation = "http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd";

Issues Running StateMachine

I'm having trouble getting the statemachine to transition.

Am I allowed to fire and event from inside an OnEntry function?
When I've called Start on the statemachine do I need to do a while loop to keep the state machine open?
Do you have a working code example for the tutorial on the site? It would be nice to see a full code structure just to see how you expect an IStateMachine to be interacted with.

the Save / Load feature is incomplete and the loaded STM doesn't continue where it was saved

Hi and Happy New Year !

I noticed that the events added to the queue aren't Saved and because of that, a new Loaded STM does not continue properly after a Load.

Also, because there is no way to know if the STM is done working (except if manually stopped depending on active / passive implementations) to be saved properly, the Save operation should happen only when the STM is not executing, because an action could fire some events.
The Save operation should wait until the current transition is completed, then save everything including the events queued.
This could be done by calling Stop (stop should wait until completion), then Save and finally Start if the STM was already running.

As I was saying in the issue #21 (comment),
I think a better suited design would be to store the StateMachine's datas in a Model (CurrentState, events queued, HistoryStates etc.) and then use this model with the Persistence implementation.

A call to Load should notify extensions for Initializing, Initialized and SwitchedState

Hi,
I'm using your StateMachine implementation with extensions and I have a couple of problems :

  • The StateMachine does not notify at all the extensions at Load()
    the initialized flag is set to true but InitializingStateMachine or InitializedStateMachine aren't called on extensions.

  • A call to LoadCurrentState() set the backing field currentState instead of the Property CurrentState, SwitchedState is never called on extensions.

Please, can you fix this behavior and update the nuget package ?

ActiveStateMachine may deadlock when used with async/await

I am not sure whether it is an actual bug, or I simply do something stupid. But consider the following snippet:

  internal class BuggyStateMachine : ActiveStateMachine<int, int>
  {
    private TaskCompletionSource<int> myTcs = new TaskCompletionSource<int>();

    public BuggyStateMachine()
    {
      In(0).On(1).Execute(() => myTcs.SetResult(0));
      Initialize(0);
      Fire(1);
    }

    public async Task RunDeadlock()
    {
      Start();
      await myTcs.Task;
      Stop();
    }

    public Task RunNoDeadlock()
    {
      Start();
      return myTcs.Task.ContinueWith(t => Stop());
    }
  }

A task returned by RunDeadlock will never finish when the method is executed on a thread without SynchronizationContext. This is because the continuation Stop() will be run on the same thread as () => myTcs.SetResult(0), which is a worker thread of the state machine. And since Stop is a synchronous method waiting for the state machine to finish, we have a deadlock.

Enhancement: Extension method to return state that handled event

Appccelerate’s base extension methods are a great debugging tool. I use them all the time to help debug new state machines or state machines being modified or enhanced.

In a hierarchical state machine with deeply nested substates, it can be quite difficult to debug issues where multiple substates respond to the same event (with or without guards).

For example, suppose C is a substate of B, B is a substate of A, and all have an event handler with guard for event E. If E is fired from state C and C’s guard fails, then B’s E event handler is called. If B’s guard fails, then A’s E event handler is called.

Because each state’s event handler guard can be based on different conditions, and result in different actions/transitions when the guard succeeds, it can be difficult to trace the state machine’s behavior in response to event E.

It would be valuable if you could provide a new base extension method, HandledIn (or some suitably named method), that returns the state that ultimately handled the most recently fired event. In the above example, if event E is fired from state C, and state C and B’s event handlers’ guards failed, but state A’s event handler’s guard succeeded, then HandledIn would return state A. If state A’s event handler’s guard also failed, then HandledIn would return null (unhandled event).

Thanks,

Paul

Some questions

Hello,

I want to use the library and wrote an example application with a hierarchical state machine to check it out.
Now I have a few questions about the current implementation.

1.) When using builder.DefineHierarchyOn..., do I always need an initial substate (WithInitialSubState).

2.) Is there a way to perform initial actions along with the initial transisions (not entry and exit actions!).

3.) Assume I have:

builder.DefineHierarchyOn(States.top)
.WithHistoryType(HistoryType.None)
.WithInitialSubState(States.s1);

builder.In(States.s1).On(Events.eD).Goto(States.top).Execute(() => IOForm.Write("Event D" + Environment.NewLine));

On Event eD I get:

s1-Exit
top-Exit
Event D
top-Entry
s1-Entry

Two questions about that:

  • Why is the action (here printing "Event D") not performed in state s1 as the code suggests?
  • Why are the entry and exit actions of the top state performed at all? I was in state s1, which is a substate of top, so in order to go to top, just going one step up is enough?

Did I misunderstand something oder perhaps I have overlooked conifguration possibilities.

I would be very grateful for an answer.

Default transition / run-to-completion

Is there a way to define default transition (transition without a trigger, which activates automatically on completion of incoming event)? That would allow to run more complex logic in run-to-completion approach.

AsyncPasiveStateMachine dependency injection singleton

I have the following line in Startup.cs:

services.AddSingleton(typeof(IAsyncStateMachine<WorkflowState, WorkflowEvent>), typeof(AsyncPassiveStateMachine<WorkflowState, WorkflowEvent>));

I'm also using some guards to execute different flows based on different flags.
The first state machine workflow execution runs without any issues but if I try to execute the workflow again, I'm getting an error:

Exception in workflow: The transition without guard has to be the last defined transition because state machine checks transitions in order of declaration.

Do you know what can cause this?

AsyncPassiveStateMachine crash

We had a crash with the async state machine and i'm not sure what the problem is. Here is the stack trace:

Object reference not set to an instance of an object.

Stack Trace:
   at System.Collections.Generic.LinkedList`1.InternalRemoveNode(LinkedListNode`1 node)
   at Appccelerate.StateMachine.AsyncPassiveStateMachine`2.<ProcessQueuedEvents>d__41.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Appccelerate.StateMachine.AsyncPassiveStateMachine`2.<Execute>d__40.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Appccelerate.StateMachine.AsyncPassiveStateMachine`2.<Fire>d__27.MoveNext()

Any help is welcome :)

edit: Looks like a multi-threading issue. I was not able to reproduce it so far. Looks like the state machine removed a fire event while iterating through the collection while adding a new fire event. Just a guess...

Cannot test application with state machine using async ExecuteOnEntry action

I am writing a WPF application where I use a state machine which deals with transitions between one screen to the other.

To do so, I use a StateMachineDefinitionBuilder, and part of its configuration looks like this:

var builder = new StateMachineDefinitionBuilder<ShellState, ShellEvent>();

[...]

builder.In(ShellState.ProfilesInitialization)
                .ExecuteOnEntry(
                    async () =>
                    {
                         await this.profileSelectionViewModel.InitializeProfiles();
                    })
                .On(ShellEvent.GettingProfiles)
                .Goto(ShellState.Busy);

[...]

In my production code, I create the state machine like this:

var defn= builder.Build();
var stateMachine = defn.CreateActiveStateMachine();

My production application works like a charm.

Now, when I switch to my tests, I use the very same builder as above. Only the creation of the state machine is a bit different:

var stateMachine = defn.CreatePassiveStateMachine();

With that setup in place, my async call above is not awaited in my tests. The only way I've found to make it work in both test and production code is to change the above ExecuteOnEntry action to

() =>
{
    this.profileSelectionViewModel.InitializeProfiles().Wait();
})

Is that expected? I was expecting to be able to use async / await and have those calls synchronous with a passive state machine.

Update website documentation to use builders instead of creating state machines directly

Documentation on Appcellerate website is out of date. For example take this example documentation: http://www.appccelerate.com/statemachinesample.html

However currently state machines do not have empty contructors anymore.
I almost removed Appccelerate from our solution until I saw that the repo readme(.md) had an example where a state machine builder is used..

Please update documentation to reflect this breaking (api) change.

.net core?

Try if the state machine can target .net core.

Accessing Event Argument not working?

Hi there.

Congrats for your great work here. I have an issue but maybe Im doing something wrong.

Im trying to access the Fire event content inside the execute method, but I'm always receiving null. I expected to see "hola" on Console. Is that the desired behavior?

using System;
using Appccelerate.StateMachine;

namespace ConsoleApp3
{
    class Program
    {
        static void Main(string[] args)
        {
            var fsm = new PassiveStateMachine<string, string>();
            fsm.In(States.Lobby)
                .On("Hola").Goto(States.Play)
                .Execute<string>(MyMethod) // or .Execute<string>(x=>MyMethod(x)) 
                .Execute((object x) => MyMethod2(x)) // or .Execute<object>(x => MyMethod2(x));
                .Execute<object>(MyMethod2)
                ;
            fsm.Initialize(States.Lobby);
            fsm.Start();

            fsm.Fire("Hola");
        }

        private static void MyMethod2(object o)
        {
            Console.WriteLine(o);
        }

        private static void MyMethod(string s)
        {
            Console.WriteLine(s);
        }
    }


    public static class States
    {
        public const string Lobby = "Lobby";
        public const string Play = "Play";
    }
}

AsyncActiveStateMachine.Fire Task never completes

The AsyncActiveStateMachine internally uses TaskCompletionSource without the option TaskCreationOptions.RunContinuationsAsynchronously.
This can lead to situations where the Task on the Fire method never completes when the ExecuteOnEntry method blocks.

By default, when this TaskCreationOption is not set the continuations after awaiting the TaskCompletionSource.Task are executed synchronously on the same thread that has called the TaskCompletionSource.SetResult method. This is in my opinion not the expected behavior of a asynchronous active state machine.

Fix:
In the class AsyncActiveStateMachine on line 39 and line 253 create the TaskCompletionSource with the parameter TaskCreationOptions.RunContinuationsAsynchronously (e.g. "new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously)")

Here an example which simulates this issue:
The Fire method task never completes.

public enum State
{
    FooState1,
    FooState2,
}

public enum Event
{
    FooEvent,
}

public class Program
{
    private static AsyncActiveStateMachine<State, Event> stateMachine;

    static async Task Main()
    {
        Console.WriteLine($"Started (Thread: {Thread.CurrentThread.ManagedThreadId})");

        stateMachine = new AsyncActiveStateMachine<State, Event>();
        stateMachine
            .In(State.FooState1)
                .ExecuteOnEntry(OnFooState1)
                .On(Event.FooEvent).Goto(State.FooState2);

        stateMachine
            .In(State.FooState2)
            .ExecuteOnEntry(OnFooState2);

        Console.WriteLine($"Before Initialized (Thread: {Thread.CurrentThread.ManagedThreadId})");
        await stateMachine.Initialize(State.FooState1).ConfigureAwait(false);
        Console.WriteLine($"After Initialized (Thread: {Thread.CurrentThread.ManagedThreadId})");

        Console.WriteLine($"Before Start (Thread: {Thread.CurrentThread.ManagedThreadId})");
        await stateMachine.Start().ConfigureAwait(false);
        Console.WriteLine($"After Start (Thread: {Thread.CurrentThread.ManagedThreadId})");

        await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);

        Console.WriteLine($"Before Fire Event 1 (Thread: {Thread.CurrentThread.ManagedThreadId})");
        await stateMachine.Fire(Event.FooEvent).ConfigureAwait(false);
        Console.WriteLine($"After Fire Event 1 (Thread: {Thread.CurrentThread.ManagedThreadId})");

        Console.WriteLine($"Done (Thread: {Thread.CurrentThread.ManagedThreadId})");

        Console.ReadKey();
    }

    private static async Task OnFooState1()
    {
        Console.WriteLine($"In State 1 (Thread: {Thread.CurrentThread.ManagedThreadId})");
    }

    private static async Task OnFooState2()
    {
        Console.WriteLine($"In State 2 (Thread: {Thread.CurrentThread.ManagedThreadId})");

        // Simulate blocking operation
        while (true) { }
    }

Outcome without "RunContinuationsAsynchronously":

Started (Thread: 1)
Before Initialized (Thread: 1)
After Initialized (Thread: 1)
Before Start (Thread: 1)
After Start (Thread: 1)
In State 1 (Thread: 3)
Before Fire Event 1 (Thread: 3)
In State 2 (Thread: 3)

Outcome with "RunContinuationsAsynchronously":

Started (Thread: 1)
Before Initialized (Thread: 1)
After Initialized (Thread: 1)
Before Start (Thread: 1)
After Start (Thread: 1)
In State 1 (Thread: 3)
Before Fire Event 1 (Thread: 3)
After Fire Event 1 (Thread: 3)
Done (Thread: 3)
In State 2 (Thread: 4)

Exception handling in transition

Hi there and thanks for the great library.

I'm experiencing a little problem with exceptions occurring during state transition functions. At the moment - at to the point of my understanding - If an exception is thrown within a transition an event is fired which at least allows us to log the error, but the transition is still executed.

Is there a possible way to reject a transition in case of an exception? I.e. the state does not change?

Thank you very much.
Merlin

Parametrized Entry Actions

Hi,

using:

bbv.Common.StateMachine (v.7.4.12149.1635)
bbv.Common.AsyncModule (v.7.2.12149.1635)
bbv.Common (v.7.1.12149.1635)

I'm trying to use a parametrized entry action. The documentation on the Appcelerate website states that it is possible to use the following syntax:

stateMachine.In(States.A)
     .ExecuteOnEntry<string>(this.SomeMethodWithStringArgument);

Now I'm having the following action:

private void StateOnEntryOperations(Dictionary<int, string> pars)
{
      DoActions(pars);
}

And I'm defining my state A:

_stateMachine.In(States.A)
     .ExecuteOnExit<Dictionary<int, string>>(StateOnEntryOperations);

trying to fire the transition to state A:

private void FireStateA()
{
      Dictionary<int, string> dictionary = new Dictionary<int, string>();
      // Add elements to dictionary...
      Machine.Fire(Events.TriggerForStateA, dictionary);
}

I need the argument to be passed from method FireStateA().
What I get is a compiler error forcing me to use:

.ExecuteOnEntry(Action<Dictionary<int, string>> action, Dictionary<int, string> parameter)

If I understand properly I should use a compiled and not dinamically generated parameter:

stateMachine.In(States.A)
     .ExecuteOnExit<Dictionary<int, string>>(StateOnEntryOperations, new Dictionary<int, string>());

but is would be always empty...

Am I missing something? Is there any other syntax allowing me to do what I need?

Thanks in advance

Alberto

Not compatible with .NET 4.0

When trying to install with nuget package manager

Could not install package 'Appccelerate.Fundamentals 2.8.0'. You are trying to install this package into a project that targets '.NETFramework,Version=v4.0', but the package does not contain any assembly references or content files that are compatible with that framework. For more information, contact the package author.

Which Framework version do you support? I could not find the info on the documentation site.

NuGet Dependencies Request > 2.1; however, build then produces different version warnings

I've been working with the Appccelerate state machine component for several months now.

When I downloaded the latest component using NuGet (version 2.7), I saw no errors during the NuGet install. During the build, I received a warning about different versions of dependent assemblies. Most importantly, when I ran my unit tests, I received the following error:

Result Message: System.IO.FileNotFoundException : Could not load file or assembly 'Appccelerate.Fundamentals, Version=2.7.0.0, Culture=neutral, PublicKeyToken=917bca444d1f2b4c' or one of its dependencies. The system cannot find the file specified.

When NuGet installed the state machine component, I recall that it requested version 2.1.0 of Appccelerate.Fundamentals.

What can we do to correct this issue? My work around is to install version 2.7 of the Appcelerate dependent packages by hand.

Thanks for all your hard work. You have saved me much work by all your hard work.

Have a great day!

Exception in Execute action

Hello,

I have to handle exceptions in transition somehow. Either go to another state or stay in the current state, but I can't deal with the actual behavior of the machine is such situation. See following code:

public enum States { S1, S2 }
public enum Actions { A1 }

async Task Main()
{
	var fsm = new AsyncPassiveStateMachine<States, Actions>();
			
	fsm.In(States.S1)
		.On(Actions.A1)
		.Goto(States.S2)
		.Execute(() => throw new Exception());

	fsm.In(States.S1)
		.ExecuteOnEntry(() => "Entry S1".Dump());

	fsm.In(States.S2)
		.ExecuteOnEntry(() => "Entry S2".Dump());
		
	fsm.TransitionExceptionThrown += (sender, args) => args.Dump();

	fsm.Initialize(States.S1);	
	
	await fsm.Fire(Actions.A1);
	
	await fsm.Start();
	
}

If there is no exception listener registered, the machine stops at exception. With one registered, it detects the exception but the transition is still performed, bringing the machine in an inconsistent state. Guards are of no use here.

Imagine following situation: a database operation is performed in the transition. But the connection fails meanwhile. I need to be noticed about it, but in no case should I step forward. How can I properly handle this in the machine?

Please advice!

Xamarin not compatible

Is there any way (or prevision) to use Appccelerate on Xamarin SDK or Mono?
Thank you.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.