Skip to content

Linux: onIdle() only runs while the plug-in editor is open, so request_restart / request_callback / flush are dropped headless #528

Description

@baconpaul

On Linux, ClapAsVst3::onIdle() runs only while the plug-in's editor window is open. With no window — a headless instance, a plug-in the user never opened, or one whose window was closed again — it does not run at all, and everything the wrapper defers to it is silently dropped.

Why

src/detail/os/linux.cpp has no timer. LinuxHelper::init() and terminate() are empty bodies, attach/detach only push to and erase from _plugs, and LinuxHelper::executeDefered() — the function that would call onIdle() on each attached plug — has no caller. (The one textual reference at line 120 is inside the #if 0 Windows block.) So _os_attached.on() in ClapAsVst3::setActive(true) is a no-op on Linux.

The other two platforms do have it:

  • detail/os/macos.mmMacOSHelper::attach does CFRunLoopTimerCreate + CFRunLoopAddTimer(CFRunLoopGetMain(), ...), callback → executeDefered()onIdle().
  • detail/os/windows.cppWindowsHelper::init creates a message window and SetTimer(_msgWin, 0, 20, NULL); WM_TIMERexecuteDefered()onIdle().

Both pump for the lifetime of the instance, independent of any editor. On Linux the only driver is the IdleHandler timer registered against the host's IRunLoop, and the wrapper only obtains that from the wrapped view — attachTimers(_wrappedview->getRunLoop()) in the createView attach callback, and detachTimers(...) with _iRunLoop = nullptr when the view goes away.

The comment already in onIdle() notes it in passing:

#if LIN
  if (!_iRunLoop)  // don't process timers if we have a runloop.
                   // (but if we don't have a runloop on linux onIdle isn't called
                   // anyway so consider just not having this at all once we decide

What it costs

With no editor open on Linux, all of these are dead, and become dead again as soon as the window is closed:

  • clap_host::request_restart never reaches restartComponentrestartPlugin() only sets _requestRestart, which onIdle() consumes
  • clap_host::request_callback never reaches clap_plugin::on_main_thread
  • clap_params_host::request_flush never flushes
  • the wrapper's own _queueToUI is never drained

Concretely: a plug-in that defers reallocation to its next activate cycle — a changed FFT size, latency, or oversampling factor — asks for a restart that never arrives, and runs on stale settings indefinitely. A plug-in that defers anything to on_main_thread (retiring objects handed back by the audio thread, mark_dirty, deferred parameter rescans) never runs it, so those either pile up or are lost. This is the same class of gap as #521/#524 for AUv2, but Linux/VST3 and only in the no-window case.

The awkward part

I don't think there is a clean fix purely inside the wrapper, and I'd rather raise it than guess. IComponentHandler::restartComponent is [UI-thread & Connected], and on Linux the only UI thread the plug-in can reach is the host's run loop, which VST3 exposes solely through IPlugFrame::queryInterface(IRunLoop) — i.e. only when a view exists. A wrapper-owned thread could service the [thread-safe] CLAP callbacks but must not call restartComponent or performEdit from there.

So possibly the honest answers are some mix of: pump the thread-safe subset from a wrapper-owned timer thread; opportunistically drain at setActive/setProcessing/process boundaries for the parts that allow it; and document that the rest requires an open editor on Linux. Happy to prototype whichever direction you prefer.

Found while tracing why a CLAP plug-in's request_restart never lands in Ardour — that turned out to be an Ardour bug as well (it answers kIoChanged with kNotImplemented, the handler is #if 0), but this one is ours and is independent of the host. Separate from #527, which fixes two unrelated one-liners in the same file.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions