35

In an event-driven architecture each component only acts when an event is sent through the system.

Imagine a hypothetical car with a brake pedal and a brake light.

  • The brake light turns on when it receives a brake_on event, and off when it receives a brake_off event.
  • The brake pedal sends a brake_on event when it is pressed down, and a brake_off event when it is released.

This is all well and good, until you have the situation where the car is turned on with the brake pedal already pressed down. Since the brake light never received a brake_on event, it will stay off - clearly an undesirable situation. Turning the brake light on by default only reverses the situation.

What could be done to resolve this 'initial state problem'?

EDIT: Thank you for all the responses. My question was not about an actual car. In cars they solved this problem by continuously sending the state - therefore there is no startup issue in that domain. In my software domain, that solution would use many unnecessary CPU cycles.

EDIT 2: In addition to @gbjbaanb's answer, I'm going for a system in which:

  • the hypothetical brake pedal, after initialization, sends an event with its state, and
  • the hypothetical brake light, after initialization, sends an event requesting a state event from the brake pedal.

With this solution, there are no dependencies between components, no race conditions, no message queues to go stale, and no 'master' components.

3
  • 2
    The first thing that comes to mind is to generate a "synthetic" event (call it initialize) which contains the needed sensor data.
    – msw
    Feb 11, 2015 at 14:49
  • Shouldn't the pedal send a brake_pedal_on event, and the actual brake send the brake_on event? I wouldn't want my brake light to come on if the brake was not working.
    – bdsl
    Feb 12, 2015 at 13:15
  • 3
    Have I mentioned it was a hypothetical example? :-) It's heavily simplified to keep the question short and to the point. Feb 12, 2015 at 13:19

6 Answers 6

34

There are many ways to do this, but I prefer to keep a message-based system as decoupled as possible. This means the overall system cannot read the state of any component, nor any component read the state of any other (as that way lies spaghetti ties of dependancies).

So, while the running system will look after itself, we need a way to tell each component to start itself up, and we already have such a thing in the component registration, ie at startup the core system has to inform each component that it is now registered (or will ask each component to return its details so it can be registered). This is the stage at which the component can perform its startup tasks, and can send messages as it would do in normal operation.

So the brake pedal, when the ignition is started, would receive a registration/check message from the car management and it would return not only a "I'm here and working" message, but it would then check its own state and send the messages for that state (eg a pedal depressed message).

The problem then becomes one of startup dependancies, as if the brake light is not yet registered then it will not receive the message, but this is easily resolved by queuing all of these messages until the core system has completed its startup, registration and check routine.

The biggest benefit is that there is no special code required to handle initialisation except that you already have to write (ok, if your message-sending for brake pedal events is in a brake-pedal handler you will have to call that in your initialisation too, but that's usually not a problem unless you've written that code tied heavily to the handler logic) and no interaction between components except those that they already send to each other as normal. Message passing architectures are very good because of this!

