Skip to content

Commit

Permalink
VST3 Client: Fix setComponentState() threading on Linux
Browse files Browse the repository at this point in the history
Before this commit it was possible for the plugin to transfer control
to its internal MessageThread and call
IComponentHandler::restartComponent() from it.
  • Loading branch information
szarvas committed Jul 14, 2022
1 parent d246cc2 commit 6c09aa6
Showing 1 changed file with 63 additions and 21 deletions.
84 changes: 63 additions & 21 deletions modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,34 @@ using namespace Steinberg;
//==============================================================================
#if JUCE_LINUX || JUCE_BSD

enum class HostMessageThreadAttached { no, yes };

class HostMessageThreadState
{
public:
template <typename Callback>
void setStateWithAction (HostMessageThreadAttached stateIn, Callback&& action)
{
const std::lock_guard<std::mutex> lock { m };
state = stateIn;
action();
}

void assertHostMessageThread()
{
const std::lock_guard<std::mutex> lock { m };

if (state == HostMessageThreadAttached::no)
return;

JUCE_ASSERT_MESSAGE_THREAD
}

private:
HostMessageThreadAttached state = HostMessageThreadAttached::no;
std::mutex m;
};

class EventHandler final : public Steinberg::Linux::IEventHandler,
private LinuxEventLoopInternal::Listener
{
Expand All @@ -141,7 +169,8 @@ class EventHandler final : public Steinberg::Linux::IEventHandler,
LinuxEventLoopInternal::deregisterLinuxEventLoopListener (*this);

if (! messageThread->isRunning())
messageThread->start();
hostMessageThreadState.setStateWithAction (HostMessageThreadAttached::no,
[this]() { messageThread->start(); });
}

JUCE_DECLARE_VST3_COM_REF_METHODS
Expand Down Expand Up @@ -173,6 +202,17 @@ class EventHandler final : public Steinberg::Linux::IEventHandler,
refreshAttachedEventLoop ([this, runLoop] { hostRunLoops.erase (runLoop); });
}

/* Asserts if it can be established that the calling thread is different from the host's message
thread.
On Linux this can only be determined if the host has already registered its run loop. Until
then JUCE messages are serviced by a background thread internal to the plugin.
*/
static void assertHostMessageThread()
{
hostMessageThreadState.assertHostMessageThread();
}

private:
//==============================================================================
/* Connects all known FDs to a single host event loop instance. */
Expand Down Expand Up @@ -240,7 +280,8 @@ class EventHandler final : public Steinberg::Linux::IEventHandler,
if (messageThread->isRunning())
messageThread->stop();

MessageManager::getInstance()->setCurrentThreadAsMessageThread();
hostMessageThreadState.setStateWithAction (HostMessageThreadAttached::yes,
[] { MessageManager::getInstance()->setCurrentThreadAsMessageThread(); });
}
}

Expand Down Expand Up @@ -283,13 +324,26 @@ class EventHandler final : public Steinberg::Linux::IEventHandler,
std::multiset<Steinberg::Linux::IRunLoop*> hostRunLoops;
AttachedEventLoop attachedEventLoop;

static HostMessageThreadState hostMessageThreadState;

//==============================================================================
JUCE_DECLARE_NON_MOVEABLE (EventHandler)
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EventHandler)
};

HostMessageThreadState EventHandler::hostMessageThreadState;

#endif

static void assertHostMessageThread()
{
#if JUCE_LINUX || JUCE_BSD
EventHandler::assertHostMessageThread();
#else
JUCE_ASSERT_MESSAGE_THREAD
#endif
}

//==============================================================================
class InParameterChangedCallbackSetter
{
Expand Down Expand Up @@ -968,25 +1022,8 @@ class JuceVST3EditController : public Vst::EditController,
//==============================================================================
tresult PLUGIN_API setComponentState (IBStream* stream) override
{
if (! MessageManager::existsAndIsCurrentThread())
#if JUCE_LINUX || JUCE_BSD
{
tresult result = kResultOk;
WaitableEvent finishedEvent;

MessageManager::callAsync ([&]
{
result = setComponentState (stream);
finishedEvent.signal();
});

finishedEvent.wait();
return result;
}
#else
// As an IEditController member, the host should only call this from the message thread.
jassertfalse;
#endif
assertHostMessageThread();

if (auto* pluginInstance = getPluginInstance())
{
Expand Down Expand Up @@ -1965,7 +2002,12 @@ class JuceVST3EditController : public Vst::EditController,
owner->lastScaleFactorReceived = editorScaleFactor;

if (component != nullptr)
{
#if JUCE_LINUX || JUCE_BSD
const MessageManagerLock mmLock;
#endif
component->setEditorScaleFactor (editorScaleFactor);
}
}

return kResultTrue;
Expand Down Expand Up @@ -2776,7 +2818,7 @@ class JuceVST3Component : public Vst::IComponent,
{
// The VST3 spec requires that this function is called from the UI thread.
// If this assertion fires, your host is misbehaving!
JUCE_ASSERT_MESSAGE_THREAD
assertHostMessageThread();

if (state == nullptr)
return kInvalidArgument;
Expand Down

0 comments on commit 6c09aa6

Please sign in to comment.