3

I have a state machine that, as a side effect of going into a certain state, sends a message to a remote server. In some situations, however, I don't want the state machine to send that message, even if it ends up in that specific state. The state machine itself doesn't know when such a situation occurs because that depends on external variables. How can I influence the execution of that side effect? Do I need to make the state machine become aware of those external variables? Or should the sending of the message not be a direct side effect of the state machine?

To give some more context: my state machine represents a login procedure where one of the states is 'authorized'. When the state machine goes into or leaves the 'authorized' state, a message needs to be sent to a remote server. I have multiple of these state machines running in parallel, each tracking the login state of different users.

If an event applies to only a single state machine, it is ok that the state machine sends the message to the server.

handle_event_x() {
    state_machine.handle_event_x(); // ok if implicitly send message
}

If an event applies to multiple state machines, each of them implicitly sends the message.

handle_event_x() {
    state_machine_1.handle_event_x(); // implicitly sends message
    state_machine_2.handle_event_x(); // implicitly sends message
    state_machine_3.handle_event_x(); // implicitly sends message
    ...
}

The message infrastructure, however, supports and encourages combining multiple messages. In order to do that, I need to somehow block the state machine from sending that message and send one for all state machines combined.

handle_event_x() {
    state_machine_1.handle_event_x(); // block sending message
    state_machine_2.handle_event_x(); // block sending message
    state_machine_3.handle_event_x(); // block sending message
    ...
    send_combined_message();
}

How can I do this if the state machine itself isn't aware of whether it is handled alone or in a group? Should the message sending not be part of the state machine action in the first place?

3
  • You declare the transition as executing a certain action, and make the action do or not do something depending on the external variable, but keep both the variable and the action code outside the state machine. Commented Jun 7, 2023 at 15:39
  • 2
    It's hard to say without more details on how everything is implemented, what's sending the message, etc. But the first question that comes to mind is whether the message is sent in response to entering a state (onEnter) when instead, it should be sent when transitioning (onTransition.) Often times, those are effectively the same but it makes a difference if a state machine's state is ever saved and then reinitialized/restored.
    – spaaarky21
    Commented Jun 8, 2023 at 5:33
  • sending and/or blocking messages should be delegate to a dedicated component. your state machines should always call that dedicated component, and then transparently the component will decide if it should send message imediatly or store it to send a combined message later.
    – fluminis
    Commented Jun 8, 2023 at 10:06

1 Answer 1

5

I need to somehow block the state machine

Implementing semaphores in your state machine increases complexity considerably and introduces cases for which the state machine might not be prepared (deadlocks, semaphore local states, race conditions).

from sending that message and sending one for all state machines combined.

This is a different problem. You are looking for a way to decouple the time of the request from the notification itself.

How can I do this if the state machine itself isn't aware of whether it is handled alone or in a group?

If you try to implement batch notification processing from the state machine, you are likely coupling the state machine to the component that actually sends messages. In other words, you would be lacking details from one system to another.

Instead, divide and conquer. How? Don't send the message immediately, "schedule" or "enqueue" a request for another component to do the job. Think of this as a buffer of requests.

While the state machine writes requests to the buffer, a consumer pulls requests from it continuously or periodically depending on the need for immediateness. Besides the frequency, consumers need processing policies too, to determine how many requests to process per cycle, error handling, failover, etc. These policies are the elements you can make influenceable by external factors; not the state machine whose main purpose has nothing to do with notifications or batch operations.

Considerations

This "buffer" can be implemented in a distributed fashion (queues) or within the state machine's module (in-memory). Whatever fits better in the current context.

If consumers do long polling, they are likely to end with small batches or even batches of length 1. To gather more requests, they will need a second buffer. An internal one. To avoid memory leaks or unexpected latency (due to periods of inactivity) set a timeout to flush the 2nd buffer; don't wait for it to be full.

If consumers do short polling, set a frequency proportional to the frequency of incoming requests and to the max latency allowed for a single message.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.