[linux-audio-dev] jack_callback <-> rest of the world

Florian Schmidt mista.tapas at gmx.net
Sun Oct 30 11:11:18 UTC 2005


On Sun, 30 Oct 2005 14:03:39 +1100
Dave Robillard <drobilla at connect.carleton.ca> wrote:

> On Sun, 2005-30-10 at 00:42 +0200, Richard Spindler wrote:
> > I read the tutorial at http://userpages.umbc.edu/~berman3/ , it uses
> > mutex+condition, is it okay to do this? Are there better ways?
> 
> That's a wonderful tutorial... on how NOT to write a Jack client.
> (There's no lock free data structures "in C" or Linux?  There's one
> _included with Jack_...)
> 
> On mutexes, calling pthread_mutex_trylock in the process thread is okay,
> but pthread_mutex_lock is not.  Don't Do That(TM).

The tutorial client can be fixed pretty easily with this (see below),
though as the comment said, the code was written when there didn't exist
a lockfree ringbuffer implementation. This has changed (jack provides
one). I know that you know this David, just restating the obvious for
less experienced readers. Of course the tutorial client still needs to
be considered broken even with the trylock.

Also the basic problem of signalling (in this case the disk thread that
there is work to do) still persists even with lockless ringbuffers. The
other thinkable approach would be to make the diskthread wakeup
regularly and check whether data is available in the ringbuffer. This is
nasty, too, and unsuitable for some situations.

So there's basically these approaches nowadays

- using named pipes and having the signaller passing single bytes
through it to wake up the signallee that blocks on the pipe. This is
really not correct, though it might work ok in practice (see jack/ardour
(in jack it is used to work around the fact that there's no
inter-process mechanism for this though futexes could be used, too, if i
understand correctly)).

- using condition variables

Btw: i think when using a single mutex only for the condition variable
(as opposed to the tutorial client which uses the same mutex for the
signalling _and_ for synchronizing access to its data structure), the
likeliness of contention is rather unprobable given that that the
signaller aquires (again, of course via trylock) right before and
releases the lock right after the pthread_cond_signal/broadcast. 

Same for the singallee. As pthread_cond_wait releases the mutex while
waiting and reaquires it before handing back control to the signallee
the contention case becomes very unprobable when aquiring and releasing
the lock right before and after the pthread_cond_wait.

Of course with this strategy the case that trylock fails on the
condition mutex needs to be handled gracefully (i.e. remembering for the
next process callback to try again).

Besides the one  mutex used only for the condition var, there maybe need
to be additional mutexes in this scenario for the shared data
structures. Of course from the process callback pthread_mutex_lock is
still a nono (again use trylock and handle failure gracefully). But i
suppose most locking for the shared data can be worked around with
lockless data structures...

Btw: in the general case the signallee should always check for spurious
wakeups which is not nessecary with the capture_client from the jack
distribution as it doesn't hurt to wake up once or twice too often once
in a while (at least not when using a ringbuffer - when it's empty the
signallee simply goes back to waiting). Maybe the pthread_mutex_lock
calls might be moved around the pthread_cond_wait call. The disk thread
has the mutex locked all the time during writing.

int
process (jack_nframes_t nframes, void *arg)

{
        thread_info_t *info = (thread_info_t *) arg;
        jack_default_audio_sample_t *in;
        sample_buffer_t *buf;
        unsigned int i;

        if (!info->can_process) {
                return 0;
        }

        /* we don't like taking locks, but until we have a lock
           free ringbuffer written in C, this is what has to be done
        */

        if (pthread_mutex_trylock (&buffer_lock) != 0) {
		/* this is the unprobable contention case.
		   we will simply do nothing here. audio 
		   will be lost. better than an xrun though */

		return 0;
	}

	/* ok, aquired the mutex, so we can do our thing */

        buf = get_free_buffer (nframes, nports);

        for (i = 0; i < nports; i++) {
                in = (jack_default_audio_sample_t *) jack_port_get_buffer (ports[i], nframes);
                memcpy (buf->data[i], in, sizeof (jack_default_audio_sample_t) * nframes);
        }

        put_write_buffer (buf);

        /* tell the disk thread that there is work to do */
        
        pthread_cond_signal (&data_ready);
        pthread_mutex_unlock (&buffer_lock);

        return 0;      
}

Have fun and correct me where wrong,
Flo

-- 
Palimm Palimm!
http://tapas.affenbande.org



More information about the Linux-audio-dev mailing list