AudioProcessorGraph thread safety


#1

I’m currently implementing my own heavily simplified version of an AudioProcessorGraph for educational purposes. One thing I haven’t been able to wrap my head around is how AudioProcessorGraph remains thread-safe and avoids pops and clicks when modifying the graph from a non-audio thread.

For example, in the plugin host demo, it looks like connections and nodes are being freely modified from the message thread, and I’m left scratching my head as to how that safely propagates to the model used for rendering by the audio thread. There doesn’t seem to be any sort of async lock-free command queue in use (which is what I’m using in my implementation), what I see happening is the graph rendering ops list being rebuilt on the modifying thread and being swapped out on the message thread. While I can see how that works, I don’t really get how Node objects are being safely added/removed. Is there some sort of gasp locking going on? Or are we eating race conditions?

AudioProcessorGraph is some real voodoo magic, any insight is appreciated.


#2

It is all a bit voodoo-y… The nodes are ref-counted and held by both the new and old execution sequence objects, so it should be impossible to use a dangling pointer to a node.


#3

Are graph modifications intended to be safe from any thread? It looks like addNode at the very least does allocation for a new Node object on the calling thread (suggesting it shouldn’t be called on the audio thread), but then it goes on to trigger an async update which would run on the message thread anyway…


#4

No - all graph changes are expected to happen on the message thread.


#5

Okay, so I take it the AsyncUpdater usage is to make sure that the method returns immediately. I was able to follow the code and see that the graph rebuilding and intermediate buffer reallocation happens during the async callback, and then a tiny callback lock is used to synchronize with the audio thread (scandalous!) so the new array can be swapped in.

Not so voodoo anymore (well, excluding the topological sorting minimizing intermediate buffers - but that’s for another day), thanks Jules!