Realtime threads options & priorities

Hi there,

I have an app developed with JUCE (v7). I am not directly the developper of this app but I have a line on him when needed.

I am developing a specific audio driver porting Dante protocole into this app. And running all of this on intel machines with multi-cores (Efficient & Performance) and a lowlatency linux kernel.
My driver is compliant with the AudioIODevice class.

I have then 2 threads in my driver when running the JUCE app : one for push/pulling sample to/from the Dante driver, one to push/pull sample to/form JUCE as a callback. They have to synchronize, naturally at some point…
Combining both Audinate examples and JUCE Alsa driver, I have ringbuffers inbetween to allow different buffer/vector size on each side (JUCE callback vector size is settable from 32 to 4096 samples in the interface, driver push/pull block size is not, fixed to 32 samples). And a mechanism to notify (with atomic) between the threads.

Both of these threads have an RT priority in the POSIX system (realtime -99) Thank’s to Thread::RealtimeOptions structure from JUCE).
However in this configuration, I have weird artefacts (from regular clicks to ring modulator effect) with bigger block size on JUCE callback (>= 256 samples).

There probably is something wrong with my code but…
1/ it was working great in JUCE 6.1.3 and
2/ strangely, those artefacts stop completely and audio comes back clean as soon as I use the chrt command of Linux to set JUCE Callback priority to something else (and lesser priority) than RT/-99.
=> I really don’t understand why this change would make things better…

Also, changing the priority value in the RealtimeOptions struct does not seem to affect the effective priority of the threads reported by htop command.

Any idea/pointer on this kind of things will be appreciated!
Thank’s a lot

Benjamin

The old embedded maxim applies: two realtime threads in the same app is one too many realtime threads.

You could probably get better results if you have one realtime thread doing the i/o of both queues.

But to address your issue with priorities, without knowing more details about your app it sounds, at first, like you’re running into the classic contention issue and the threads, being equivalent priority, are producing a race condition for the associated resources.

If you really do have to have two realtime threads, set one slightly lower priority than the other (by a single unit even suffices), so that the system is not racing to supply enough ticks to both threads … when you change the priorities with chrt, you give the kernel just enough slice to defeat the race condition and things smooth out.

Do you think your “maxim” applies to plugins that run in a host? If so, I do not agree

Thank you for your answer @anon48770766

I do agree about the maxim and that it would be better with only one realtime thread. However, I have some constraints on the Dante/Audinate side that made me write the code like this…
It may possible to do it very differently but implies a big refactoring that will take more time than I have right now.

I also see clearly the race conditions you are mentioning and thought the same thing.
The shared resource however (ringbuffer) is supposed to be non-blocking. Only the write & read heads are atomic and only one of the thread modify each of them. Close to a lock-free-fifo (or so I hope!).

Last question remains: I cannot make JUCE set the priority just a little lesser with the priority field of the RealtimeOptions struct. From my first rough test (I have to investigate further) this parameter does not seem to have any visible effect… Why is that?

I will continue to investigate the code of JUCE for this last question and try to evaluate the refactoring need for a 1 realtime thread version.

Anyone has already fiddle with this parameter?
I saw this post as well : startRealtimeThread examples - #3 by lcapozzi
But it is centered around Apple problems…

Thank’s

Benjamin

My bad on this one. Sorry, I had an error in my code (not enough caffeine?). Once it’s correctly programmed, a lesser value in the struct effectively means a lesser priority shown easily with htop.

I’ll try this config as a quick fix and probably dig into the code again to have only one realtime thread later.

Best,

Benjamin

1 Like

Hey,

Any Chance you can give me more Information about the setup and solution. I am looking for something similar

If I remember correctly, that implementation details of realtime threads/priority management (and the mechanism for changing the priority) was heavily overhauled early in v7 (Thread: Introduce a new Thread backend · juce-framework/JUCE@d3cff37 · GitHub)
and after problems on different system (especially MacOS) got a few rounds of fixes mainly based on this thread: [Bug] Serious Problem with HighResolutionTimer since JUCE 7.0.3

Therefore, the original problem with starting realtime threads described here might have been fixed.

Concerning the task of shuffling samples from one point to another: I also don’t see the point in trying to start two threads, that are essentially doing the same work. If you can pickup samples from Audinate at an “arbitary” time, you can do that directly from your JUCE callback.
If Audinate is actually the one supplying the thread and you merely hook a callback, you run into a similar problem (with a similar solution) as Using different audio devices at the same time?

Thanks a lot for clarifying on this part. The other part I am interested in, is the Port of Dante on Intel and the BenCellos solution. Any more Info about this? I know that this question is not directly related to Juce

Hi @basdl,
I was not exactly “porting” Dante on Intel… but I will not discuss the Audinate part in here.
As for how I achieved this project with JUCE, well, I still have 2 realtime threads: one push/pulling the samples from/to Dante, one being the “driver” for the JUCE part.
With this architecture, I assume I have a bigger latency than what I could theoretically achieve with only one thread. But the project arrived to a “stable” state that we decided to preserve rather than spend a lot of time re-writing the whole JUCE part from scratch without any insurance of doing better.

I sent you a PM with my e-mail address if you’re feeling like sharing privately more of what you are trying to acheive.

Best,
Benjamin

Maybe this talk has a few insights that could offer help to further optimize realtime performance?

https://www.youtube.com/watch?v=1TgzjOoiqIs

It offered a nice case study on how to optimize realtime threads on Linux. Even though it’s about a high channel count, the basic concepts seem useful overall.