gen_statem: Join states?

classic Classic list List threaded Threaded
5 messages Options
Reply | Threaded
Open this post in threaded view
|

gen_statem: Join states?

Oliver Korpilla
Hello.

In the system we're building we're using gen_statem processes to keep track of signalling scenarios, essentially series of different types of messages from various sources. For linear or branching scenarios gen_statem is well prepared. But there's one thing that seemed to be missing and that is support for joins?

I recently reviewed a developer's code that implemented receiving two different messages that could arrive in any order as four state functions - receiving A first (1), then receiving B (2), then entering an end state; or receiving B first (3), then receiving A (4), then entering an end state. The code entering the end state was duplicated in both (2) and (4). What essentially was needed was a "check list" of which messages had already arrived to determine the transition into the end state.

Such "check list" scenarios are quite common in our work (and can grow to be more complex) and I couldn't find a direct support for it (though I was limiting myself to state_function mode), so we're now starting a separate "join process" - a gen_server that keeps and maintain the join list - and do the decision now from a single wait state. Given how light-weight Erlang processes are this works fine.

What prevented me from doing this through data maintained between calls was that I would have to partition the state into information for tracking the join and the actual information kept to process the signals. This seemed less generic and clean.

I'm also aware that had I rewritten the whole state machine to handle_event functions and utilized a complex state this could have helped achieve the same. What is essentially is needed, from my POV, is a way to track information pertaining to state transitions separate from information for processing incoming events, and complex states seem to do that...?

What is your preferred way of doing this? Is it possible to somehow support this at the library level? Are handle_event/complex states the way to go in such situations?

Thank you and regards,
Oliver
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: gen_statem: Join states?

Frans Schneider-2
I use gen_statem's complex state often for exact the situation you
describe. For example, I use this state:

-record(state, {reliability_level :: best_effort | reliable,
                state1 :: idle | announcing,
                state2 :: waiting | must_repair}).

Now with handle_event/4, it is very easy to handle any of these
sub-states, even with enter calls. To me, this is what makes gen_statem
so nice.
Do mind that one has to make use of generic time-outs and not state
time-outs, with complex states.

Frans


On 06/23/2018 09:07 AM, Oliver Korpilla wrote:

> Hello.
>
> In the system we're building we're using gen_statem processes to keep track of signalling scenarios, essentially series of different types of messages from various sources. For linear or branching scenarios gen_statem is well prepared. But there's one thing that seemed to be missing and that is support for joins?
>
> I recently reviewed a developer's code that implemented receiving two different messages that could arrive in any order as four state functions - receiving A first (1), then receiving B (2), then entering an end state; or receiving B first (3), then receiving A (4), then entering an end state. The code entering the end state was duplicated in both (2) and (4). What essentially was needed was a "check list" of which messages had already arrived to determine the transition into the end state.
>
> Such "check list" scenarios are quite common in our work (and can grow to be more complex) and I couldn't find a direct support for it (though I was limiting myself to state_function mode), so we're now starting a separate "join process" - a gen_server that keeps and maintain the join list - and do the decision now from a single wait state. Given how light-weight Erlang processes are this works fine.
>
> What prevented me from doing this through data maintained between calls was that I would have to partition the state into information for tracking the join and the actual information kept to process the signals. This seemed less generic and clean.
>
> I'm also aware that had I rewritten the whole state machine to handle_event functions and utilized a complex state this could have helped achieve the same. What is essentially is needed, from my POV, is a way to track information pertaining to state transitions separate from information for processing incoming events, and complex states seem to do that...?
>
> What is your preferred way of doing this? Is it possible to somehow support this at the library level? Are handle_event/complex states the way to go in such situations?
>
> Thank you and regards,
> Oliver
> _______________________________________________
> erlang-questions mailing list
> [hidden email]
> http://erlang.org/mailman/listinfo/erlang-questions
>
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: gen_statem: Join states?

Jesper Louis Andersen-2
In reply to this post by Oliver Korpilla
I usually stuff the events into a map and then have a handle_continue/2 like behavior to check if everything in the map is present.

Simplified:

