1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-29 02:40:05 +00:00

LV2 Host: Avoid potential deadlock in AudioProcessorGraph

Previously, preparing an AudioProcessorGraph containing hosted LV2
plugins would recreate the plugins and then set their state. For plugins
without threadsafe restore, setting the state would take the
callbackLock to avoid races with processBlock. This meant that

- During prepare, the graph would take the processorLock, then the
  processor would take its own callbackLock.
- During playback, the graph would take the processor's callbackLock,
  and then would take the node's processorLock.

This is probably benign (prepare shouldn't be called concurrently with
processBlock at all), but to be on the safe side we now avoid taking the
callbackLock when setting new plugin state during prepareToPlay.
This commit is contained in:
reuk 2022-04-27 18:18:27 +01:00
parent 3c08f8beda
commit 7ea93ce5d2
No known key found for this signature in database
GPG key ID: 9ADCD339CFC98A11

View file

@ -4405,7 +4405,8 @@ public:
numSamples,
sampleRate);
setStateInformation (mb.getData(), (int) mb.getSize());
// prepareToPlay is *guaranteed* not to be called concurrently with processBlock
setStateInformationImpl (mb.getData(), (int) mb.getSize(), ConcurrentWithAudioCallback::no);
jassert (numSamples == instance->features.getMaxBlockSize());
@ -4490,7 +4491,8 @@ public:
return;
lastAppliedPreset = newProgram;
applyStateWithAppropriateLocking (loadStateWithUri (presetUris[(size_t) newProgram]));
applyStateWithAppropriateLocking (loadStateWithUri (presetUris[(size_t) newProgram]),
ConcurrentWithAudioCallback::yes);
}
const String getProgramName (int program) override
@ -4527,16 +4529,7 @@ public:
void setStateInformation (const void* data, int size) override
{
JUCE_ASSERT_MESSAGE_THREAD;
if (data == nullptr || size == 0)
return;
auto begin = static_cast<const char*> (data);
std::vector<char> copy (begin, begin + size);
copy.push_back (0);
auto mapFeature = instance->symap->getMapFeature();
applyStateWithAppropriateLocking (PluginState { lilv_state_new_from_string (world->get(), &mapFeature, copy.data()) });
setStateInformationImpl (data, size, ConcurrentWithAudioCallback::yes);
}
void setNonRealtime (bool newValue) noexcept override
@ -4576,6 +4569,8 @@ public:
AudioProcessorParameter* getBypassParameter() const override { return bypassParam; }
private:
enum class ConcurrentWithAudioCallback { no, yes };
LV2AudioPluginInstance (std::shared_ptr<World> worldIn,
const Plugin& pluginIn,
std::unique_ptr<InstanceWithSupports>&& in,
@ -4596,7 +4591,22 @@ private:
std::move (uiDescriptorIn),
[this] { postChangedParametersToUi(); })
{
applyStateWithAppropriateLocking (std::move (stateToApply));
applyStateWithAppropriateLocking (std::move (stateToApply), ConcurrentWithAudioCallback::no);
}
void setStateInformationImpl (const void* data, int size, ConcurrentWithAudioCallback concurrent)
{
JUCE_ASSERT_MESSAGE_THREAD;
if (data == nullptr || size == 0)
return;
auto begin = static_cast<const char*> (data);
std::vector<char> copy (begin, begin + size);
copy.push_back (0);
auto mapFeature = instance->symap->getMapFeature();
applyStateWithAppropriateLocking (PluginState { lilv_state_new_from_string (world->get(), &mapFeature, copy.data()) },
concurrent);
}
// This does *not* destroy the editor component.
@ -4694,13 +4704,13 @@ private:
return instance.get();
}
void applyStateWithAppropriateLocking (PluginState&& state)
void applyStateWithAppropriateLocking (PluginState&& state, ConcurrentWithAudioCallback concurrent)
{
PortMap portStateManager (instance->ports);
// If a plugin supports threadSafeRestore, its restore method is thread-safe
// and may be called concurrently with audio class functions.
if (hasThreadSafeRestore)
if (hasThreadSafeRestore || concurrent == ConcurrentWithAudioCallback::no)
{
state.restore (*instance, portStateManager);
}