previous up next
7.1 The dynamic view of the architecture

Figure 7.1 shows an example of an interaction between the main components of the system in runtime. We adopted the notation used in [GHJV95] to which we superposed the activity of the threads. The time flows from top to bottom. The vertical rectangles show when an object is active. A method call is represented by an arrow from the calling to the called object. The label above the arrow indicates the method name. We have tried to visualize the switching between the three threads: when a vertical rectangle is shaded gray, the thread in which the object is called is suspended. On the right hand side is indicated which of the threads is active.


 
Figure 7.1
 
Figure 7.1: The interaction diagram shows the interaction of the main components of the system in time (see text for a more detailed explication.) (ET = Event Thread, ST = Synthesis Thread.)

In figure 7.1 the event thread calls a program to handle an add-event. The application of the event's program provokes the creation of a synthesis process. To store the new object storage space is requested from the heap. After the creation, the synthesizer is requested to add the new synthesis process to the list of active processes. From this point on, the synthesis process is said to be active. The life of a synthesis process knows four life stages. When it is just created its state is called embryo. When it is added to the synthesizer it is called once with the message birth. The birth method allows the process to do specific work when it is called for the first time (a fade-in, for example). The process is then said to be alive and will subsequently receive the message live. A kill event (the third event in the figure) will cause a currently active process to be removed from the synthesizer. The process is not immediately eliminated; its state is set to dying, and it gets another chance to finish its song. It is called once more with the message die in which it can do a fade-out, for example. In the last stage, called heaven, the process is removed from the array in the synthesis thread.

We can see in the figure that the event thread is constantly interrupted by the synthesis thread. Since the synthesis is more important than the event handling, it has priority. The synthesis is a hard real-time task and should never be delayed since this would introduce audible clicks in the sound output. The synthesis thread has therefore the highest priority. The processing of incoming events is a soft real-time task. The events should be handled as fast as we can. If an event specifies the start of a new sound, for example, the delay should be less than 30 msec. However, it's better to handle the event a little to late (say 50 msec) than not at all. This task has a high priority but lower than the synthesis task. The interaction with the user thru the Scheme shell or any other interface is not, strictly speaking, a real-time task. Granted, to have a good interaction, responses to user input should be fast. Nevertheless, this task has the lowest priority of the three.


 
Figure 7.2
 
Figure 7.2: Priority inversion (IT = Interpreter Thread, ET = Event Thread, ST = Synthesis Thread.)

In most cases the synthesis processes will depend on other objects. For example, an oscillator will depend on a controller object indicating the frequency of the oscillator. The oscillator retrieves the value of the controller at regular time intervals. If the controller object is defined in the Scheme environment, incoming events and the subsequent application of programs can modify the state of the controller. This makes the controller a critical zone (see section 1.3) since it is called by several concurrent tasks. Access to its shared data must be synchronized. This is detailed next. Consider the flow of events shown in figure 7.2. The Scheme interpreter sends an object the message to change its value. The method set is a critical region and the object protects its shared data with a monitor. At that point the synthesis thread wakes up, suspends the Scheme thread (lowest priority), and calls the synthesis process. The synthesis process sends the object the message get to obtain its value. Since the interpreter thread still possesses the monitor (the set method), the synthesis thread will block when it tries to enter the object's monitor. As a result the interpreter thread is resumed again. Before it has had the time to leaves the critical region an event arrives. Now, the event thread wakes up and suspends the interpreter thread (still lower priority). At this point the synthesizer thread must wait for the event thread to finish handling the event. This event handling may take very long and the synthesis thread may be delayed for an unpredictable amount of time. This is the problem of priority inversion we mentioned in section 1.3. It is a well known problem that is generally solved with the technique of priority inheritance. In the situation above, the interpreter thread would inherit the priority of the synthesis thread, in which case it cannot be suspended by the event thread. Despite this fact, the synthesis thread still has to wait for the interpreter thread to leave the critical zone. With priority inheritance, the maximum delay of the synthesizer is no longer unpredictable but related to the size of the critical zone. However, if the computation in the critical zones is reduced to a minimum, these delays will be reduced to a minimum. In the next section we continue the study of the interaction in the special case of the garbage collector.

previous up next