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.

Frank Kusters
  • 726
  • 5
  • 12
  • 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 '15 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 '15 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. – Frank Kusters Feb 12 '15 at 13:19

6 Answers6

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!

gbjbaanb
  • 48,354
  • 6
  • 102
  • 172
  • 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...) – Frank Kusters Feb 11 '15 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 '15 at 00: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. – Frank Kusters Feb 12 '15 at 07: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 '15 at 08: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. – Frank Kusters Feb 12 '15 at 08:46
  • @spaceknarf yes, they are but they are also the only way to manage it in the absence of a master. If you do have a master though, then its easy - just start it first (you cannot let it be just one of the component as how do you determine if they are loaded or not? It cannot be in response to a 'startup' event if the master starts after some components, so you're back to reading state from the components directly) – gbjbaanb Feb 12 '15 at 09:03
  • @gbjbaanb There exists a service registry where all components register themselves. After the master has started (which could be anytime), it checks that all the components it was configured to check have registered themselves with the service registry. – Frank Kusters Feb 12 '15 at 09:25
  • @gbjbaanb After more discussion with a colleague, I found a solution without a 'master' component. I've added it to the question. However, a variant of the master is still useful: a watchdog. – Frank Kusters Feb 12 '15 at 10:42
  • @spaceknarf The problem you now have is that the brake light has a loose dependancy on the brake pedal, in that it sends a 'request state please' event. You can solve this by sending the msg to all components, but every time something starts it will send out a message that causes all components to respond with their state... quite a flurry of messages! Shouldn't be mjuch of a problem though. good luck. – gbjbaanb Feb 12 '15 at 12:43
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.

enderland
  • 12,091
  • 4
  • 51
  • 63
  • in a physical system you would run a wire and use binary logic: HIGH is brake depressed and LOW is brake not depressed – ratchet freak Feb 11 '15 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 '15 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 '20 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.

Jon Raynor
  • 10,905
  • 29
  • 47
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.

Mike Nakis
  • 32,003
  • 7
  • 76
  • 111
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.

Thebluefish
  • 676
  • 1
  • 5
  • 11
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.

Lie Ryan
  • 12,291
  • 1
  • 30
  • 41