Although ‘blocking’ has allowed you to make the Controller model much simpler, ‘blocking’ may not always be as useful or runtime semantics might prevent you from using ‘blocking’ at all in an application. In his chapter, we will consider the runtime behaviour of the changes we made to RobustTimer and consider some limitations to the usage of ‘blocking’.
For simplicity’s sake, we will consider two active threads in this analysis: the main thread, running the event loop, and the Dezyne private thread, consuming events from dzn::pump.
The behaviour we described as ‘blocking’ is the cancellation process of the Timer. This process is started when in the Alarming state, a valid password is entered. Password entry is done through the passwordEntered event on the IController port of the AlarmSystem. This is where the call stack begins: invoking passwordEntered from the main thread.
Because AlarmSystem is generated with a thread-safe-shell, invoking the passwordEntered event is done with the dzn::shell functionality from the Dezyne runtime libraries. This means the main thread will be blocked until the Dezyne private thread has completed processing the event. Important: this is not because of ‘blocking’! This behaviour is fully because of the thread-safe-shell.
Once the event is scheduled in dzn::pump, at some point in time it will be picked up by the Dezyne private thread. Part of the processing of the passwordEntered event is the ‘blocking’ iTimer.cancel call:
The processing of the passwordEntered event is done on the Dezyne private thread, but remember that the main thread is blocked until the related return statement is sent (signaling that the event has been fully processed). Now, when the Dezyne private thread invokes the iTimer.cancel event, it will encounter ‘blocking’ as the implementation of iTimer.cancel in RobustTimer is ‘blocking’:
The Dezyne runtime libraries provide support for ‘blocking’ in the sense that an execution on the Dezyne private thread can be suspended and released again, which ‘blocking’ makes use of. Invoking a ‘blocking’ event suspends the execution of the current event from the dzn::pump and starts execution of the next available event. This subtle implementation in the Dezyne runtime is how the blocked execution can be released again; the release will occur from another event scheduled in dzn::pump. In the figure above, this other event is ext_iTimer.cancelled. When the execution is released, the Dezyne private thread can continue processing the iTimer.cancel event which eventually returns and both threads are released again.
Implications of ‘blocking’ for Dezyne applications
Obviously, a big concern regarding whether you can use ‘blocking’ in your Dezyne models is that your native code running on the main thread must not suffer from being blocked. The Sensor polling implementation could just as easily have been handled in the event loop instead of through dzn::pump; however, then the debouncing algorithm could suffer from not polling consistently. The current event loop only handles the entering of passwords; this is so trivial that blockage is not a concern. However, when more execution takes place on the main thread you will have to make sure that it is allowed to be postponed until the blocking call returns.
A second concern is that the execution semantics of your platform of choice must support some way to schedule events outside of normal main thread activity. If the main thread is blocked and the private Dezyne thread is waiting for events in dzn::pump, the only way to continue is to have some sort of activity outside of the two existing threads. This activity can then release the Dezyne private thread, which can release the main thread again. Examples of such activity are Interrupt Service Routines (ISRs) or raising a signal like in the native Timer implementation.
Another consideration you should make is whether the asynchronous process you’re simplifying with ‘blocking’ even benefits from it. RobustTimer could be simplified even more by having a ‘blocking’ start event that returns upon timeout. This would make the components requiring ITimer even simpler, perhaps, but it would no longer be possible to cancel the timer.
In a way, we even removed functionality from Controller by synchronizing the cancellation process; at the end of the ‘external’ tutorial, we implemented a “queue” to allow passwords entered during the cancellation to be stored and handled when the process ends. By hiding this process from Controller, such interactions are no longer necessary. Sometimes you can make clever use of the asynchronous period and if that is the case, you will have to consider whether the simplified models outweigh the clever usage.
All in all, there are some hard and some soft constraints to using the ‘blocking’ keyword: hard constraints are the execution model on your main thread and whether it can handle being blocked for an arbitrary amount of time; soft constraints mostly consist of design decisions and whether you can make use of the asynchronous process or not.