fix crash daw_vst_bridge khi đóng editor Nexus đang play: reload instance trong close job + silence same-plugin quanh createInstance/createView
This commit is contained in:
Binary file not shown.
@@ -41,6 +41,11 @@ public:
|
||||
// inside processAudioBlock on the same instance).
|
||||
virtual bool needsReload() const { return false; }
|
||||
virtual void setReloading(bool /*on*/) {}
|
||||
// VST3: rebuild the plugin instance (terminate + loadPlugin) on the owning
|
||||
// apartment thread. The bridge calls it after tearing an editor down so the
|
||||
// audio loop never processes an instance whose editor was dropped without
|
||||
// view->removed(). Non-VST engines: no-op.
|
||||
virtual bool reload() { return true; }
|
||||
// VST3: split openGUI so instance teardown/reload runs on the owning
|
||||
// apartment thread while view->attached() may run on a temporary thread
|
||||
// with the channel worker pumping messages (see bridge main.cpp).
|
||||
|
||||
@@ -29,6 +29,7 @@ public:
|
||||
bool attachView(void* parentWindowHandle) override;
|
||||
bool hasAttachedView() const override;
|
||||
void closeGUI() override;
|
||||
bool reload() override;
|
||||
void setChannel(uint32_t ch) { channel_ = ch; }
|
||||
bool needsReload() const override { return hasAttachedOnce_ && !guiAttached_; }
|
||||
void setReloading(bool on) override { reloading_ = on; }
|
||||
@@ -45,7 +46,6 @@ private:
|
||||
bool hasAttachedOnce_;
|
||||
bool reloading_; // guarded by InstrumentEngineManager::mu_ (set via setReloading)
|
||||
uint32_t channel_ = 0;
|
||||
bool reload();
|
||||
};
|
||||
|
||||
#endif // VST3_INSTRUMENT_H
|
||||
|
||||
@@ -246,12 +246,7 @@ bool Vst3Instrument::loadPlugin(const std::string& path, double sampleRate) {
|
||||
|
||||
std::cerr << "[dbg] loadPlugin: createInstance ..." << std::endl;
|
||||
std::cerr << "[dbg] loadPlugin: chosen name=" << chosen.name() << " category=" << chosen.category()
|
||||
<< " subcat=" << chosen.subCategoriesString() << " idbytes=";
|
||||
{
|
||||
const unsigned char* cid2 = (const unsigned char*)chosen.ID().data();
|
||||
for (int b = 0; b < 16; ++b) std::cerr << std::hex << (int)cid2[b] << ' ';
|
||||
std::cerr << std::dec << std::endl;
|
||||
}
|
||||
<< " subcat=" << chosen.subCategoriesString() << std::endl;
|
||||
IPtr<IComponent> component = factory.createInstance<IComponent>(chosen.ID());
|
||||
if (!component) {
|
||||
std::cerr << "[Vst3Instrument] createInstance<IComponent> failed" << std::endl;
|
||||
@@ -579,16 +574,6 @@ bool Vst3Instrument::attachView(void* parentWindowHandle) {
|
||||
if (!s || !s->controller || !parentWindowHandle) return false;
|
||||
IPlugView* rawView = nullptr;
|
||||
tresult qi = s->controller->queryInterface(IPlugView::iid, (void**)&rawView);
|
||||
{
|
||||
const unsigned char* iidb = (const unsigned char*)&IPlugView::iid;
|
||||
std::cerr << "[dbg] openGUI: IPlugView::iid bytes=";
|
||||
for (int b = 0; b < 16; ++b) std::cerr << std::hex << (int)iidb[b] << ' ';
|
||||
std::cerr << std::dec << std::endl;
|
||||
const unsigned char* eidb = (const unsigned char*)&IEditController::iid;
|
||||
std::cerr << "[dbg] openGUI: IEditController::iid bytes=";
|
||||
for (int b = 0; b < 16; ++b) std::cerr << std::hex << (int)eidb[b] << ' ';
|
||||
std::cerr << std::dec << std::endl;
|
||||
}
|
||||
std::cerr << "[dbg] openGUI: controller qi IPlugView=" << (int)qi << " raw=" << (void*)rawView << " isSingle=" << (s->controllerIsComponent ? 1 : 0) << std::endl;
|
||||
FUnknownPtr<IPlugView> view(rawView);
|
||||
if (!view) {
|
||||
@@ -669,7 +654,18 @@ void Vst3Instrument::closeGUI() {
|
||||
return;
|
||||
#else
|
||||
auto* s = static_cast<Vst3HostState*>(state_);
|
||||
if (s && s->view && guiAttached_) s->view->removed();
|
||||
// Do NOT call s->view->removed(): Nexus's removed() enters an internal
|
||||
// modal wait for a message only this thread's message pump can dispatch
|
||||
// — but this thread is inside removed(), so it deadlocks forever
|
||||
// (observed: closeGUI job never returns; wdisp flood from the editor).
|
||||
// Nulling the view and letting the next terminate() (reload / destructor)
|
||||
// tear the editor windows down on this same thread is the deterministic
|
||||
// path out. Callers (post_close_gui / load job) additionally destroy the
|
||||
// editor's child windows right here on this thread so the plugin stops
|
||||
// pumping; the leaked editor object is inert once its windows are gone.
|
||||
if (s && s->view && guiAttached_)
|
||||
std::cerr << "[dbg] closeGUI: skip view->removed() (Nexus deadlock) view="
|
||||
<< (void*)s->view << std::endl;
|
||||
if (s) s->view = nullptr;
|
||||
guiAttached_ = false;
|
||||
#endif
|
||||
|
||||
+306
-38
@@ -19,6 +19,7 @@
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cctype>
|
||||
#include <condition_variable>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
@@ -62,6 +63,16 @@ static std::mutex g_guiMutex;
|
||||
static std::map<uint32_t, void*> g_guiWindows; // channel -> HWND (keep window alive)
|
||||
static std::map<HWND, uint32_t> g_hwndToCh; // HWND -> channel (WM_DESTROY cleanup)
|
||||
static std::map<uint32_t, std::unique_ptr<ChannelWorker>>* g_workers = nullptr;
|
||||
// Same-plugin-DLL reentrancy guards: two threads inside one VST3 DLL (Nexus)
|
||||
// crash or deadlock. g_attachPaths = lowercase plugin paths whose reload/
|
||||
// createView is running on some worker — a same-path close job must not unmute
|
||||
// its channel mid-attach (the attach job owns the silence state until
|
||||
// attachView finishes). g_closeInFlight = channels whose close job
|
||||
// (closeGUI + destroy children + reload) is still inside createInstance —
|
||||
// unmuting them then would race the plugin teardown.
|
||||
static std::mutex g_attachMutex;
|
||||
static std::vector<std::string> g_attachPaths;
|
||||
static bool g_closeInFlight[16] = { false };
|
||||
|
||||
static LRESULT CALLBACK VstWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
|
||||
if (uMsg == WM_CLOSE) {
|
||||
@@ -172,7 +183,12 @@ public:
|
||||
// dispatched here or the editor freezes after attach.
|
||||
lk.unlock();
|
||||
MSG msg;
|
||||
while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) {
|
||||
// Cap the idle pump: a busy editor (Nexus posts timer/paint
|
||||
// messages continuously) otherwise starves this worker's job
|
||||
// queue forever — post_close_gui never runs, Option B's 5s
|
||||
// close wait times out, two editors stay alive (deadlock).
|
||||
for (int pumped = 0; pumped < 16; ++pumped) {
|
||||
if (!PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) break;
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessageW(&msg);
|
||||
}
|
||||
@@ -207,6 +223,48 @@ private:
|
||||
bool stop_ = false;
|
||||
};
|
||||
|
||||
// --- same-plugin-DLL silence helpers ----------------------------------------
|
||||
// Two threads inside one VST3 DLL (Nexus) crash/deadlock: the audio loop's
|
||||
// process() on one instance must not race createInstance/createView/terminate
|
||||
// on another. Windows plugin paths are case-insensitive — compare lowercased.
|
||||
static std::string lower_plugin_path(uint32_t ch) {
|
||||
std::string p = g_engine ? g_engine->pathOf(ch) : std::string();
|
||||
std::transform(p.begin(), p.end(), p.begin(),
|
||||
[](unsigned char c) { return (char)::tolower(c); });
|
||||
return p;
|
||||
}
|
||||
|
||||
// Channels (other than ch) currently assigned the same plugin DLL path.
|
||||
static std::vector<uint32_t> same_plugin_channels(uint32_t ch) {
|
||||
std::vector<uint32_t> out;
|
||||
std::string p = lower_plugin_path(ch);
|
||||
if (p.empty()) return out;
|
||||
for (uint32_t y = 0; y < 16; ++y) {
|
||||
if (y == ch) continue;
|
||||
if (!g_engine->get(y)) continue;
|
||||
if (lower_plugin_path(y) == p) out.push_back(y);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static bool close_in_flight(uint32_t ch) {
|
||||
std::lock_guard<std::mutex> lk(g_attachMutex);
|
||||
return g_closeInFlight[ch];
|
||||
}
|
||||
|
||||
// Unmute y unless its own close job is still inside createInstance — that job
|
||||
// performs the unmute once its fresh instance is loaded (or, if an attach for
|
||||
// the same plugin is in flight, the attach job's restore does it).
|
||||
static void unmute_if_not_closing(uint32_t y, const char* why) {
|
||||
if (close_in_flight(y)) {
|
||||
std::cerr << "[dbg] " << why << ": ch=" << y
|
||||
<< " close job still in flight - close job unmutes" << std::endl;
|
||||
return;
|
||||
}
|
||||
g_engine->setReloading(y, false);
|
||||
std::cerr << "[dbg] " << why << ": restored ch=" << y << std::endl;
|
||||
}
|
||||
|
||||
// closeGUI() MUST run on the channel worker thread (its COM STA apartment) —
|
||||
// the view was attached there. Calling view->removed() from the main thread
|
||||
// (WM_DESTROY handler) is a cross-apartment COM call that corrupts the plugin;
|
||||
@@ -231,9 +289,92 @@ static void post_close_gui(uint32_t ch, HWND hwnd) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_guiMutex);
|
||||
auto it = g_guiWindows.find(ch);
|
||||
if (it == g_guiWindows.end() || it->second != hwnd) return;
|
||||
if (it == g_guiWindows.end() || it->second != hwnd) {
|
||||
std::cerr << "[dbg] closeGUI ch=" << ch << " skipped (window replaced)" << std::endl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
std::cerr << "[dbg] closeGUI ch=" << ch << " start" << std::endl;
|
||||
// Silence first: the audio loop must not process() the instance
|
||||
// while we tear its editor down on this thread.
|
||||
g_engine->setReloading(ch, true);
|
||||
// Close-in-flight: until the fresh instance is loaded, no other
|
||||
// job may unmute this channel (its plugin is being torn down).
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_attachMutex);
|
||||
g_closeInFlight[ch] = true;
|
||||
}
|
||||
if (auto* i = g_engine->get(ch)) {
|
||||
// closeGUI() nulls the view WITHOUT view->removed() (Nexus
|
||||
// removed() deadlocks this worker - modal wait for a message
|
||||
// only its own pump can dispatch, but it is inside removed()).
|
||||
i->closeGUI();
|
||||
// CRASH FIX: the editor was dropped WITHOUT view->removed() —
|
||||
// the instance's editor state is dangling, so the audio loop
|
||||
// must NOT process() it. Rebuild a fresh instance NOW on this
|
||||
// worker (its COM STA apartment is alive); reload() ==
|
||||
// terminate + loadPlugin (createInstance). Same-plugin
|
||||
// channels are silenced so no second thread enters this DLL
|
||||
// during createInstance (2 threads in one DLL -> Nexus exits
|
||||
// silently, observed in probes). Serialized with other loads
|
||||
// by g_loadMutex.
|
||||
std::vector<uint32_t> sp = same_plugin_channels(ch);
|
||||
for (uint32_t y : sp) {
|
||||
g_engine->setReloading(y, true);
|
||||
std::cerr << "[dbg] closeGUI: silenced same-plugin ch=" << y
|
||||
<< " during reload ch=" << ch << std::endl;
|
||||
}
|
||||
bool reloadOk = false;
|
||||
{
|
||||
// destroy children + reload in ONE lock: destroying the
|
||||
// editor windows runs the plugin's window proc (DLL
|
||||
// entry) on this worker; it must not race another
|
||||
// thread's createInstance/createView of the same DLL.
|
||||
// Same-thread destroy is valid (attachView created them
|
||||
// on this worker); without destroy the editor keeps
|
||||
// pumping (0x47b flood) while the audio loop processes
|
||||
// the instance (2 threads in one DLL) -> Nexus USER32 crash.
|
||||
std::lock_guard<std::mutex> lg(g_loadMutex);
|
||||
#ifdef _WIN32
|
||||
if (hwnd && IsWindow(hwnd)) {
|
||||
while (HWND c = FindWindowExA(hwnd, nullptr, nullptr, nullptr))
|
||||
DestroyWindow(c);
|
||||
}
|
||||
#endif
|
||||
reloadOk = i->reload();
|
||||
}
|
||||
std::cerr << "[dbg] closeGUI ch=" << ch << " reload="
|
||||
<< (reloadOk ? 1 : 0) << std::endl;
|
||||
// createInstance is done: unmuting is safe again. But if an
|
||||
// attach for the SAME plugin path is still running (its
|
||||
// createView must not race process() on any same-path
|
||||
// channel), leave the silence in place — the attach job's
|
||||
// restore unmutes everything once attachView finished.
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_attachMutex);
|
||||
g_closeInFlight[ch] = false;
|
||||
}
|
||||
bool pathBusy = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_attachMutex);
|
||||
std::string p = lower_plugin_path(ch);
|
||||
pathBusy = !p.empty() &&
|
||||
std::find(g_attachPaths.begin(), g_attachPaths.end(), p) != g_attachPaths.end();
|
||||
}
|
||||
if (pathBusy) {
|
||||
std::cerr << "[dbg] closeGUI ch=" << ch
|
||||
<< ": attach in flight for same plugin - leaving silenced (attach restore unmutes)"
|
||||
<< std::endl;
|
||||
} else {
|
||||
for (uint32_t y : sp) unmute_if_not_closing(y, "closeGUI");
|
||||
g_engine->setReloading(ch, false);
|
||||
}
|
||||
} else {
|
||||
// Instrument already gone (unloaded) — nothing to rebuild.
|
||||
g_engine->setReloading(ch, false);
|
||||
std::lock_guard<std::mutex> lk(g_attachMutex);
|
||||
g_closeInFlight[ch] = false;
|
||||
}
|
||||
if (auto* i = g_engine->get(ch)) i->closeGUI();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -455,28 +596,35 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
}
|
||||
#ifdef _WIN32
|
||||
// STALL FIX (spawn.log "bridge stalled 3s restart"): audio loop
|
||||
// processes ANOTHER live instance of the SAME plugin DLL (Nexus)
|
||||
// while reload()+createView runs on THIS worker thread -> two
|
||||
// threads inside one DLL -> deadlock, writeIndex stops, Tauri
|
||||
// restarts the bridge. Silence same-path channels for the
|
||||
// reload/attach duration, then restore (audio returns).
|
||||
std::vector<uint32_t> samePathSilenced;
|
||||
// Option B: chi 1 editor VST mo tai 1 thoi diem toan
|
||||
// bridge. Instance thu 2 cua CUNG plugin (Nexus) attach
|
||||
// view o apartment/worker khac -> treo. Dong editor cua
|
||||
// channel khac TRUOC khi attach: WM_CLOSE -> main pump
|
||||
// (audio loop) destroy window -> WM_DESTROY -> closeGUI()
|
||||
// + xoa registry. Chay tren worker job de khong stall
|
||||
// writeIndex cua real-time loop.
|
||||
// destroy window -> WM_DESTROY -> closeGUI() + xoa registry.
|
||||
// Chay tren worker job de khong stall writeIndex cua real-time
|
||||
// loop. CRASH FIX: silence moi channel TRUOC khi dong editor
|
||||
// cua no (view->removed() tren worker cung luc process() tren
|
||||
// audio loop = 2 thread trong 1 plugin -> crash USER32).
|
||||
{
|
||||
std::vector<uint32_t> others;
|
||||
bool abortAttach = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_guiMutex);
|
||||
for (const auto& kv : g_guiWindows)
|
||||
if (kv.first != guiCh) others.push_back(kv.first);
|
||||
}
|
||||
for (uint32_t y : others) {
|
||||
// Skip channels whose editor already detached (close job
|
||||
// finished): do not WM_CLOSE a hidden reused window, do
|
||||
// not re-silence a channel that is already unmuted.
|
||||
if (auto* yi0 = instruments.get(y)) {
|
||||
if (!yi0->hasAttachedView()) {
|
||||
std::cerr << "[dbg] openGUI: ch=" << y
|
||||
<< " editor already detached - skip"
|
||||
<< std::endl;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
HWND yHwnd = nullptr;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_guiMutex);
|
||||
@@ -484,6 +632,12 @@ int main(int argc, char* argv[]) {
|
||||
if (it != g_guiWindows.end()) yHwnd = (HWND)it->second;
|
||||
}
|
||||
if (!yHwnd || !IsWindow(yHwnd)) continue;
|
||||
if (instruments.get(y)) {
|
||||
instruments.setReloading(y, true);
|
||||
std::cerr << "[dbg] openGUI: silenced ch=" << y
|
||||
<< " while closing its editor (before attach ch="
|
||||
<< guiCh << ")" << std::endl;
|
||||
}
|
||||
PostMessage(yHwnd, WM_CLOSE, 0, 0);
|
||||
std::cerr << "[dbg] openGUI: closing editor ch=" << y
|
||||
<< " before attach ch=" << guiCh << std::endl;
|
||||
@@ -491,27 +645,42 @@ int main(int argc, char* argv[]) {
|
||||
// tren worker cua channel y. Doi cho view da detach (window
|
||||
// duoc giu lai de reuse, khong doi registry erase nhu cu).
|
||||
bool closed = false;
|
||||
for (int i = 0; i < 500; ++i) {
|
||||
// The close job = closeGUI + destroy children + fresh
|
||||
// reload (createInstance, ~2s for Nexus). hasAttachedView
|
||||
// flips false after closeGUI only - ALSO wait for the
|
||||
// close job to finish entirely (close_in_flight): the
|
||||
// attach must not run createView while another thread is
|
||||
// still inside this plugin DLL (2 threads in one DLL ->
|
||||
// Nexus exits silently, observed in probes).
|
||||
for (int i = 0; i < 500; ++i) { // 5s budget
|
||||
auto* yi = instruments.get(y);
|
||||
if (!yi || !yi->hasAttachedView()) { closed = true; break; }
|
||||
bool detached = !yi || !yi->hasAttachedView();
|
||||
if (detached && !close_in_flight(y)) { closed = true; break; }
|
||||
Sleep(10);
|
||||
}
|
||||
if (!closed)
|
||||
if (closed) {
|
||||
// Editor detached and its close job finished; the
|
||||
// close job unmutes the channel itself. Do NOT reset
|
||||
// reloading here - the close job owns the silence state.
|
||||
std::cerr << "[dbg] openGUI: restored ch=" << y
|
||||
<< " after editor close" << std::endl;
|
||||
} else {
|
||||
std::cerr << "[dbg] openGUI: editor ch=" << y
|
||||
<< " not closed in 5s, proceeding" << std::endl;
|
||||
// Same plugin DLL as guiCh? Silence its processAudioBlock
|
||||
// until attach finishes (see STALL FIX above).
|
||||
std::string yp = instruments.pathOf(y);
|
||||
std::string gp = instruments.pathOf(guiCh);
|
||||
std::transform(yp.begin(), yp.end(), yp.begin(), ::tolower);
|
||||
std::transform(gp.begin(), gp.end(), gp.begin(), ::tolower);
|
||||
if (!gp.empty() && gp == yp && instruments.get(y)) {
|
||||
instruments.setReloading(y, true);
|
||||
samePathSilenced.push_back(y);
|
||||
std::cerr << "[dbg] openGUI: silenced same-plugin ch=" << y
|
||||
<< " during attach ch=" << guiCh << std::endl;
|
||||
<< " close job not finished in 5s, aborting attach ch=" << guiCh
|
||||
<< std::endl;
|
||||
abortAttach = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (abortAttach) {
|
||||
// A close job is stuck; do NOT createView (would race the
|
||||
// plugin DLL). Unmute what we silenced (skips channels
|
||||
// whose close job is still in flight - that job unmutes
|
||||
// them when it finishes).
|
||||
for (uint32_t y : others) unmute_if_not_closing(y, "openGUI-abort");
|
||||
std::cerr << "[dbg] openGUI: attach aborted ch=" << guiCh << std::endl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)0;
|
||||
@@ -524,25 +693,76 @@ int main(int argc, char* argv[]) {
|
||||
// loop) dispatch messages cua no - khong can pump worker.
|
||||
// Chan UAF bang flag reloading_ (set/clear duoi engine
|
||||
// mutex; renderAll giu mutex khi process).
|
||||
bool guard = inst->needsReload();
|
||||
if (guard) instruments.setReloading(guiCh, true);
|
||||
// CRASH FIX (0xc000041d USER32 while playing): keep THIS
|
||||
// channel silenced for the whole time its editor is attached.
|
||||
// The worker pumps the plugin's editor windows -> the plugin's
|
||||
// window proc runs on this worker thread while the audio loop
|
||||
// calls processor->process() on the SAME instance (two threads
|
||||
// inside one DLL) -> Nexus crashes ~1-2s after attach during
|
||||
// playback. Muting the channel while the editor is open is the
|
||||
// deterministic trade-off (track silent while GUI visible);
|
||||
// restored when the editor closes (post_close_gui / LOAD).
|
||||
// STALL/CRASH FIX (Fix C, restored): silence EVERY channel
|
||||
// assigned the SAME plugin DLL path for the reload+attach
|
||||
// duration — the audio loop must not process() another
|
||||
// instance of this DLL while createInstance/createView runs
|
||||
// on this worker (2 threads in one DLL -> Nexus exits
|
||||
// silently, observed in probes). Iterate ALL 16 channels, not
|
||||
// just window-owning ones: session-restore instances without
|
||||
// a window are live and renderable.
|
||||
std::vector<uint32_t> samePathSilenced;
|
||||
std::string gp = lower_plugin_path(guiCh);
|
||||
if (!gp.empty()) {
|
||||
for (uint32_t y = 0; y < 16; ++y) {
|
||||
if (y == guiCh) continue;
|
||||
if (!instruments.get(y)) continue;
|
||||
if (lower_plugin_path(y) == gp) {
|
||||
instruments.setReloading(y, true);
|
||||
samePathSilenced.push_back(y);
|
||||
std::cerr << "[dbg] openGUI: silenced same-plugin ch=" << y
|
||||
<< " during attach ch=" << guiCh << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
instruments.setReloading(guiCh, true);
|
||||
// Register attach-in-flight: a same-path close job must not
|
||||
// unmute its channel while createView runs here (it stays
|
||||
// silenced; this job's restore unmutes it at the end).
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_attachMutex);
|
||||
if (!gp.empty()) g_attachPaths.push_back(gp);
|
||||
}
|
||||
bool ok = false;
|
||||
{
|
||||
// Serialize createInstance (reload) with other loads.
|
||||
// Serialize ALL plugin-DLL entry (createInstance AND
|
||||
// createView) with other loads: two threads inside one
|
||||
// DLL (Nexus) crash. createView outside the lock raced a
|
||||
// same-path close job's reload (createInstance) -> Nexus
|
||||
// exited silently (observed in probes).
|
||||
std::lock_guard<std::mutex> lg(g_loadMutex);
|
||||
ok = inst->reloadForGUI();
|
||||
if (ok) ok = inst->attachView(hwnd);
|
||||
}
|
||||
if (ok) ok = inst->attachView(hwnd);
|
||||
if (guard) instruments.setReloading(guiCh, false);
|
||||
for (uint32_t y : samePathSilenced) {
|
||||
instruments.setReloading(y, false);
|
||||
std::cerr << "[dbg] openGUI: restored ch=" << y
|
||||
<< " after attach ch=" << guiCh << std::endl;
|
||||
// Attach done (ok or failed) — close jobs may unmute again.
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_attachMutex);
|
||||
if (!gp.empty()) {
|
||||
auto it = std::find(g_attachPaths.begin(), g_attachPaths.end(), gp);
|
||||
if (it != g_attachPaths.end()) g_attachPaths.erase(it);
|
||||
}
|
||||
}
|
||||
// Restore same-path silence. Channels whose own close job is
|
||||
// still inside createInstance stay muted — that job owns
|
||||
// their unmute and performs it once reload finished.
|
||||
for (uint32_t y : samePathSilenced)
|
||||
unmute_if_not_closing(y, "openGUI");
|
||||
if (ok)
|
||||
std::cout << "[NativeBridge] GUI attached hwnd=" << hwnd
|
||||
<< " plugin=" << arg2 << " ch=" << guiCh << std::endl;
|
||||
<< " plugin=" << arg2 << " ch=" << guiCh
|
||||
<< " (channel muted while editor open)" << std::endl;
|
||||
else {
|
||||
// Attach failed -> no editor running -> safe to process again.
|
||||
instruments.setReloading(guiCh, false);
|
||||
std::cerr << "[NativeBridge] GUI attach FAILED hwnd=" << hwnd
|
||||
<< " plugin=" << arg2 << std::endl;
|
||||
#ifdef _WIN32
|
||||
@@ -566,9 +786,11 @@ int main(int argc, char* argv[]) {
|
||||
// with gui_probe: worker-thread openGUI + concurrent pump → attached
|
||||
// returns kResultOk; without it → hangs forever).
|
||||
MSG msg;
|
||||
int pumpedMain = 0;
|
||||
while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessageW(&msg);
|
||||
++pumpedMain;
|
||||
}
|
||||
#endif
|
||||
// A. Control events — non-rt safe, drained first
|
||||
@@ -591,6 +813,22 @@ int main(int argc, char* argv[]) {
|
||||
uint32_t ch = c.channel & 0xF;
|
||||
if (!workers[ch]) workers[ch] = std::make_unique<ChannelWorker>();
|
||||
workers[ch]->post([&instruments, t, ch, path, sampleRate, block]() {
|
||||
// CRASH FIX: this job enters the plugin DLL (createInstance
|
||||
// in loadPlugin, old-instance terminate on replace) while
|
||||
// the audio loop may process() another instance of the SAME
|
||||
// DLL — 2 threads in one DLL crashes Nexus (observed with
|
||||
// session-restore instances). Silence this channel (its old
|
||||
// instance is being torn down / replaced) and every
|
||||
// same-plugin channel for the whole job. Old path covers
|
||||
// the old-instance teardown; the new path (after assign)
|
||||
// covers the freshly created instance.
|
||||
instruments.setReloading(ch, true);
|
||||
std::vector<uint32_t> spOld = same_plugin_channels(ch);
|
||||
for (uint32_t y : spOld) {
|
||||
g_engine->setReloading(y, true);
|
||||
std::cerr << "[dbg] load: silenced same-plugin ch=" << y
|
||||
<< " during load ch=" << ch << std::endl;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
// DONG cua so editor dang mo cua channel TRUOC khi assign():
|
||||
// thay the inst (VST3 -> SF2/inst khac) ma editor con song ->
|
||||
@@ -614,7 +852,18 @@ int main(int argc, char* argv[]) {
|
||||
// -> DestroyWindow cross-thread -> USER32 0xc000041d. An
|
||||
// window, giu trong registry de reuse (reopen ShowWindow lai).
|
||||
if (hToHide && IsWindow(hToHide)) {
|
||||
// CRASH FIX (0xc000041d USER32 while playing): silence
|
||||
// the channel while the old editor detaches — view->removed()
|
||||
// on this worker would race processor->process() on the
|
||||
// audio loop (same plugin instance).
|
||||
instruments.setReloading(ch, true);
|
||||
if (auto* i = instruments.get(ch)) i->closeGUI();
|
||||
// Destroy the old editor's child windows (worker-owned,
|
||||
// created by attachView here) so they stop pumping;
|
||||
// the parent window is hidden and kept for reuse.
|
||||
// See post_close_gui for the same pattern.
|
||||
while (HWND c = FindWindowExA(hToHide, nullptr, nullptr, nullptr))
|
||||
DestroyWindow(c);
|
||||
ShowWindow(hToHide, SW_HIDE);
|
||||
}
|
||||
#endif
|
||||
@@ -630,6 +879,25 @@ int main(int argc, char* argv[]) {
|
||||
std::cerr << "[NativeBridge] instrument load FAILED ch=" << ch
|
||||
<< " type=" << (int)t << " " << path << std::endl;
|
||||
}
|
||||
// New-path channels (the freshly created instance's DLL) —
|
||||
// silence any not already covered by the old path.
|
||||
std::vector<uint32_t> spNew;
|
||||
if (ok) spNew = same_plugin_channels(ch);
|
||||
for (uint32_t y : spNew) {
|
||||
if (std::find(spOld.begin(), spOld.end(), y) == spOld.end()) {
|
||||
g_engine->setReloading(y, true);
|
||||
std::cerr << "[dbg] load: silenced same-plugin ch=" << y
|
||||
<< " (new path) during load ch=" << ch << std::endl;
|
||||
}
|
||||
}
|
||||
// Restore. Failed load keeps the OLD instance — unmute it
|
||||
// back (silenced at job start); a successful assign leaves
|
||||
// the fresh instance unmuted by construction.
|
||||
if (!ok) g_engine->setReloading(ch, false);
|
||||
for (uint32_t y : spOld) unmute_if_not_closing(y, "load");
|
||||
for (uint32_t y : spNew)
|
||||
if (std::find(spOld.begin(), spOld.end(), y) == spOld.end())
|
||||
unmute_if_not_closing(y, "load");
|
||||
});
|
||||
} else if (c.type == 1) { // PANIC
|
||||
instruments.allNotesOff();
|
||||
|
||||
Reference in New Issue
Block a user