Transport Thread Design

There are nominally 3 threads involved in the control of transport state:

  1. a "user interface" thread. Most typically, this is the GUI thread, but it could also be threads handling various other kinds of UI such as OSC, generic MIDI control, Mackie/Logic Control etc.
  2. the "realtime" aka "process" aka "audio" thread. This is created for us by JACK and runs the JACK process callback, which is where all processing occurs in Ardour.
  3. the "butler" or "transport" thread. This thread has two primary responsibilities. One is managing disk I/O, the other is taking care of transport state related work that cannot be done in real time.

A change to the transport state begins when the UI thread requests a change via one of the Session::request_* methods. This creates a new Session event, which is inserted into the "immediate" event list. The event contains a number of fields that describe the request and any data required to fulfill it.

Very early in the JACK process callback, the session will check the event list and discover the event request. It will pass the event to Session::process_event() which initiates the required steps to satisfy the request. For many events, the work of satisfying the request can be split into two parts: one that can be done within realtime constraints, and another that may take an indeterminate amount of time. The process thread will, as part of Session::process_event(), call a function that does the realtime part, and queues up the second part via the Session variable "post_transport_work", which is a bitset where each bit indicates a particular type of non-realtime work that needs to be done.

Note: when entering the JACK process callback, the value of "post_transport_work" is checked. If any bits are set, it indicates that there is still pending work to be done before the transport state change is complete, and the process callback will exit without doing much of anything at all.

After setting the value of Session::post_transport_work, the event handling function will wake up the butler/transport thread, so that it can get busy with whatever needs to be done. Note that if butler is already awake and doing disk I/O, it will check in between each block of I/O to see if transport work has been queued. This way, substantial disk I/O does not cause us to wait for too long before the transport state change is handled.