Thread safe way to pass an object from the plugin editor to the processor


#1

As a C++ self-taught, I sometimes stumble upon some lack of basic knowledge. Let me quickly explain the situation:

I’m building a Plugin that generates sound output from file contents, loaded at runtime. I created a struct, containing a ScopedPointer to an audio buffer, that gets created when a new file is loaded and some other pod values, giving some information on how the buffer content should be played back.

The PluginProcessor has an OwnedArray of these structs. The PluginEditor has all the functionality to create such a struct from a file passed to the gui on the heap. Now I’m looking for a thread-safe way to add the new struct to the processor’s OwnedArray. Furthermore, it should not interrupt the audio thread, which reads from the array, so all write-operation to the OwnedArray as well as its possible extension have to be done in between the calls to processBlock.

What’s the best approach to get this working?


#2

Have the UI thread own the pointers. Pass them to the audio thread using a FIFO, when the audio thread no-longer needs the pointer pass it back to the message thread for deletion.


#3

Which needs to be thread safe or be handled in a thread safe manner itself…


#4

juce::AbstractFIfo is okay for this


#5

There are other solutions … have two arrays and swapping pointers over works as well.


#6

Thank you for your response. I think, I wasn’t completely clear about that - the structs should be permanently owned by the processor and will be used multiple times after creation, maybe even when the editor is closed. So currently I allocate them in the editor, fill them with the desired data and then simply add the pointer to the OwnedArray in the processor, so that it will own them from now on.

So the thing I’m scared of is, that I’m calling OwnedArray::add from the message thread (and with that maybe trigger some time-consuming re-allocation under the hood of the OwnedArray) while the audio thread wants to read from the OwnedArray at the same time.

I also thought of using a FIFO as well as having two arrays. With the FIFO, I’m not quite sure when it should be processed? Checking for new data in the FIFO at the beginning of each processBlock call and then perhaps doing some re-allocation through a call to OwnedArray::add from the audio thread seems to be a bad idea to me. I thought of creating another medium priority thread that sleeps most of the time and always gets woken up through a WaitableEvent that is triggered from processBlock as soon as the last read-access to the array was done to check if there is new data in the FIFO to add to the Array but even with this approach I fear that in a worst-case scenario the operation done by this thread couldn’t be finished until the next block is processed in case of small block sizes.

Now, having two Arrays and swapping pointers would work great for general Arrays I think, but I can’t imagine how this should work in case of an OwnedArray? It wouldn’t make so much sense to have two of them in parallel owning the same pointers. Should I end up using three Arrays that are always kept in sync - one owned to manage proper deletion of the objects and to general arrays to swap pointer? Seems to be quite a lot overhead to me :wink:


#7

Just for the sake of interest, are there any safe values for the actual time that could be expected for OwnedArray::add operation in a worst-case-scenario? Maybe I’m bothering about things that won’t even be a problem in real-world-scenarios :smiley:


#8

You could use OwnedArray:: ensureStorageAllocated() to allocate a reasonable number of items in advance. Then the already present elements won’t be disturbed while adding more.


#9

If this only happens occasionally and you (or the user) can live with short dropouts you could use a ScopedTryLock in processBlock() and a normal ScopedLock in your editor:

void Processor::processBlock(...)
{
    ScopedTryLock lock(arrayChangingLock);
    if (! lock.isLocked())
        return;
    [...]
}

void Editor::addToArray()
{
    ScopedLock lock(processor.getArrayAddLock());
    [...]
}

There might be side effects I’m not aware of but at least I haven’t noticed any yet.


#10

The message/ui thread still runs when the editor is closed. You can have actions run on it, e.g.

class X: public AudioProcessor, Timer 
{ 
void timerCallback() override 
{ 
// this code runs on the message thread
};

#11

You could use reference counting (e.g. shared_ptr or the JUCE equivalent) to handle objects into two data structures.


#12

Thank you, both approaches seem good to me and gave me some new ideas. I think I’ll go for two dynamic arrays with std::shared_ptr and swapping references to them (wich should make no difference in terms of performance compared to a pointer, right?) and a scoped lock for that reference, so that it only gets swapped when the audio thread is not reading from the Array.


#13

After digging a bit deeper into the JUCE ReferenceCountedObject class, I decided to change my struct to a class and let it inherit from ReferenceCountedObject. Now I use two ReferenceCountedArrays instead of Arrays of std::shared_ptrs in my processor and swap references. Furthermore, every GUI component from the editor, having access to the data owns a ReferenceCountedObjectPtr to the object it controls.
This seems to work fine on a first try, however, I just thought about what will happen if the plugin is deleted in the host. Is it possible that the destructors of the editor and the processors are called at the same time on different threads, so that both will try to delete the ReferenceCountedObject at the same time? Or will the editor alway be deleted before the processor gets deleted?


#14

The decrementing of the reference count should be an atomic operation, if implemented correctly. Hence there is no chance, that it becomes zero on two different threads.

However, if the processor is the last one to hold a reference inside the processBlock, the destruction can end up on the audio thread. I think this is the only danger to look for.

I would say yes, see documentation for AudioProcessor::createEditor():