9
  • 1
    I like your answer, as it keeps all components decoupled - it was the most important reason to choose this architecture. However, currently there is no real 'master' component that decides the system is in an 'initialized' state - everything just starts running. With the problem in my question as a result. Once the master decides the system is running, it can send a 'system initialized' event to all components, after which every component starts broadcasting its state. Problem solved. Thank you! (Now I'm just left with the problem how to decide if the system is initialized...) Feb 11, 2015 at 22:02
  • How about having the status-update dispatcher keep track of the most recent update received from each object, and whenever a new subscription request is received, have it send the new subscriber the most recent updates it has received from the registered event sources?
    – supercat
    Feb 12, 2015 at 0:20
  • In that case you also have to keep track of when events expire. Not all events are amenable to keeping around forever for any new components that might register. Feb 12, 2015 at 7:52
  • @spaceknarf well, in the case where "everything just starts running" you cannot build dependancy into the components so the pedal starts after the light, you will just have to start them in that order, though i imagine something starts them running, so run them in the 'right' order (eg linux startup init scripts before systemd where the service to start first is called 1.xxx and the 2nd is called 2.xxx etc).
    – gbjbaanb
    Feb 12, 2015 at 8:32
  • Scripts with an order like that are fragile. It contains a lot of implicit dependencies. Instead, I was thinking if you have a 'master' component, that has a statically configured list of components that should run (as mentioned by @Lie Ryan), then it can broadcast a 'ready' event once all those components are loaded. In response to that, all components broadcast their initial state. Feb 12, 2015 at 8:46
4

You can have an initialize event which sets states appropriately upon load/startup. This can be desirable for simple systems or programs not including multiple hardware pieces, however for more complicated systems with multiple physical components as you run the same risk as not initializing at all - if a "brake on" event is missed or lost along your communication system (for example, a CAN based system) you may inadvertently set your system backwards as if you started it with the brake depressed. The more controllers you might have, such as with a car, the higher likelihood something is missed.

To account for this, you can have the "brake on" logic repeatedly send out "brake on" events. Perhaps every 1/100 second or something. Your code containing the brain can listen for these events and trigger "brake on" while it is receiving the them. After 1/10sec of not receiving "brake on" signals it triggers an internal "brake_off" event.

Different events will have considerably different timing requirements. In a car, your brake light needs to be much faster than say your check fuel light (where a multisecond delay is probably acceptable) or other less important systems.

Complexity of your physical system will dictate which of these approaches is more appropriate. Given your example is a vehicle, you probably would want something similar to the latter.

Either way, with a physical system, you do NOT want to rely on a single event being received/processed correctly. Connected microcontrollers on a networked system often have a "I'm alive" timeout for this reason.

3
  • in a physical system you would run a wire and use binary logic: HIGH is brake depressed and LOW is brake not depressed Feb 11, 2015 at 15:15
  • @ratchetfreak there are a lot of possibilities for this sort of thing. Perhaps a switch can handle that. There are a lot of other system events which are not handled that simply.
    – enderland
    Feb 11, 2015 at 15:23
  • @ratchetfreak physical systems do not use these kind of logic. For instance under steering wheel you have controller, to which all wires are connected, and then is messages with main computer over CAN. Same for brakes, somewhere sits brake controller, which talks to main computer over CAN, not physical wires.
    – Shadow
    Apr 26, 2020 at 18:34
2

In this case, I would not model the brake as a simple on/off. Rather, I would send "brake pressure" events. For example, a pressure of 0 would indicate off and a pressure of 100 would be fully depressed. The system (node) would constantly send break pressure events (at a certain interval) to the controller(s) as needed.

When the system was started it would start to recieve pressure events until it was turned off.

1

If your only means of passing state information is through events, then you are in trouble. Instead, you need to be able to both:

  1. query the current state of the brake pedal, and
  2. register for "state changed" events from the brake pedal.

The brake light can be seen as an observer of the brake pedal. In other words, the brake pedal does not know anything about the brake light, and can operate without it. (This means that any notion of the brake pedal proactively sending an "initial state" event to the brake light is ill-conceived.)

Upon instantiation of the system, the brake light registers with the brake pedal to receive braking notifications, and also reads the current state of the brake pedal and turns itself on or off.

Then, the braking notifications can be implemented in one of three ways:

  1. as parameterless "braking pedal state changed" events
  2. as a pair of "braking pedal is now depressed" and "braking pedal is now released" events
  3. as a "new breaking pedal state" event with a "depressed" or "released" parameter.

I prefer the first approach, which means that upon receiving the notification, the brake light will simply do what it already knows how to do: read the current state of the brake pedal and turn itself on or off.

0

In an event-driven system (which I currently use and love), I find it important to keep things as decoupled as possible. So with that idea in mind, let's delve right in.

It's important to have some default state. Your brake light would take the default state of 'off' and your brake pedal would take the default state of 'up'. Any changes after that would be an event.

Now to address your question. Imagine your brake pedal being initialized and pressed down, the event fires, but there's no brake lights yet to receive the event. I have found it easiest to separate the creation of the objects (where the event listeners would get initialized) as a separate step before initializing any logic. That will prevent any race conditions as you've described.

I also find it awkward to use two different events for what is effectively the same thing. brake_off and brake_on could be simplified into e_brake with a parameter bool on. You can simplify your events this way by adding supporting data.

0

What you need is a broadcast event and message inboxes. A broadcast is a message that is published to an unspecified number of listeners. A component can subscribe for broadcast events so it only receive events that it is interested in. This provides decoupling, as the sender doesn't need to know who the receivers are. The subscription table needs to be statically configured during component installation (instead of when the is initialized). The inbox is a part of the message router that acts as a buffer to hold messages when the destination component is offline.

Using invoices brings one problem, which is inbox size. You don't want the system to have to retain a growing number of messages for components that will never be online anymore. This is important especially with embedded system with strict memory constraints. To overcome inbox size limit, all broadcasted messages need to follow a few rule. The rules are:

  1. every broadcast events requires a name
  2. at any given time, the sender of a broadcast event can only have one active broadcast with a specified name
  3. the effect caused by the event must be idempotent

The broadcast name needs to be declared during component installation time. If a component sends a second broadcast with the same name before the receiver processes the previous one, the new broadcast overwrites the previous one. Now you can have a static inbox size limit, which can be guaranteed to never exceed a certain size and can be precalculated based on the subscription tables.

Finally, you also need a broadcast archive. The broadcast archive is a table that holds the last event from each broadcast name. New components that are just installed will have its inbox prepopulated with messages from the broadcast archive. Like the message inbox, the broadcast archive can also have static size.

Additionally, to deal with situation where the message router itself is offline, you also need message outboxes. The message outbox is part of the component that holds outgoing message temporarily.

Your Answer

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

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