Onboarding 2: Synchronous versus Asynchronous

Now you have been introduced to the interface, component and a system it is time to precisely distinct two types of communication between two components.

Synchronous communication

Basically, synchronous communication is equivalent to a simple function call like in general purpose programming languages. The thread of the client calls an ‘in event’ of the server and this thread hops its focus on the server side where the execution continues until all actions there have been processed. At that final moment the thread returns to the client to allow and continue processing there. In the meanwhile it is important to understand that as long as calling thread has its focus on the server side, the client itself is ‘blocked’. This means the client can not do other things in parallel with the server.

For small and swift ‘in events’, like setters and information requests, synchronous calls are fine as it is not expected its thread is being occupied for a long duration. However for longer operations, like toasting a sandwich, this can impose unwanted effects such as an unresponsive system.

The Toaster Controller re-modelled with a synchronous toasting process:

component Controller
{
provides IController api;
requires IHeater heater;
requires ITimer timer;

behavior
{
    enum State {Ready, Toasting};
    State s = State.Ready;

    [s.Ready]
    {
        on api.Toast(): 
        {
            heater.Start();
            timer.Sleep($60 * 1000$);
            heater.Stop();
        }
    }

} // behavior
} // component

In real life, one can imagine toasting a sandwich which a certain long toasting time. Say, that halfway toasting time the user senses a burned smell coming out of the toaster. Then he/she probably would like to interrupt the toasting process. However if the controller is modelled in a synchronous way, interruption is not possible. Concluding the user has to wait the original preset toasting time and ultimately is confronted with a burnt sandwich.

Many organizations specify a system requirement where synchronous operations are not allowed to take longer than a certain number of milliseconds. To conform in practice, it then takes real timing measurements with profiling for instance to determine any impact. Since Dezyne is dependent on real function calls to its environment, the developer should pay attention to the execution time. With the event tracing facility of Dezyne, one can inspect the events that are called and received. By adding timestamps to this tracing, the developer has means to inspect and measure the time it takes for events to ‘go around’.

One notable advantage of modelling synchronously, is that the developer has the possibility to intentionally block the client during a transaction. There is even a certain keyword in Dezyne to block the client while internally executing asynchronous behaviour. All this while the component does not have to deal with client interventions and this can make modelling easier. But in the end, this is may not turn out to be a golden solution since the environment may already have posted events in the Dezyne queue and must they be processed anyhow at the moment the component has finished its current actions.

So ultimately the advice is to utilize synchronous communication only in cases where the timed roundtrip is expected to be short.

Asynchronous communication

In the first article the toasting process was modelled asynchronously. When we look closer to the sequence diagram in the figure below it can be seen that after the ‘kick-off’ (which is a synchronous call) the calling thread returns to the client. Now, client and server run independently in parallel where the server is concentrating on toasting and the client has the ability to do other things. With other things it includes being responsive for interruption. After a while when the timer emits the Timeout() out event, the server side stops the heater and responds Done() to the client. When the client thread handles this Done() out event it is occupied only for a short time.

In case the user of the Toaster decides to interrupt, it simply calls Cancel() during the moment the server was independently processing toasting. Notice that the out-of-the-box Dezyne runtime semantics has the beauty that when cancelling toasting, the timer is stopped synchronously and according to its interface the timer will not ever emit Timeout(). This means also the Cancel() in event does not need a further out event to confirm to the user cancellation has completed.

Note that there can (and will) be circumstances that the environment has been set up asynchronously which can mean that the Cancel() in event must be coupled with a Cancelled() out event to confirm. This all depends on the behaviour(s) of the required interfaces and can all easily be modelled with Dezyne.

Interface IController extended with Cancel():

interface IController
{
in void Toast();
in void Cancel();
out void Done();

behavior
{
    enum State {Ready, Toasting};
    State s = State.Ready;

    [s.Ready]
    {
        on Toast: { s = State.Toasting; }
    }

    [s.Toasting]
    {
        on Cancel: s = State.Ready;
        on inevitable: { Done; s = State.Ready; }
    }
} // behavior
} // interface

Component Controller extended with handling the Cancel() request:

component Controller
{
provides IController api;
requires IHeater heater;
requires ITimer timer;

behavior
{
    enum State {Ready, Toasting};
    State s = State.Ready;

    [s.Ready]
    {
        on api.Toast(): 
        {
            heater.Start();
            timer.Create($60 * 1000$);
            s = State.Toasting;
        }
    }

    [s.Toasting]
    {
        on api.Cancel():
        {
            heater.Stop();
            timer.Cancel();
            s = State.Ready;
        }

        on timer.Timeout():
        {
            heater.Stop();
            s = State.Ready;
            api.Done();
        }
    }
} // behavior
} // component

Conclusion

It is important to decide where and when to model behaviour synchronously or asynchronously. Take into account that when Dezyne events are called, it needs a bit of processing time and often depends on calls to the environment where your Dezyne models are integrated. Also think of responsiveness of the system and possible functional system requirements that intentionally want the ability to divert instructing the interface/component to perform other actions.