GenServer State Management in Elixir: A Production Order Book

A long-running GenServer holds orders, positions, strategy state, and operational flags for a system trading real capital. The post walks the actual state struct, the three categories of data inside it, the persistence model (write to the database before the broker, push heavy work to Oban, never persist candles), and the design principles that fell out of running it in production — including why mid-roll restarts refuse to resume rather than guess.

1 Like

strategy, market_data_source, account_manager — each is a map with a module key and additional context.

I suggest to flatten them into the parent struct, use tuples, or use new structs. A map is a bad choice because with a map I have to write the keys and I will make typos.

trading?, rolling?, portfolio_heat_warned, portfolio_heat_blocked — state machine flags that control what
the GenServer is allowed to do.

Boolean flags are bad choices to encode state machine states (sooner you will find some combinations are illegal). Ideally, they should use enum types but since Elixir does not have a true enum type I’d just use atoms.

1 Like

Thanks Derek — both fair, and both in the direction I’d take this code if I were refactoring it now.

On the maps: bare maps with conventional keys are a typo waiting to happen, you’re right. They ended up that way because each handle carries module-specific context whose shape varies by implementation — a strategy handle for order-flow logic holds different keys than one for a calendar roll. A struct with module: and a free-form context: map would give me a static field on the part that actually dispatches without forcing a schema on the part that legitimately varies. That’s the refactor I’d make.

On the booleans: the case is strongest for portfolio_heat_warned / portfolio_heat_blocked, which are an escalation ladder rather than two independent flags. Collapsing them into portfolio_heat: :ok | :warned | :blocked makes the “blocked but not warned” state unrepresentable, which is exactly what you’d want. trading? and rolling? are closer to genuinely independent — rolling can be in progress while trading is administratively halted — but I’d still take a tagged status to force the question of which combinations are actually reachable.

Appreciate the careful read.

Either we call if FSM, or we have flags, tertium non datur. State machine is not an object having state field. There should be a single source of truth and any flags spread the responsibility inevitably resulting in a diverged inconsistent state sooner or later.

FSM has transitions and the transition callback in the only place where the decision might have happened.

1 Like

Reading you again, your target is derek-zhou’s atoms-as-cure, but the same point lands on the concession I made to them. Renaming rolling? to status: :rolling doesn’t make it a state machine; it just retypes the flag. The FSM lives in the transition function, not the field type. That’s the right correction.

The struct as written has multiple operational flags doing what should be the work of one transition callback. A single transition function owning every legal move (and making the illegal ones structurally impossible) is the shape that wants to exist. :gen_statem is the obvious BEAM tool; a hand-rolled step/3 plus pattern-matched events is the lighter version.

What I’m curious about is where you’d draw the line for what belongs inside the transition function versus what’s an input to it. Some of those flags feel like events the FSM consumes, not states it carries, but I’d rather see you lay it out than guess. The distinction is one a lot of posts get wrong, this one included.

1 Like

I would not draw this line whatsoever. As an author of Finitomata, I spent a lot of time playing with different ideas and I came up with the simplest one: each transition callback has an internal state and an incoming payload with each event.

I find using flags for state machines confusing. I have vague memories of designing state machines using binaries, truth tables and Karnaugh maps. Binaries would be similar to flags, so it can of course be done, but I rather not.

In my possibly naive mind deterministic finite state machines have only a single state at any time, only one valid transition at any time, and have transition callbacks to do side effects.

Correct.

Incorrect.

Not necessarily.

How do you do deterministic if there are several valid transitions from a state? It is certainly easier if there is only one.

Only one state is even easier, but not as handy. Deterministic finite automaton - Wikipedia

If I read that correctly that depends on using a Mealy state machine with inputs as added guides for a deterministic output, rather than a Moore state machine where there is only present state. For a deterministic Moore state machine I can not understand how there can be more than one valid output at a time?

I’ve used Finitomata earlier so thanks for that!

From what that example show there is exactly one valid transition for any given current state. Where I think we might talk around each other (?) is that I consider the overall current state as a composite of state machine state and input states. (It might be a synchronous state machine thing as that is what I’ve used the most. Take the current state, poll the inputs, and there should only be one possible transition ahead for deterministic operation).

(Then there are hierarchical and more complicated ones, but I don’t think we are discussing those. At least I’m not.)

Anyway, flags are still confusing.

1 Like

A Mealy state machine is just a Moore state machine plus a state-less function that maps the outputs of the Moore state machine, the input to the Moore state machine, to a new set of outputs. Neither inputs nor outputs are states.

1 Like

Considering all the books about state machines I do not think our various short comments will be able to capture the full extent of state machines, their differences, or the every precise terminology as used in their many applications.

My naive understanding is that Moore machines have outputs depending only on state, with the next state depending on current state and inputs. In Mealy machines the outputs both on current state and inputs, and so do the next state. Personally I thought they could be converted back and forth, and the practical reason to choose Moore was more about avoiding electrical glitches when used with hardware. According to Wikipedia though not all Mealy machines can be converted to Moore machines, but I leave that rabbit hole for someone else to drown in.

I’ve mostly used state machines with electronic sensors, buttons, joysticks etc as inputs and controlling motors, relays and UI as outputs. The values of these are variables where the inputs reflect their current state, while for the outputs reflect what state I want them to get to. They are not states in the finite state machine terms, but they are nevertheless stateful variables which together with the FSM state describe the systems overall current state.

I agree different terminology for these different kind of states would be clarifying, and that in this setting states of inputs and outputs would better referred to as just variables. Nevertheless those variables do state though. If I got a latching button that is either on or off then that is holding state, and usually referred to as such. I don’t think you can convince me inputs or outputs do not represent state.

2 Likes

It seems like you prefer to discuss FSM in the logic design setting. In this setting, there is a strong distinction between combinatorial logic (pure functions) and sequential logic (state management), states, inputs and outputs are rigorously defined. Outputs are combinatorial results of the state (or state plus input). One can say that outputs reveal state, but they are usually not the state themselves.

You may want to refet to the following introduction material.

I had those same intro examples as home exercises back in the day.

  1. Inputs can be stateful and thus have state as the buttom example above. As for outputs they can similarly control state. Say turn on or off a switch or a light bulb to keep it simple.
  2. The states of the FSM belong to the FSM. Their existence does not take away that inputs can have their own state.

As far as I can tell we disagree that inputs can have state. I would also not count on being able to know FSM state based on the output as many states can give the same output.

I would argue there is Michael Sipser’s Theory of Computation and the rest and I would not recommend reading the rest.

1 Like

That does seems like an interesting one. :slight_smile:

Edit: Actually available as open course on MIT. https://ocw.mit.edu/courses/18-404j-theory-of-computation-fall-2020/video_galleries/video-lectures/

1 Like

I am not sure we disagree on anything because I do not understand your points. Did you mean to say:

  • inputs to a state machine could be outputs of another state machine, Of course, but then they are outputs, not states.
  • outputs could be the states themselves through an identity function (like in your latching button case). Of course, degenerate case exists. But they are not states additional to what the state machine hold.

I think mentally separating inputs, outputs and states help me to reason about FSMs. YMMV.