event(a, State) ->
    check(State#{ a => true });
event(b, State) ->
    check(State#{ b => true });
...

check(#{ a := A, b := B} = State) ->
    end(A, B, State);
check(#{} = State) ->
    {noreply, ...}.

Usually, rather than storing true, I'd store the event data there, and rather than having a map() at the top-level I'd probably have a record. But the gist of the idea is there. A more advanced variant of this will have a defined state machine and valid transitions between them. Then check is also an invariant on the transitions validity and it becomes a (runtime) contract to check against.

Aside: Idris can do this check at compile time, but since we don't have dependent types...



On Sat, Jun 23, 2018 at 9:07 AM Oliver Korpilla <[hidden email]> wrote:
Hello.

In the system we're building we're using gen_statem processes to keep track of signalling scenarios, essentially series of different types of messages from various sources. For linear or branching scenarios gen_statem is well prepared. But there's one thing that seemed to be missing and that is support for joins?

I recently reviewed a developer's code that implemented receiving two different messages that could arrive in any order as four state functions - receiving A first (1), then receiving B (2), then entering an end state; or receiving B first (3), then receiving A (4), then entering an end state. The code entering the end state was duplicated in both (2) and (4). What essentially was needed was a "check list" of which messages had already arrived to determine the transition into the end state.

Such "check list" scenarios are quite common in our work (and can grow to be more complex) and I couldn't find a direct support for it (though I was limiting myself to state_function mode), so we're now starting a separate "join process" - a gen_server that keeps and maintain the join list - and do the decision now from a single wait state. Given how light-weight Erlang processes are this works fine.

What prevented me from doing this through data maintained between calls was that I would have to partition the state into information for tracking the join and the actual information kept to process the signals. This seemed less generic and clean.

I'm also aware that had I rewritten the whole state machine to handle_event functions and utilized a complex state this could have helped achieve the same. What is essentially is needed, from my POV, is a way to track information pertaining to state transitions separate from information for processing incoming events, and complex states seem to do that...?

What is your preferred way of doing this? Is it possible to somehow support this at the library level? Are handle_event/complex states the way to go in such situations?

Thank you and regards,
Oliver
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions


--
J.

_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: gen_statem: Join states?

Oliver Korpilla
In reply to this post by Frans Schneider-2
Hello, Frans.

So your recommendation is to prefer handle_event state machines whenever it is foreseeable that a join state is required?

I'm sorry, I didn't understand the intent behind your example.

Thank you,
Oliver
 
 

Gesendet: Samstag, 23. Juni 2018 um 10:19 Uhr
Von: "Frans Schneider" <[hidden email]>
An: "Oliver Korpilla" <[hidden email]>, [hidden email]
Betreff: Re: [erlang-questions] gen_statem: Join states?
I use gen_statem's complex state often for exact the situation you
describe. For example, I use this state:

-record(state, {reliability_level :: best_effort | reliable,
state1 :: idle | announcing,
state2 :: waiting | must_repair}).

Now with handle_event/4, it is very easy to handle any of these
sub-states, even with enter calls. To me, this is what makes gen_statem
so nice.
Do mind that one has to make use of generic time-outs and not state
time-outs, with complex states.

Frans


On 06/23/2018 09:07 AM, Oliver Korpilla wrote:

> Hello.
>
> In the system we're building we're using gen_statem processes to keep track of signalling scenarios, essentially series of different types of messages from various sources. For linear or branching scenarios gen_statem is well prepared. But there's one thing that seemed to be missing and that is support for joins?
>
> I recently reviewed a developer's code that implemented receiving two different messages that could arrive in any order as four state functions - receiving A first (1), then receiving B (2), then entering an end state; or receiving B first (3), then receiving A (4), then entering an end state. The code entering the end state was duplicated in both (2) and (4). What essentially was needed was a "check list" of which messages had already arrived to determine the transition into the end state.
>
> Such "check list" scenarios are quite common in our work (and can grow to be more complex) and I couldn't find a direct support for it (though I was limiting myself to state_function mode), so we're now starting a separate "join process" - a gen_server that keeps and maintain the join list - and do the decision now from a single wait state. Given how light-weight Erlang processes are this works fine.
>
> What prevented me from doing this through data maintained between calls was that I would have to partition the state into information for tracking the join and the actual information kept to process the signals. This seemed less generic and clean.
>
> I'm also aware that had I rewritten the whole state machine to handle_event functions and utilized a complex state this could have helped achieve the same. What is essentially is needed, from my POV, is a way to track information pertaining to state transitions separate from information for processing incoming events, and complex states seem to do that...?
>
> What is your preferred way of doing this? Is it possible to somehow support this at the library level? Are handle_event/complex states the way to go in such situations?
>
> Thank you and regards,
> Oliver
> _______________________________________________
> erlang-questions mailing list
> [hidden email]
> http://erlang.org/mailman/listinfo/erlang-questions
>
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: gen_statem: Join states?

Oliver Korpilla
In reply to this post by Jesper Louis Andersen-2
Hello, Jesper.

My problem is not how to evaluate the information whether events have arrived. I simply kept a list of IDs and used lists:delete/2 and checked for the empty list. Your solution however also looks very useful.

My main problem was _where_ to stop this information. gen_statem gives you a possibility to store state, but then you would have to store information about the join (the list of events that have not arrived yet - or your map of events that do have arrived yet) together with the actual data pertaining to processing the events themselves. (For example we store in this other data the contents of our parsed configuration.) So, if I simply store the join information there, I have to create a data structure differentiating the two - like a record or a map. This would then require updating it every time and I did not consider it a very generic solution.

I considered doing something like { [ <Events> ], OtherData } but I didn't particularly like this situation - it still mixes information about the flow with the other data. That's why I looked for a solution within the FSM itself which I initially didn't find but now have Frans' pointers to follow.

Complex states with gen_statem:handle_event/4 seem to be a good choice - like a { waitForJoinEvents, [ <Events> ]} kind of state. I guess this could be generalized especially by using functions like you decribed. At least the "other data" would remain clearly separate from the data mean to direct flow. Functions could be written to evaluate this { StateName, EventList } tuple together with the incoming message and make decisions when for example the state should be changed. (Trivially, even the StateName can be ignored, though, as it's mostly useful for having a handle_event/4 that calls the the join with the right follow-up action when the join is satisfied.)

I guess I simply have to rewrite the involved FSMs as handle_event_function ones. Given the greater flexibility of the approach it seems to be the better default choice.

Thank you and kind regards,
Oliver
 
 

Gesendet: Samstag, 23. Juni 2018 um 12:21 Uhr
Von: "Jesper Louis Andersen" <[hidden email]>
An: "Oliver Korpilla" <[hidden email]>
Cc: "Erlang (E-mail)" <[hidden email]>
Betreff: Re: [erlang-questions] gen_statem: Join states?

I usually stuff the events into a map and then have a handle_continue/2 like behavior to check if everything in the map is present.
 
Simplified:
 
event(a, State) ->
    check(State#{ a => true });
event(b, State) ->
    check(State#{ b => true });
...
 
check(#{ a := A, b := B} = State) ->
    end(A, B, State);
check(#{} = State) ->
    {noreply, ...}.
 
Usually, rather than storing true, I'd store the event data there, and rather than having a map() at the top-level I'd probably have a record. But the gist of the idea is there. A more advanced variant of this will have a defined state machine and valid transitions between them. Then check is also an invariant on the transitions validity and it becomes a (runtime) contract to check against.
 
Aside: Idris can do this check at compile time, but since we don't have dependent types...
 
  

On Sat, Jun 23, 2018 at 9:07 AM Oliver Korpilla <[hidden email][mailto:[hidden email]]> wrote:Hello.

In the system we're building we're using gen_statem processes to keep track of signalling scenarios, essentially series of different types of messages from various sources. For linear or branching scenarios gen_statem is well prepared. But there's one thing that seemed to be missing and that is support for joins?

I recently reviewed a developer's code that implemented receiving two different messages that could arrive in any order as four state functions - receiving A first (1), then receiving B (2), then entering an end state; or receiving B first (3), then receiving A (4), then entering an end state. The code entering the end state was duplicated in both (2) and (4). What essentially was needed was a "check list" of which messages had already arrived to determine the transition into the end state.

Such "check list" scenarios are quite common in our work (and can grow to be more complex) and I couldn't find a direct support for it (though I was limiting myself to state_function mode), so we're now starting a separate "join process" - a gen_server that keeps and maintain the join list - and do the decision now from a single wait state. Given how light-weight Erlang processes are this works fine.

What prevented me from doing this through data maintained between calls was that I would have to partition the state into information for tracking the join and the actual information kept to process the signals. This seemed less generic and clean.

I'm also aware that had I rewritten the whole state machine to handle_event functions and utilized a complex state this could have helped achieve the same. What is essentially is needed, from my POV, is a way to track information pertaining to state transitions separate from information for processing incoming events, and complex states seem to do that...?

What is your preferred way of doing this? Is it possible to somehow support this at the library level? Are handle_event/complex states the way to go in such situations?

Thank you and regards,
Oliver
_______________________________________________
erlang-questions mailing list
[hidden email][mailto:[hidden email]]
http://erlang.org/mailman/listinfo/erlang-questions 
 --
J.
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions