[LAD] Best practice for sharing complex data structures with a jack callback function

Jack O'Quin jack.oquin at gmail.com
Sat Jul 7 03:13:36 UTC 2007


On 7/6/07, Fons Adriaensen <fons at kokkinizita.net> wrote:
> On Fri, Jul 06, 2007 at 06:48:54PM -0500, Andres Cabrera wrote:
>
> > The long title says it all... =) I want to pass some data generated
> > from real-time audio analysis to the main program (only in that
> > direction, i.e. only the jack callback writes to the structures). Are
> > there synchronization issues involved? Can I use a C++ object for this,
> > or is that an absolute no,no?
>
> You can use a C++ object for this (it's the cleanest way IMHO),
> but there are some points to watch out for.
>
> * Within the callback you cannot safely allocate and object.
>   It must exist statically, or be allocated at a safe place,
>   and pointer given to the callback.
>
> * The main program has to know when it should read the object.
>   Either use a flag in the object, and let the main program
>   poll it periodically (if the delay is acceptable), or use
>   a semaphore. POSIX semas are simple and easy to use, and
>   you can signal them safely from within the callback.
>
> * The callback has to know when it can (re)write the object.
>   Clearly it should never wait on a sema, and it is already
>   periodic, so here you can use a flag in the object.
>
> * If you use a flag and polling in both directions make
>   sure to use two separate flags. One is read-only for
>   the main program and write-only for the callback and
>   vice versa.
>
> Things get a bit (but not much) more complex if you need
> more than one object, but this should get you started.

Beware!  Flags are not enough to guarantee that you get
a coherent copy of the structure.  The safest technique is
using "guard" words at the start and end of the structure.
The process thread updates the guard word at the beginning
to a unique value (time of day, or increment an integer).
Then, it performs the update.  Last it copies the first guard
word to the one at the end.

The code in the other thread must copy the entire struct
to another memory area, then check that the guard words
are equal.   If not, it loops back and tries the copy again.
After several tries, it should usleep() a while before trying
some more.  Something like this (see libjack/transclient.c)...

/* copy a JACK transport position structure (thread-safe) */
void
jack_transport_copy_position (jack_position_t *from, jack_position_t *to)
{
	int tries = 0;
	long timeout = 1000;

	do {
		/* throttle the busy wait if we don't get the answer
		 * very quickly. See comment above about this
		 * design.
		 */
		if (tries > 10) {
			usleep (20);
			tries = 0;

			/* debug code to avoid system hangs... */
			if (--timeout == 0) {
				jack_error ("hung in loop copying position B");
				abort();
			}
		}
		*to = *from;
		tries++;

	} while (to->unique_1 != to->unique_2);
}

This is adequate for normal SMP Intel and AMD machines with strong
memory ordering.  On some other platforms, a memory fence instruction
is probably needed after updating the struct..  The spin wait is mainly for
SMP.  On a UP it does not help, so you need to sleep.

The reason for doing something comp;icated like this rather than using
a mutex is that there are realtime priority inversion problems if the proces
thread has to wait on a mutex, because the other thread is not running
with SCHED_FIFO priority.
-- 
 joq



More information about the Linux-audio-dev mailing list