Onboarding 3: Execution Semantics

After introduction of the first two onboarding tutorials it is time to dive into the execution semantics of Dezyne. With execution semantics we mean how and in which order the events are being processed.

Out of the box Dezyne is suitable to be fit in environments where the user/developer can confirm there is only a single thread on which the Dezyne components execute their actions. This is the case when the Dezyne models are deployed in an OS-less environment and where for instance one main-loop is in control of sensing for input and can feed the Dezyne models with in and out events from the environment.
An alternative setting can be an environment where the user/developer has arranged an own dispatcher thread that receives possible concurrent event calls but will propagate them one by one to Dezyne.

Where it drills down to is that to allow Dezyne components to execute safely, they must be placed in an environment where events are triggered strictly sequentially towards the Dezyne system. The next figure depicts the ingredients.

Execution Semantics

Fundamentally to understand is that ‘in events’ are direct function calls on the component behaviour and are therefore synchronous. At the other hand ‘out events’ are always posted to a queue first before they are picked up by the component behaviour. The type of out event can be either synchronous or asynchronous.

  1. On calling an ‘in event’ the component starts executing the actions according to the behaviour specification. During execution of these actions the component can not be interrupted and will not notice that new events arrived. We call this run-to-completion semantics. After executing all actions - only then - the component behaviour is open for a next in- or out-event.
  2. When an ‘out event’ is entering a component, it is first delivered to the component’s queue. This moment of entering and queuing can be in both situations where the component behaviour is busy or idle. Notice that the thread that delivers the out event is immediately unblocked afterwards and can continue its own processing.
  3. The queued ‘out event’ must first be dequeued before it is being processed. This moment can be when the component behaviour is idle or when the component behaviour finishes a run-to-completion execution. In this latter case it will probe the queue for a posted out event and pick it up for a new round of run-to-completion. It keeps busy processing until the queue is empty. For the client of the component, this means the post condition is always the out event queue is empty when the client is allowed to call a new ‘in event’.

To reinstate: keep in mind that the execution environment must trigger in and out events sequentially from only one thread or process. A Dezyne component is not suitable to be called from a multi-threaded/concurrent context. This has the consequence of unwanted re-entrancy of the component behaviour which is illegal while it is already busy executing actions.

This situation typically can occur on the outer border of a Dezyne system. And for this situation Dezyne Code Generator offers the option to generate an additional Thread-Safe Shell around a System Component.

Execution Semantics (TSS: thread-safe shell)

The Shell option of the Dezyne Code Generator provides for the safe and reliable execution of events that originate from concurrent threads/processes. It shields the Dezyne components from ‘direct access’ by the environment that would violate the standard execution semantics. The mechanism used for this, is a dedicated dispatcher thread called dzn_pump with its own queue that will sequentialize all in and out events coming from the environment.

  1. On the call of a client the dzn_pump ensures the condition that only one client at a time is allowed to execute its in event towards the System Component. So, concurrent clients are blocked until the serviced client has finished its call. Concurrent clients are serviced on First-in-First-out basis.
  2. Delivery of out events by the concurrent environment will be orchestrated by the dzn_pump via its own queue first. The thread that delivers that out event is immediately free to continue its own processing. However as a consequence of this decoupling, it means that all out events become asynchronous. Whilst in between Dezyne components you can have also synchronous out events. So to repeat, the Dezyne component on the bottom border of the Shell can not use synchronous out events anymore when using the thread-safe shell.
  3. In this last step as indicated in the figure, we reached the spot where the dzn_pump dispatcher dequeues one in- or out- event at a time and triggers it towards the Dezyne component according to the standard execution semantics.

Placing a thread-safe shell around a Dezyne System is a convenient way to fit Dezyne into a multi-threaded context. This implies that the Dezyne components at the bottom border of a TSS-ed system must deal with multi-threaded phenomena like Race Conditions. To make any or all race conditions visible the required ports must be marked with the keyword external. With this annotation the Dezyne Verifier will check components on delayed delivery of out events. In practice this can mean that a few more rule cases need to be added to the component behaviour to deal with the additional race condition situations. When making use of the Shell option of the Dezyne Code generator, it will check on and force the presence of the external keyword on the required ports.

Conclusion

Understanding the Dezyne execution semantics is important when making the step to run the Dezyne generated code in the target environment. In general, a system of multiple Dezyne components run together with the standard execution semantics. This includes ability to model asynchronous out events as well as synchronous out events.

The developer must pay close attention to the execution characteristics of the target environment to decide whether an additional Thread-Safe Shell is needed to safely run the system of Dezyne components. If that’s the case, then the bottom border Dezyne components must take into account race conditions that can originate from the multi-threaded context. Meaning, out events may be delivered at a later moment then ‘expected’. Extra rule cases in the Component Behaviour can deal with these situations and there also exist some Best Practices to deal with race conditions.