diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 8e46480384f..bb192f95192 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -48,6 +48,7 @@ After processing, the content will be moved to the main changelog and this file **Fixed:** - Fix memory leak in event system during window close operations (#5678) - Fix crash when using context menus on Linux with Wayland +- Fix deadlock EventIPCTransport.DispatchWailsEvent holding RLock during InvokeSync (#5106) **Security:** - Update dependencies to address CVE-2024-12345 in third-party library diff --git a/v3/pkg/application/transport_event_ipc.go b/v3/pkg/application/transport_event_ipc.go index 4b3309ee6e7..b6f831a87e6 100644 --- a/v3/pkg/application/transport_event_ipc.go +++ b/v3/pkg/application/transport_event_ipc.go @@ -5,10 +5,21 @@ type EventIPCTransport struct { } func (t *EventIPCTransport) DispatchWailsEvent(event *CustomEvent) { - // Snapshot windows under RLock + // Snapshot the window list under the lock, then release before dispatching. + // DispatchWailsEvent calls ExecJS → InvokeSync which blocks until the main + // thread executes the JS. Holding windowsLock.RLock during InvokeSync causes + // a deadlock when the main thread (or any other goroutine) needs windowsLock + // for write operations (NewWithOptions, Remove) — the pending writer blocks + // new readers, and the existing readers can't complete because InvokeSync + // needs the main thread which is waiting for the write lock. t.app.windowsLock.RLock() - defer t.app.windowsLock.RUnlock() - for _, window := range t.app.windows { + windows := make([]Window, 0, len(t.app.windows)) + for _, w := range t.app.windows { + windows = append(windows, w) + } + t.app.windowsLock.RUnlock() + + for _, window := range windows { if event.IsCancelled() { return } diff --git a/v3/pkg/application/webview_window.go b/v3/pkg/application/webview_window.go index aec674db830..77e9d0d62cd 100644 --- a/v3/pkg/application/webview_window.go +++ b/v3/pkg/application/webview_window.go @@ -1232,6 +1232,9 @@ func (w *WebviewWindow) SetFrameless(frameless bool) Window { } func (w *WebviewWindow) DispatchWailsEvent(event *CustomEvent) { + if w.impl == nil || w.isDestroyed() { + return + } // Guard against race condition where event fires before runtime is initialized // This can happen during page reload when WindowLoadFinished fires before // the JavaScript runtime has mounted dispatchWailsEvent on window._wails