From 8b168b310ca83e4df30f0be5c848efc9d256c0f2 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 29 Apr 2021 03:09:42 +0200 Subject: [PATCH] Fix mutual recursion with latency in Ardour/Mixbus This would cause Ardour and Mixbus to freeze when inserting a latency introducing (JUCE based) VST3 plugin. As mentioned in #98. --- CHANGELOG.md | 3 +++ .../vst3-impls/component-handler-proxy.cpp | 5 ++-- src/wine-host/bridges/vst3.cpp | 18 +++++++++++-- src/wine-host/bridges/vst3.h | 27 +++++++++++++++++++ 4 files changed, 49 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 519c5111..fd681f6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,9 @@ Versioning](https://semver.org/spec/v2.0.0.html). plugins. We now explicitly reparent the window to back the root window first before deferring the window closing. This should work around the issue, while still keeping editor closing nice and snappy. +- Prevented latency introducing plugins from causing **Ardour** and **Mixbus** + to freeze. This for example prevents _Neural DSP Darkglass_ from freezing when + used under those DAWs. - _PSPaudioware InifniStrip_ would fail to initialize because the plugin expects the host to always be using Microsoft COM and it won't initialize it by itself. InfiniStrip loads as expected now. diff --git a/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp b/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp index f024ca74..b9d3e0b9 100644 --- a/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp +++ b/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp @@ -61,8 +61,9 @@ Vst3ComponentHandlerProxyImpl::endEdit(Steinberg::Vst::ParamID id) { tresult PLUGIN_API Vst3ComponentHandlerProxyImpl::restartComponent(int32 flags) { - return bridge.send_message(YaComponentHandler::RestartComponent{ - .owner_instance_id = owner_instance_id(), .flags = flags}); + return bridge.send_mutually_recursive_message( + YaComponentHandler::RestartComponent{ + .owner_instance_id = owner_instance_id(), .flags = flags}); } tresult PLUGIN_API Vst3ComponentHandlerProxyImpl::setDirty(TBool state) { diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 51c048a4..06a1041e 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -1300,8 +1300,22 @@ size_t Vst3Bridge::register_object_instance( }, [&](const YaComponent::SetActive& request) -> YaComponent::SetActive::Response { - return object_instances[request.instance_id] - .component->setActive(request.state); + // NOTE: Ardour/Mixbus will immediately call this + // function in response to a latency change + // announced through + // `IComponentHandler::restartComponent()`. We + // need to make sure that these two functions are + // handled from the same thread to prevent + // deadlocks caused by mutually recursive function + // calls. + // TODO: Check if this causes any issues when activating + // plugins while simultaneously resizing another + // instance of the same plugin + return do_mutual_recursion_or_handle_in_main_context< + tresult>([&]() { + return object_instances[request.instance_id] + .component->setActive(request.state); + }); }, [&](const YaPrefetchableSupport::GetPrefetchableSupport& request) diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index 82fbd9bd..f671c522 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -256,6 +256,11 @@ class Vst3Bridge : public HostBridge { * but in this sequence that thread is being blocked by a call to * `IPlugFrame::resizeView()`. * + * We also need to use this for when a plugin calls + * `IComponentHandler::restartComponent()` to change the latency, and when + * the host then calls `IAudioProcessor::setActive()` in response to that to + * restart the plugin. Otherwise this will lead to an infinite loop. + * * The hacky solution here is to send the message from another thread, and * to then allow this thread to execute other functions submitted to an IO * context. @@ -346,6 +351,28 @@ class Vst3Bridge : public HostBridge { return do_call_response.get(); } + /** + * The same as the above function, but we'll just execute the function on + * this thread when the mutual recursion context is not active. + * + * @see Vst3Bridge::do_mutual_recursion_or_handle_in_main_context + */ + template + T do_mutual_recursion(F f) { + std::packaged_task do_call(std::move(f)); + std::future do_call_response = do_call.get_future(); + + if (std::lock_guard lock(mutual_recursion_contexts_mutex); + !mutual_recursion_contexts.empty()) { + boost::asio::dispatch(*mutual_recursion_contexts.back(), + std::move(do_call)); + } else { + do_call(); + } + + return do_call_response.get(); + } + /** * Register a context with with `context_menu`'s ID and owner in * `object_instances`. This will be called during the constructor of