  • It’s safe to assume that an editor will be deleted before its filter.

#15

But if the ReferenceCountedArray is a member variable of the Processor class and processBlock only accesses array elements and not creates copies them in the form of ReferenceCountedObjectPtr stack variables, this should never happen, shouldn’t it? Because I assume that the Processor instance won’t be deleted on the audio thread?!


#16

Yes, you are right, in that specific case that will not happen. I think, if you have the ownership of the reference counted objects sorted that well, you don’t need the reference count at all?

But TBH I couldn’t follow the problem in the first place. I wouldn’t put any intelligence into the editor at all. Only short notifications like button clicked, slider to that value, maybe custom elements sending short information. Maybe even a file name. But from there I would do stuff either in a timerCallback, like @jimc proposed (message thread, safe to use) or in a separate background thread.

And in the other direction the editor should be just a dumb view, like looking through a microscope. The thing you are looking at doesn’t have to know, that it is being looked at.

a) this resolves your problems in terms of ownership etc. and
b) you might want to use that functionality e.g. when reading from disk, or automatise stuff etc. You don’t want to limit your functionality to the presence of an editor.
But you might have reasons to want it that way, that I am unaware of…

Yes, that would be literally suicide :wink:


#17

Well, the intelligence mostly lives in my processor’s class in a

addFile (File &fileToAdd) 

member function that handles all the stuff discussed above. In my current approach the editor opens a FileChooser, then calls processor.addFile with the file selected and waits for addFile to return with success. If it returns with success, the file could be read and a new Component is added to the editor giving the user access to control the way the file is played back. So, as addFile is part of the processor but called from the editor, so I assume it will be called from the editor’s thread. I haven’t noticed any kind of noticeable GUI-freezing as this will block the GUI as while the file is loaded, right? So I believe it happens fast enough to work smoothly. However, would you suggest to call addFile from a background thread in this case?


#18

The editor has not it’s own thread, there is one MessageThread, that handles all UI events, from mouse clicks to paint and timerCallbacks for the host and all it’s open editors. (That’s also why the timerCallback are limited in accuracy, as they are only scheduled on the messageThread, but will only be called, when the messageThread becomes free).

For a proof of concept it is fine, but for production definitely. Reading a file is very variable, up to not finishing at all, like the file is on a NFS / samba share, a NTFS with a lot of files in the folder, there are so many possibilities, that can throttle your file reading down to zero…

And while you are reading, the user doesn’t get any paint / updates at all. So it seems frozen, and the user will probably kill the app and eventually deinstall your plugin…
Also, while you block the message thread, also the host cannot repaint.

Also note Jule’s comments on FileChooser in a plugin, (summary: avoid it at all cost, host’s might even not run your modal loops at all in the future). The solution is a non-modal FileChooserComponent as overlay in your editor.

So if you can, use a background thread. Even better, use a reusable thread using ThreadPoolJob (as starting a thread also takes a considerable amount of some time).


#19

Great, this forum is so helpful, :heart: you guys!

Fair point, that makes 100% sense :wink:

I did a five-minute google search and found no explaination what exactly a modal loop is, only lots of forum entries discussing the pros and cons of modal loops :smiley:

Is it about blocking the whole message thread by running something like

while (!userClickedButton) ...

as long as the FileChooser is opened?
Are there good examples on how to do it better with the overlay you mentioned?


#20

A more independent voice might be wikipedia.

It is also specifically a problem in Plug-Ins, as the plugin shares the message thread with the host, blocking not onlyone window, but a whole window stack. There is a risk, that the modal dialog is hidden behind another window, which leads to confusion. It is even worse, if the dialog window is raised from the processor itself, when the user can not get the connection to the actual plugin.

Instead of the FileChooser, create a FileBrowserComponent and display it inside your existing editor. The Editor can inherit FileBrowserListener and implement the callback virtual void fileDoubleClicked (const File &file) to hide the FileBrowserComponent and start the loading.