Description
Potential deadlock in EventIPCTransport.DispatchWailsEvent
(transport_event_ipc.go)
We hit a deadlock that traces back to a lock ordering
issue in the Wails event dispatch path.
In transport_event_ipc.go:7-17, DispatchWailsEvent acquires
windowsLock.RLock() and then calls window.DispatchWailsEvent() for each
window — which calls ExecJS → InvokeSync, blocking until the main
thread executes the JS.
func (t *EventIPCTransport) DispatchWailsEvent(event *CustomEvent) {
t.app.windowsLock.RLock() // holds read lock
defer t.app.windowsLock.RUnlock()
for _, window := range t.app.windows {
if event.IsCancelled() {
return
}
window.DispatchWailsEvent(event) // → ExecJS → InvokeSync
(blocks on main thread)
}
}
If the main thread needs windowsLock for any reason (e.g.
processURLRequest → WindowManager.GetByID → RLock, or any write lock
from NewWithOptions/Remove), you get a deadlock:
- Event dispatch goroutines: hold windowsLock.RLock(), waiting for main
thread via InvokeSync
- Main thread: waiting for windowsLock (blocked because a write lock is
pending, which blocks behind the active readers)
In our case this froze the entire app for 4+ hours. We confirmed the
deadlock via pprof goroutine dump — goroutine 1 (main thread) blocked
on windowsLock.RLock for 261 minutes, with ~30 event dispatch
goroutines piled up behind it.
Would it be possible to snapshot the window list under the lock and
then release it before calling InvokeSync? Something like:
func (t *EventIPCTransport) DispatchWailsEvent(event *CustomEvent) {
t.app.windowsLock.RLock()
snapshot := make([]Window, 0, len(t.app.windows))
for _, w := range t.app.windows {
snapshot = append(snapshot, w)
}
t.app.windowsLock.RUnlock()
for _, window := range snapshot {
if event.IsCancelled() {
return
}
window.DispatchWailsEvent(event)
}
}
This is on Wails v3 alpha.74, macOS (darwin/arm64).
To Reproduce
...
Expected behaviour
...
Screenshots
No response
Attempted Fixes
No response
System Details
Additional context
No response
Description
Potential deadlock in EventIPCTransport.DispatchWailsEvent
(transport_event_ipc.go)
We hit a deadlock that traces back to a lock ordering
issue in the Wails event dispatch path.
In transport_event_ipc.go:7-17, DispatchWailsEvent acquires
windowsLock.RLock() and then calls window.DispatchWailsEvent() for each
window — which calls ExecJS → InvokeSync, blocking until the main
thread executes the JS.
If the main thread needs windowsLock for any reason (e.g.
processURLRequest → WindowManager.GetByID → RLock, or any write lock
from NewWithOptions/Remove), you get a deadlock:
thread via InvokeSync
pending, which blocks behind the active readers)
In our case this froze the entire app for 4+ hours. We confirmed the
deadlock via pprof goroutine dump — goroutine 1 (main thread) blocked
on windowsLock.RLock for 261 minutes, with ~30 event dispatch
goroutines piled up behind it.
Would it be possible to snapshot the window list under the lock and
then release it before calling InvokeSync? Something like:
This is on Wails v3 alpha.74, macOS (darwin/arm64).
To Reproduce
...
Expected behaviour
...
Screenshots
No response
Attempted Fixes
No response
System Details
Additional context
No response