fix: bridge crash/stall when opening native VST GUI while playing
- Fix A (USER32 crash): WM_CLOSE no longer destroys the native VST
window from the main thread (plugin editor children live on the
channel worker thread). WM_CLOSE -> hide + closeGUI on worker;
WM_DESTROY erases registry. LOAD_INSTRUMENT close uses closeGUI +
SW_HIDE, waits on hasAttachedView() instead of registry erase.
- Fix B: per-process SHM name SonicForge_DAW_IPC_{pid} so stale
bridges from old builds can never attach to a new app's ring buffer.
- Fix C (stall): opening editor for a plugin whose other channel still
has a live instance of the same DLL (Nexus) deadlocked the audio
loop (reload+createView on worker while audio thread processed the
other instance). Silence same-path channels via setReloading during
reload/attach, restore afterwards; audio resumes.
- Rebuild bridge + update install/ portable package.
This commit is contained in:
@@ -72,6 +72,16 @@ public:
|
||||
|
||||
INativeInstrument* get(uint32_t channel);
|
||||
|
||||
// Remove and destroy the instrument on `channel` (its destructor may call
|
||||
// VST terminate — MUST run on the channel worker thread, caller's duty).
|
||||
// Used by Option B: two live instances of the same plugin DLL (Nexus)
|
||||
// hang createView on the second — unload the other channel's copy.
|
||||
void unload(uint32_t channel);
|
||||
|
||||
// Path last assigned to a channel (empty if none). Windows paths are
|
||||
// case-insensitive — caller must lowercase before comparing.
|
||||
std::string pathOf(uint32_t channel);
|
||||
|
||||
// Mark a channel as rebuilding its VST instance (GUI reopen): the
|
||||
// real-time loop then skips processAudioBlock for that channel so VST
|
||||
// teardown never races the audio thread.
|
||||
@@ -90,6 +100,7 @@ private:
|
||||
|
||||
mutable std::mutex mu_;
|
||||
std::map<uint32_t, std::unique_ptr<INativeInstrument>> channels_;
|
||||
std::map<uint32_t, std::string> paths_; // last assigned path per channel
|
||||
// Per-instrument scratch so engines that overwrite (not mix) stay additive.
|
||||
std::vector<float> scratchL_, scratchR_;
|
||||
};
|
||||
|
||||
@@ -186,11 +186,33 @@ bool InstrumentEngineManager::assign(uint32_t channel, InstrumentType type,
|
||||
auto it = channels_.find(channel);
|
||||
if (it != channels_.end()) oldInst = std::move(it->second);
|
||||
channels_[channel] = std::move(inst);
|
||||
paths_[channel] = path;
|
||||
}
|
||||
oldInst.reset();
|
||||
return true;
|
||||
}
|
||||
|
||||
void InstrumentEngineManager::unload(uint32_t channel) {
|
||||
if (channel >= 16) return;
|
||||
// Destructor runs on the CALLER thread (VST3 terminate must run on the
|
||||
// channel worker). The map write is under mu_ so renderAll never stalls.
|
||||
std::unique_ptr<INativeInstrument> oldInst;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
auto it = channels_.find(channel);
|
||||
if (it != channels_.end()) oldInst = std::move(it->second);
|
||||
channels_.erase(channel);
|
||||
paths_.erase(channel);
|
||||
}
|
||||
oldInst.reset();
|
||||
}
|
||||
|
||||
std::string InstrumentEngineManager::pathOf(uint32_t channel) {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
auto it = paths_.find(channel);
|
||||
return it == paths_.end() ? std::string() : it->second;
|
||||
}
|
||||
|
||||
INativeInstrument* InstrumentEngineManager::get(uint32_t channel) {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
auto it = channels_.find(channel);
|
||||
|
||||
+80
-24
@@ -64,13 +64,40 @@ static std::map<HWND, uint32_t> g_hwndToCh; // HWND -> channel (WM_DESTROY c
|
||||
static std::map<uint32_t, std::unique_ptr<ChannelWorker>>* g_workers = nullptr;
|
||||
|
||||
static LRESULT CALLBACK VstWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
|
||||
if (uMsg == WM_DESTROY) {
|
||||
if (uMsg == WM_CLOSE) {
|
||||
uint32_t ch = UINT32_MAX;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_guiMutex);
|
||||
auto it = g_hwndToCh.find(hwnd);
|
||||
if (it != g_hwndToCh.end()) ch = it->second;
|
||||
}
|
||||
if (ch != UINT32_MAX) {
|
||||
// CRASH FIX (0xc000041d STATUS_FATAL_USER_CALLBACK_EXCEPTION /
|
||||
// 0xc0000005 trong USER32, Event Log 9:47/10:20/10:22):
|
||||
// KHONG de DefWindowProc DestroyWindow o day. Plugin editor la
|
||||
// child cua window nay, tao TREN worker thread (view->attached()
|
||||
// chay trong attach job) - DestroyWindow tren main thread pha huy
|
||||
// child cross-thread -> crash USER32. Detach view tren worker
|
||||
// (closeGUI), an window, GIU window trong registry de reuse.
|
||||
post_close_gui(ch, hwnd);
|
||||
ShowWindow(hwnd, SW_HIDE);
|
||||
return 0;
|
||||
}
|
||||
// Window khong thuoc registry (chua dang ky) - de DefWindowProc huy.
|
||||
} else if (uMsg == WM_DESTROY) {
|
||||
// Chi xay ra khi window thuc su bi huy (khong con path chu dong nao
|
||||
// DestroyWindow khi view dang attached). Xoa registry + detach view.
|
||||
uint32_t ch = UINT32_MAX;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_guiMutex);
|
||||
auto it = g_hwndToCh.find(hwnd);
|
||||
if (it != g_hwndToCh.end()) ch = it->second;
|
||||
g_hwndToCh.erase(hwnd);
|
||||
for (auto it = g_guiWindows.begin(); it != g_guiWindows.end();) {
|
||||
if (it->second == hwnd) it = g_guiWindows.erase(it);
|
||||
else ++it;
|
||||
}
|
||||
}
|
||||
if (ch != UINT32_MAX) post_close_gui(ch, hwnd);
|
||||
}
|
||||
return DefWindowProcA(hwnd, uMsg, wParam, lParam);
|
||||
@@ -193,21 +220,21 @@ static void post_close_gui(uint32_t ch, HWND hwnd) {
|
||||
if (wit != g_workers->end()) w = wit->second.get();
|
||||
}
|
||||
if (w) {
|
||||
// closeGUI() (view->removed()) PHAI chay tren channel worker - COM STA
|
||||
// apartment cua plugin song o do. Window KHONG bi destroy: VstWindowProc
|
||||
// giu lai (an) de reuse nen plugin editor children (worker-owned) khong
|
||||
// bao gio bi huy cross-thread. Registry (g_guiWindows/g_hwndToCh) chi
|
||||
// xoa trong WM_DESTROY khi window thuc su bi huy.
|
||||
w->post([ch, hwnd]() {
|
||||
// Guard window-replaced race: neu OPEN_GUI moi da dung window khac
|
||||
// (hoac da xoa), khong detach view cua window moi.
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_guiMutex);
|
||||
auto it = g_guiWindows.find(ch);
|
||||
if (it == g_guiWindows.end() || it->second != hwnd) return;
|
||||
}
|
||||
if (auto* i = g_engine->get(ch)) i->closeGUI();
|
||||
std::lock_guard<std::mutex> lock(g_guiMutex);
|
||||
g_hwndToCh.erase(hwnd);
|
||||
// Chi erase neu map van tro den HWND NAY — neu reopen da tao
|
||||
// window moi (race: close cu + OPEN_GUI moi), khong duoc xoa
|
||||
// entry cua window moi (con tro treo -> leak window moi).
|
||||
auto wit = g_guiWindows.find(ch);
|
||||
if (wit != g_guiWindows.end() && wit->second == hwnd) g_guiWindows.erase(wit);
|
||||
});
|
||||
} else {
|
||||
std::lock_guard<std::mutex> lock(g_guiMutex);
|
||||
g_hwndToCh.erase(hwnd);
|
||||
auto wit = g_guiWindows.find(ch);
|
||||
if (wit != g_guiWindows.end() && wit->second == hwnd) g_guiWindows.erase(wit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -428,6 +455,13 @@ 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
|
||||
@@ -453,17 +487,30 @@ int main(int argc, char* argv[]) {
|
||||
PostMessage(yHwnd, WM_CLOSE, 0, 0);
|
||||
std::cerr << "[dbg] openGUI: closing editor ch=" << y
|
||||
<< " before attach ch=" << guiCh << std::endl;
|
||||
// WM_CLOSE -> VstWindowProc -> post_close_gui -> closeGUI()
|
||||
// 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) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_guiMutex);
|
||||
if (g_guiWindows.find(y) == g_guiWindows.end()) { closed = true; break; }
|
||||
}
|
||||
auto* yi = instruments.get(y);
|
||||
if (!yi || !yi->hasAttachedView()) { closed = true; break; }
|
||||
Sleep(10);
|
||||
}
|
||||
if (!closed)
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
@@ -487,6 +534,11 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
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;
|
||||
}
|
||||
if (ok)
|
||||
std::cout << "[NativeBridge] GUI attached hwnd=" << hwnd
|
||||
<< " plugin=" << arg2 << " ch=" << guiCh << std::endl;
|
||||
@@ -550,17 +602,21 @@ int main(int argc, char* argv[]) {
|
||||
// 0xc000041d. Post WM_CLOSE -> main pump destroy window tren
|
||||
// DUNG thread so huu no. Registry da erase o tren nen WM_DESTROY
|
||||
// khong goi closeGUI tren inst cu (inst moi chua co view).
|
||||
HWND hToDestroy = nullptr;
|
||||
HWND hToHide = nullptr;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_guiMutex);
|
||||
auto git = g_guiWindows.find(ch);
|
||||
if (git != g_guiWindows.end()) {
|
||||
hToDestroy = (HWND)git->second;
|
||||
g_guiWindows.erase(git);
|
||||
g_hwndToCh.erase(hToDestroy);
|
||||
}
|
||||
if (git != g_guiWindows.end()) hToHide = (HWND)git->second;
|
||||
}
|
||||
// CRASH FIX: detach view TRUOC khi thay inst. Plugin editor
|
||||
// children thuoc worker thread nay (attachView chay o day);
|
||||
// khong destroy parent window (main-owned) khi view con song
|
||||
// -> DestroyWindow cross-thread -> USER32 0xc000041d. An
|
||||
// window, giu trong registry de reuse (reopen ShowWindow lai).
|
||||
if (hToHide && IsWindow(hToHide)) {
|
||||
if (auto* i = instruments.get(ch)) i->closeGUI();
|
||||
ShowWindow(hToHide, SW_HIDE);
|
||||
}
|
||||
if (hToDestroy && IsWindow(hToDestroy)) PostMessageA(hToDestroy, WM_CLOSE, 0, 0);
|
||||
#endif
|
||||
std::cerr << "[dbg] load thread start ch=" << ch
|
||||
<< " type=" << (int)t << " path=" << path << std::endl;
|
||||
|
||||
Reference in New Issue
Block a user