Autonomous gen_statem
Nowadays, I usually write Elixir in my spare time. Not only that, actually whenever I encounter a problem at work, or have some random new ideas, I always think about how I can appraoch it in Elixir - or rather OTP style.
The more I try to design or architect things, the more often I find myself reaching out to gen_statem
(a successor to gen_fsm
) instead of gen_server
. A lot of things just naturally evolves over time between different states, and they should respond to things differently according to what state they are in.
The only downside is gen_statem
does not have a wrapper in Elixir like GenServer
, so you can’t use GenServer
and import a bunch of default callbacks - doesn’t really matter anyway. I gave this a real try in a rewrite of card_labeler, but before that I also spend a little time to figure out how I can make the state machine to make state transitions on its own. And this is my topic today.
States and Events (Messages)
By default, a state machine can only make state transition when handling incoming events, but in my cases I want it to do something when entering a state and directly transit itself to the next state when finished. For example the states I imagined for card_labeler include:
preparing
: initialization, but because this reaches to external API, I shouldn’t put it ininit/1
resting
: sleep for sometime, either because I don’t want to be rate limited or I simply don’t need to update that frequentlytracking
: track issue changes using GitHub APIupdating
: update the project board accordingly using the tracked update
And it should transit between these states on its own, that’s why I came up with this fancy name “autonomous state machine”. I’m not really sure whether this totally makes sense or whatnot, but organizing different actions using different states makes my code a lot cleaner.
Once again I used my Elixir playground to test how I can achieve this. By reading the documentation closer, the key is to use state enter calls. This basically sends a state machine itself a message (:enter
) when trasitting to a new state. To enable it just add it in the callback_mode
setup, like:
def callback_mode, do: [:state_functions, :state_enter]
Note: by enabling it you need to handle the :enter
message for all states.
Example
You can checkout my full example here and by running it (mix run --no-halt lib/statem.exs
) you should see logs like this:
19:51:06.138 [debug] inited
19:51:06.138 [debug] preparing
19:51:06.138 [debug] prepared
19:51:06.138 [debug] resting from preparing
19:51:06.141 [debug] tracking
19:51:06.141 [debug] tracked
19:51:06.141 [debug] updating
19:51:06.141 [debug] updated
19:51:06.141 [debug] resting from updating
19:51:11.142 [debug] tracking
19:51:11.142 [debug] tracked
19:51:11.142 [debug] updating
19:51:11.142 [debug] updated
19:51:11.142 [debug] resting from updating
19:51:16.142 [debug] tracking
19:51:16.142 [debug] tracked
19:51:16.142 [debug] updating
19:51:16.142 [debug] updated
19:51:16.142 [debug] resting from updating
Closing
And that’s all, I should have written this thing a long time ago. Anyway, I plan to write more small and simple posts like this whenever I discover something new in Elixir.