G1.2: 1 UiThread chung thay 16 ChannelWorker (COM STA + message pump + job queue); window+attach job tren UiThread; audio loop bo PeekMessageW; Option B close dong bo qua close_editor_now
This commit is contained in:
Binary file not shown.
+91
-150
@@ -123,7 +123,7 @@ static InstrumentEngineManager* g_engine = nullptr;
|
|||||||
static std::mutex g_guiMutex;
|
static std::mutex g_guiMutex;
|
||||||
static std::map<uint32_t, void*> g_guiWindows; // channel -> HWND (keep window alive)
|
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<HWND, uint32_t> g_hwndToCh; // HWND -> channel (WM_DESTROY cleanup)
|
||||||
static std::map<uint32_t, std::unique_ptr<ChannelWorker>>* g_workers = nullptr;
|
static ChannelWorker* g_uiWorker = nullptr;
|
||||||
static std::mutex g_tidMutex;
|
static std::mutex g_tidMutex;
|
||||||
static std::map<DWORD, ChannelWorker*> g_tidToWorker; // worker tid -> worker (owner-thread destroy)
|
static std::map<DWORD, ChannelWorker*> g_tidToWorker; // worker tid -> worker (owner-thread destroy)
|
||||||
// Same-plugin-DLL reentrancy guards: two threads inside one VST3 DLL (Nexus)
|
// Same-plugin-DLL reentrancy guards: two threads inside one VST3 DLL (Nexus)
|
||||||
@@ -256,8 +256,8 @@ static void* create_native_vst_window(const char* title) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Per-channel persistent worker: ONE thread owns the COM STA apartment for that
|
// G1.2: single persistent UiThread — ONE thread owns the COM STA apartment for
|
||||||
// channel's instrument for its whole lifetime. loadPlugin and openGUI MUST run
|
// ALL instruments for the whole bridge lifetime. loadPlugin and openGUI MUST run
|
||||||
// on the same thread — if the loading thread exits, its apartment dies and
|
// on the same thread — if the loading thread exits, its apartment dies and
|
||||||
// VST3 plugins that marshal internally (Nexus) hang forever in
|
// VST3 plugins that marshal internally (Nexus) hang forever in
|
||||||
// view->attached(). Verified with gui_probe: `bridge_like` (load thread exits,
|
// view->attached(). Verified with gui_probe: `bridge_like` (load thread exits,
|
||||||
@@ -451,15 +451,12 @@ private:
|
|||||||
// resizeView() must run on the channel worker (the view was attached in its
|
// resizeView() must run on the channel worker (the view was attached in its
|
||||||
// COM STA apartment), never on the main pump thread.
|
// COM STA apartment), never on the main pump thread.
|
||||||
static void post_resize_view(uint32_t ch, int w, int h) {
|
static void post_resize_view(uint32_t ch, int w, int h) {
|
||||||
if (g_workers) {
|
if (g_uiWorker) {
|
||||||
auto wit = g_workers->find(ch);
|
g_uiWorker->post([ch, w, h]() {
|
||||||
if (wit != g_workers->end()) {
|
if (!g_engine) return;
|
||||||
wit->second->post([ch, w, h]() {
|
auto* i = g_engine->get(ch);
|
||||||
if (!g_engine) return;
|
if (i) i->resizeView(w, h);
|
||||||
auto* i = g_engine->get(ch);
|
});
|
||||||
if (i) i->resizeView(w, h);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -620,27 +617,15 @@ static void clear_editor_open(uint32_t ch) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// closeGUI() MUST run on the channel worker thread (its COM STA apartment) —
|
// closeGUI() MUST run on the UiThread (its COM STA apartment) — the view was
|
||||||
// the view was attached there. Calling view->removed() from the main thread
|
// attached there. Calling view->removed() from another thread is a
|
||||||
// (WM_DESTROY handler) is a cross-apartment COM call that corrupts the plugin;
|
// cross-apartment COM call that corrupts the plugin; Nexus then hangs on the
|
||||||
// Nexus then hangs on the NEXT view->attached(). Erase the registry inside the
|
// NEXT view->attached(). G1.2: 1 UiThread chung — close chay synchronous tren
|
||||||
// job so Option B (closing another editor before attach) waits for closeGUI to
|
// job; goi truc tiep (Option B trong attach job) hoac qua post_close_gui
|
||||||
// actually finish.
|
// (WM_CLOSE/WM_DESTROY).
|
||||||
static void post_close_gui(uint32_t ch, HWND hwnd) {
|
static void close_editor_now(uint32_t ch, HWND hwnd) {
|
||||||
ChannelWorker* w = nullptr;
|
// Guard window-replaced race: neu OPEN_GUI moi da dung window khac
|
||||||
if (g_workers) {
|
// (hoac da xoa), khong detach view cua window moi.
|
||||||
auto wit = g_workers->find(ch);
|
|
||||||
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);
|
std::lock_guard<std::mutex> lock(g_guiMutex);
|
||||||
auto it = g_guiWindows.find(ch);
|
auto it = g_guiWindows.find(ch);
|
||||||
@@ -748,7 +733,11 @@ static void post_close_gui(uint32_t ch, HWND hwnd) {
|
|||||||
std::lock_guard<std::mutex> lk(g_attachMutex);
|
std::lock_guard<std::mutex> lk(g_attachMutex);
|
||||||
g_closeInFlight[ch] = false;
|
g_closeInFlight[ch] = false;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
static void post_close_gui(uint32_t ch, HWND hwnd) {
|
||||||
|
if (g_uiWorker) {
|
||||||
|
g_uiWorker->post([ch, hwnd]() { close_editor_now(ch, hwnd); });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -855,8 +844,11 @@ int main(int argc, char* argv[]) {
|
|||||||
// Per-channel persistent workers: loadPlugin + openGUI run on the SAME
|
// Per-channel persistent workers: loadPlugin + openGUI run on the SAME
|
||||||
// thread whose COM STA apartment stays alive for the channel's lifetime
|
// thread whose COM STA apartment stays alive for the channel's lifetime
|
||||||
// (see ChannelWorker comment — a dead apartment hangs Nexus attached()).
|
// (see ChannelWorker comment — a dead apartment hangs Nexus attached()).
|
||||||
std::map<uint32_t, std::unique_ptr<ChannelWorker>> workers;
|
// G1.2: MOT UiThread chung (COM STA + message pump + job queue) cho moi
|
||||||
g_workers = &workers;
|
// channel — thay 16 ChannelWorker. Verify gui_probe `shared_worker` (G1.1):
|
||||||
|
// load + openGUI 2 instance tren 1 worker OK, khong crash/hang.
|
||||||
|
ChannelWorker uiWorker;
|
||||||
|
g_uiWorker = &uiWorker;
|
||||||
// B9: native editor windows per channel — keep alive (HWND outlives the job).
|
// B9: native editor windows per channel — keep alive (HWND outlives the job).
|
||||||
// Registry la global (g_guiWindows) — WM_DESTROY cleanup can tu VstWindowProc.
|
// Registry la global (g_guiWindows) — WM_DESTROY cleanup can tu VstWindowProc.
|
||||||
// B8: sample rate from the DAW (Rust spawns us with SF_SAMPLE_RATE).
|
// B8: sample rate from the DAW (Rust spawns us with SF_SAMPLE_RATE).
|
||||||
@@ -982,43 +974,62 @@ int main(int argc, char* argv[]) {
|
|||||||
g_pendingGui[guiCh] = { (void*)arg1, pluginId };
|
g_pendingGui[guiCh] = { (void*)arg1, pluginId };
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!workers[guiCh]) {
|
// G1.2: window + attach chay tren UiThread (1 thread chung, COM STA +
|
||||||
workers[guiCh] = std::make_unique<ChannelWorker>();
|
// message pump + job queue). Window messages do UiThread pump dispatch —
|
||||||
}
|
// audio loop khong con PeekMessageW (xoa block pump o main loop).
|
||||||
void* hwnd = (void*)arg1;
|
// G1.1 verify: gui_probe shared_worker = 1 worker load+openGUI 2 inst OK.
|
||||||
#ifdef _WIN32
|
uiWorker.post([&instruments, shmIPC, guiCh, arg1, arg2 = pluginId]() {
|
||||||
// Window PHAI thuoc MAIN thread (audio loop pump nay dispatch
|
AttachInFlightGuard _aifGuard{shmIPC};
|
||||||
// messages cua no moi vong lap). Tao/cap nhat window ngay tai day.
|
if (!instruments.get(guiCh)) {
|
||||||
HWND nativeHwnd = nullptr;
|
std::cerr << "[NativeBridge] GUI attach FAILED hwnd=0"
|
||||||
{
|
<< " plugin=" << arg2 << " ch=" << guiCh << " (no instrument loaded)" << std::endl;
|
||||||
std::lock_guard<std::mutex> lock(g_guiMutex);
|
return;
|
||||||
auto it = g_guiWindows.find(guiCh);
|
}
|
||||||
if (it != g_guiWindows.end()) nativeHwnd = (HWND)it->second;
|
if (auto* inst0 = instruments.get(guiCh)) {
|
||||||
}
|
if (inst0->hasAttachedView()) {
|
||||||
if (hwnd == 0) {
|
// Dedupe race: main da silence (setReloading) truoc khi post —
|
||||||
if (nativeHwnd && IsWindow(nativeHwnd)) {
|
// restore de channel khong cam vinh vien.
|
||||||
hwnd = (void*)nativeHwnd;
|
std::vector<uint32_t> sp = same_plugin_channels(guiCh);
|
||||||
SetWindowTextA(nativeHwnd, pluginId.c_str());
|
for (uint32_t y : sp) unmute_if_not_closing(y, "openGUI-dedupe");
|
||||||
ShowWindow(nativeHwnd, SW_SHOW);
|
unmute_if_not_closing(guiCh, "openGUI-dedupe");
|
||||||
SetForegroundWindow(nativeHwnd);
|
|
||||||
// Reuse: cap nhat USERDATA (channel+1) - inst CU da bi thay
|
|
||||||
// the boi assign() -> WM_DESTROY sau nay lookup inst MOI.
|
|
||||||
SetWindowLongPtrA(nativeHwnd, GWLP_USERDATA, (LONG_PTR)(guiCh + 1));
|
|
||||||
} else {
|
|
||||||
nativeHwnd = (HWND)create_native_vst_window(pluginId.c_str());
|
|
||||||
if (!nativeHwnd) {
|
|
||||||
std::cerr << "[NativeBridge] GUI create window FAILED plugin=" << pluginId << std::endl;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(g_guiMutex);
|
|
||||||
g_guiWindows[guiCh] = nativeHwnd; // keep window alive
|
|
||||||
g_hwndToCh[nativeHwnd] = guiCh; // WM_DESTROY cleanup
|
|
||||||
}
|
|
||||||
SetWindowLongPtrA(nativeHwnd, GWLP_USERDATA, (LONG_PTR)(guiCh + 1));
|
|
||||||
hwnd = (void*)nativeHwnd;
|
|
||||||
}
|
}
|
||||||
}
|
void* hwnd = (void*)arg1;
|
||||||
|
#ifdef _WIN32
|
||||||
|
HWND nativeHwnd = nullptr;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(g_guiMutex);
|
||||||
|
auto it = g_guiWindows.find(guiCh);
|
||||||
|
if (it != g_guiWindows.end()) nativeHwnd = (HWND)it->second;
|
||||||
|
}
|
||||||
|
if (hwnd == 0) {
|
||||||
|
if (nativeHwnd && IsWindow(nativeHwnd)) {
|
||||||
|
hwnd = (void*)nativeHwnd;
|
||||||
|
SetWindowTextA(nativeHwnd, arg2.c_str());
|
||||||
|
ShowWindow(nativeHwnd, SW_SHOW);
|
||||||
|
SetForegroundWindow(nativeHwnd);
|
||||||
|
// Reuse: cap nhat USERDATA (channel+1) - inst CU da bi thay
|
||||||
|
// the boi assign() -> WM_DESTROY sau nay lookup inst MOI.
|
||||||
|
SetWindowLongPtrA(nativeHwnd, GWLP_USERDATA, (LONG_PTR)(guiCh + 1));
|
||||||
|
} else {
|
||||||
|
nativeHwnd = (HWND)create_native_vst_window(arg2.c_str());
|
||||||
|
if (!nativeHwnd) {
|
||||||
|
std::cerr << "[NativeBridge] GUI create window FAILED plugin=" << arg2 << std::endl;
|
||||||
|
std::vector<uint32_t> sp = same_plugin_channels(guiCh);
|
||||||
|
for (uint32_t y : sp) unmute_if_not_closing(y, "openGUI-createfail");
|
||||||
|
unmute_if_not_closing(guiCh, "openGUI-createfail");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(g_guiMutex);
|
||||||
|
g_guiWindows[guiCh] = nativeHwnd; // keep window alive
|
||||||
|
g_hwndToCh[nativeHwnd] = guiCh; // WM_DESTROY cleanup
|
||||||
|
}
|
||||||
|
SetWindowLongPtrA(nativeHwnd, GWLP_USERDATA, (LONG_PTR)(guiCh + 1));
|
||||||
|
hwnd = (void*)nativeHwnd;
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
// G0.1b: silence same-path instances on the MAIN thread BEFORE the
|
// G0.1b: silence same-path instances on the MAIN thread BEFORE the
|
||||||
// attach job runs - renderAll of this loop iteration (and any before
|
// attach job runs - renderAll of this loop iteration (and any before
|
||||||
@@ -1042,31 +1053,15 @@ int main(int argc, char* argv[]) {
|
|||||||
instruments.setReloading(guiCh, true);
|
instruments.setReloading(guiCh, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
workers[guiCh]->post([&instruments, shmIPC, guiCh, hwnd, arg2 = pluginId]() {
|
|
||||||
AttachInFlightGuard _aifGuard{shmIPC};
|
|
||||||
if (!instruments.get(guiCh)) {
|
|
||||||
std::cerr << "[NativeBridge] GUI attach FAILED hwnd=" << hwnd
|
|
||||||
<< " plugin=" << arg2 << " ch=" << guiCh << " (no instrument loaded)" << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (auto* inst0 = instruments.get(guiCh)) {
|
|
||||||
if (inst0->hasAttachedView()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
// Option B: chi 1 editor VST mo tai 1 thoi diem toan
|
// Option B: chi 1 editor VST mo tai 1 thoi diem toan bridge. Instance
|
||||||
// bridge. Instance thu 2 cua CUNG plugin (Nexus) attach
|
// thu 2 cua CUNG plugin (Nexus) attach view o thread khac -> treo.
|
||||||
// view o apartment/worker khac -> treo. Dong editor cua
|
// G1.2: 1 UiThread -> dong editor cua channel khac DONG BO (goi
|
||||||
// channel khac TRUOC khi attach: WM_CLOSE -> main pump
|
// close_editor_now truc tiep). Khong the PostMessage(WM_CLOSE) cho
|
||||||
// destroy window -> WM_DESTROY -> closeGUI() + xoa registry.
|
// job khac nhu cu — job close nam sau job hien tai trong queue cua
|
||||||
// Chay tren worker job de khong stall writeIndex cua real-time
|
// CUNG thread -> khong bao gio chay -> 5s timeout -> attach abort.
|
||||||
// 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;
|
std::vector<uint32_t> others;
|
||||||
bool abortAttach = false;
|
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(g_guiMutex);
|
std::lock_guard<std::mutex> lock(g_guiMutex);
|
||||||
for (const auto& kv : g_guiWindows)
|
for (const auto& kv : g_guiWindows)
|
||||||
@@ -1088,39 +1083,7 @@ int main(int argc, char* argv[]) {
|
|||||||
if (it != g_guiWindows.end()) yHwnd = (HWND)it->second;
|
if (it != g_guiWindows.end()) yHwnd = (HWND)it->second;
|
||||||
}
|
}
|
||||||
if (!yHwnd || !IsWindow(yHwnd)) continue;
|
if (!yHwnd || !IsWindow(yHwnd)) continue;
|
||||||
if (instruments.get(y)) {
|
close_editor_now(y, yHwnd);
|
||||||
instruments.setReloading(y, true);
|
|
||||||
}
|
|
||||||
PostMessage(yHwnd, WM_CLOSE, 0, 0);
|
|
||||||
// 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;
|
|
||||||
// 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);
|
|
||||||
bool detached = !yi || !yi->hasAttachedView();
|
|
||||||
if (detached && !close_in_flight(y)) { closed = true; break; }
|
|
||||||
Sleep(10);
|
|
||||||
}
|
|
||||||
if (!closed) {
|
|
||||||
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");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
@@ -1271,27 +1234,8 @@ int main(int argc, char* argv[]) {
|
|||||||
// escapes the targeted guards (e.g. inside renderAll -> process())
|
// escapes the targeted guards (e.g. inside renderAll -> process())
|
||||||
// must not kill the bridge. Log and continue the loop.
|
// must not kill the bridge. Log and continue the loop.
|
||||||
try {
|
try {
|
||||||
#ifdef _WIN32
|
// G1.2: audio loop KHONG pump messages nua — moi window (native VST +
|
||||||
// Message pump: VST editors (Nexus, JUCE-based...) block inside
|
// editor children) thuoc UiThread; UiThread pump dispatch chung.
|
||||||
// view->attached() until the host dispatches messages — openGUI runs
|
|
||||||
// on a worker thread, so THIS loop must pump concurrently (verified
|
|
||||||
// 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);
|
|
||||||
// CRASH FIX (0xc000041d): same as the worker pump — a C++ exception
|
|
||||||
// raised inside a plugin window proc (Nexus) must never escape
|
|
||||||
// DispatchMessageW as an unhandled fatal callback exception.
|
|
||||||
try {
|
|
||||||
DispatchMessageW(&msg);
|
|
||||||
} catch (...) {
|
|
||||||
std::cerr << "[NativeBridge] main pump EXCEPTION — plugin window proc threw" << std::endl;
|
|
||||||
}
|
|
||||||
++pumpedMain;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
// A. Control events — non-rt safe, drained first
|
// A. Control events — non-rt safe, drained first
|
||||||
for (uint32_t i = 0; i < shmIPC->controlQueueCount; ++i) {
|
for (uint32_t i = 0; i < shmIPC->controlQueueCount; ++i) {
|
||||||
const auto& c = shmIPC->controlQueue[i];
|
const auto& c = shmIPC->controlQueue[i];
|
||||||
@@ -1310,10 +1254,7 @@ int main(int argc, char* argv[]) {
|
|||||||
std::string path(c.arg2, plen);
|
std::string path(c.arg2, plen);
|
||||||
InstrumentType t = (InstrumentType)c.arg0;
|
InstrumentType t = (InstrumentType)c.arg0;
|
||||||
uint32_t ch = c.channel & 0xF;
|
uint32_t ch = c.channel & 0xF;
|
||||||
if (!workers[ch]) {
|
uiWorker.post([&instruments, t, ch, path, sampleRate, block]() {
|
||||||
workers[ch] = std::make_unique<ChannelWorker>();
|
|
||||||
}
|
|
||||||
workers[ch]->post([&instruments, t, ch, path, sampleRate, block]() {
|
|
||||||
// CRASH FIX: this job enters the plugin DLL (createInstance
|
// CRASH FIX: this job enters the plugin DLL (createInstance
|
||||||
// in loadPlugin, old-instance terminate on replace) while
|
// in loadPlugin, old-instance terminate on replace) while
|
||||||
// the audio loop may process() another instance of the SAME
|
// the audio loop may process() another instance of the SAME
|
||||||
|
|||||||
Reference in New Issue
Block a user