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 }