fix(server): retry wl_event_loop_dispatch interrupted by signals
- commit
- ad674f5256764b698efd3fe6a0389ac5cc6f81ef
- author
- brice · · 2026-09-27 18:15 UTC
- committer
- brice · · 2026-09-27 18:47 UTC
- parent
- 6f1d5dd
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email - refs
- tag: v0.5.0
2 files · +29 -1
M server/display.go +9 -1
| @@ -154,6 +154,7 @@ func (d *Display) Run(ctx context.Context) error { | |||
| 154 | 154 | defer runtime.UnlockOSThread() | |
| 155 | 155 | defer close(d.stopped) | |
| 156 | 156 | defer d.destroy() | |
| 157 | + | consecutiveFailures := 0 | |
| 157 | 158 | for { | |
| 158 | 159 | if ctx.Err() != nil { | |
| 159 | 160 | return nil | |
| @@ -171,8 +172,15 @@ func (d *Display) Run(ctx context.Context) error { | |||
| 171 | 172 | wlDisplayFlushClients(d.c) | |
| 172 | 173 | // The timeout only bounds how late ctx cancellation is seen. | |
| 173 | 174 | if wlEventLoopDispatch(d.loop, 100) < 0 { | |
| 174 | - | return errors.New("purego-libwayland: wl_event_loop_dispatch failed") | |
| 175 | + | errno := lastErrno() | |
| 176 | + | consecutiveFailures++ | |
| 177 | + | // epoll_wait is interrupted by Go's preemption signals (EINTR); a persistent failure (EBADF, EINVAL) repeats. | |
| 178 | + | if consecutiveFailures < 16 { | |
| 179 | + | continue | |
| 180 | + | } | |
| 181 | + | return fmt.Errorf("purego-libwayland: wl_event_loop_dispatch failed %d times in a row: %w", consecutiveFailures, errno) | |
| 175 | 182 | } | |
| 183 | + | consecutiveFailures = 0 | |
| 176 | 184 | wlDisplayFlushClients(d.c) | |
| 177 | 185 | } | |
| 178 | 186 | } | |
| @@ -154,6 +154,7 @@ func (d *Display) Run(ctx context.Context) error { | |||||
| 154 | defer runtime.UnlockOSThread() | 154 | defer runtime.UnlockOSThread() | ||
| 155 | defer close(d.stopped) | 155 | defer close(d.stopped) | ||
| 156 | defer d.destroy() | 156 | defer d.destroy() | ||
| 157 | + | consecutiveFailures := 0 | |||
| 157 | for { | 158 | for { | ||
| 158 | if ctx.Err() != nil { | 159 | if ctx.Err() != nil { | ||
| 159 | return nil | 160 | return nil | ||
| @@ -171,8 +172,15 @@ func (d *Display) Run(ctx context.Context) error { | |||||
| 171 | wlDisplayFlushClients(d.c) | 172 | wlDisplayFlushClients(d.c) | ||
| 172 | // The timeout only bounds how late ctx cancellation is seen. | 173 | // The timeout only bounds how late ctx cancellation is seen. | ||
| 173 | if wlEventLoopDispatch(d.loop, 100) < 0 { | 174 | if wlEventLoopDispatch(d.loop, 100) < 0 { | ||
| 174 | - | return errors.New("purego-libwayland: wl_event_loop_dispatch failed") | 175 | + | errno := lastErrno() |
| 176 | + | consecutiveFailures++ | |||
| 177 | + | // epoll_wait is interrupted by Go's preemption signals (EINTR); a persistent failure (EBADF, EINVAL) repeats. | |||
| 178 | + | if consecutiveFailures < 16 { | |||
| 179 | + | continue | |||
| 180 | + | } | |||
| 181 | + | return fmt.Errorf("purego-libwayland: wl_event_loop_dispatch failed %d times in a row: %w", consecutiveFailures, errno) | |||
| 175 | } | 182 | } | ||
| 183 | + | consecutiveFailures = 0 | |||
| 176 | wlDisplayFlushClients(d.c) | 184 | wlDisplayFlushClients(d.c) | ||
| 177 | } | 185 | } | ||
| 178 | } | 186 | } | ||
M server/lib.go +20 -0
| @@ -6,6 +6,7 @@ import ( | |||
| 6 | 6 | "unsafe" | |
| 7 | 7 | ||
| 8 | 8 | "github.com/bnema/purego" | |
| 9 | + | "golang.org/x/sys/unix" | |
| 9 | 10 | ) | |
| 10 | 11 | ||
| 11 | 12 | // libwayland-server entry points. Only non-varargs functions are bound. | |
| @@ -30,6 +31,7 @@ var ( | |||
| 30 | 31 | ||
| 31 | 32 | symDisplayFlushClients, symEventLoopDispatch, symResourceCreate uintptr | |
| 32 | 33 | symResourceSetDispatcher, symResourcePostEventArr, symResourceDestroy, symResourceGetID uintptr | |
| 34 | + | symErrnoLocation uintptr | |
| 33 | 35 | ||
| 34 | 36 | // Shared C callbacks. purego callbacks are never freed, so there is one | |
| 35 | 37 | // of each for the whole process. | |
| @@ -82,6 +84,19 @@ func load() error { | |||
| 82 | 84 | return | |
| 83 | 85 | } | |
| 84 | 86 | } | |
| 87 | + | symErrnoLocation, err = purego.Dlsym(lib, "__errno_location") | |
| 88 | + | if err != nil { | |
| 89 | + | libc, openErr := purego.Dlopen("libc.so.6", purego.RTLD_NOW|purego.RTLD_GLOBAL) | |
| 90 | + | if openErr != nil { | |
| 91 | + | loadErr = fmt.Errorf("purego-libwayland: open libc.so.6: %w", openErr) | |
| 92 | + | return | |
| 93 | + | } | |
| 94 | + | symErrnoLocation, err = purego.Dlsym(libc, "__errno_location") | |
| 95 | + | if err != nil { | |
| 96 | + | loadErr = fmt.Errorf("purego-libwayland: resolve __errno_location: %w", err) | |
| 97 | + | return | |
| 98 | + | } | |
| 99 | + | } | |
| 85 | 100 | ||
| 86 | 101 | cbDispatcher = purego.NewCallbackInts(func(a *purego.CallbackArgs) uintptr { | |
| 87 | 102 | // C memory owned by libwayland; reinterpret pointer bits without uintptr-to-pointer conversion. | |
| @@ -111,6 +126,11 @@ func wlEventLoopDispatch(loop uintptr, timeout int32) int32 { | |||
| 111 | 126 | r, _, _ := purego.Syscall6(symEventLoopDispatch, loop, uintptr(timeout), 0, 0, 0, 0) | |
| 112 | 127 | return int32(r) | |
| 113 | 128 | } | |
| 129 | + | func lastErrno() unix.Errno { | |
| 130 | + | p, _, _ := purego.Syscall6(symErrnoLocation, 0, 0, 0, 0, 0, 0) | |
| 131 | + | // C memory owned by libc; reinterpret pointer bits without uintptr-to-pointer conversion. | |
| 132 | + | return unix.Errno(**(**int32)(unsafe.Pointer(&p))) | |
| 133 | + | } | |
| 114 | 134 | func wlResourceCreate(client, iface uintptr, version int32, id uint32) uintptr { | |
| 115 | 135 | r, _, _ := purego.Syscall6(symResourceCreate, client, iface, uintptr(version), uintptr(id), 0, 0) | |
| 116 | 136 | return r | |
| @@ -6,6 +6,7 @@ import ( | |||||
| 6 | "unsafe" | 6 | "unsafe" | ||
| 7 | 7 | ||||
| 8 | "github.com/bnema/purego" | 8 | "github.com/bnema/purego" | ||
| 9 | + | "golang.org/x/sys/unix" | |||
| 9 | ) | 10 | ) | ||
| 10 | 11 | ||||
| 11 | // libwayland-server entry points. Only non-varargs functions are bound. | 12 | // libwayland-server entry points. Only non-varargs functions are bound. | ||
| @@ -30,6 +31,7 @@ var ( | |||||
| 30 | 31 | ||||
| 31 | symDisplayFlushClients, symEventLoopDispatch, symResourceCreate uintptr | 32 | symDisplayFlushClients, symEventLoopDispatch, symResourceCreate uintptr | ||
| 32 | symResourceSetDispatcher, symResourcePostEventArr, symResourceDestroy, symResourceGetID uintptr | 33 | symResourceSetDispatcher, symResourcePostEventArr, symResourceDestroy, symResourceGetID uintptr | ||
| 34 | + | symErrnoLocation uintptr | |||
| 33 | 35 | ||||
| 34 | // Shared C callbacks. purego callbacks are never freed, so there is one | 36 | // Shared C callbacks. purego callbacks are never freed, so there is one | ||
| 35 | // of each for the whole process. | 37 | // of each for the whole process. | ||
| @@ -82,6 +84,19 @@ func load() error { | |||||
| 82 | return | 84 | return | ||
| 83 | } | 85 | } | ||
| 84 | } | 86 | } | ||
| 87 | + | symErrnoLocation, err = purego.Dlsym(lib, "__errno_location") | |||
| 88 | + | if err != nil { | |||
| 89 | + | libc, openErr := purego.Dlopen("libc.so.6", purego.RTLD_NOW|purego.RTLD_GLOBAL) | |||
| 90 | + | if openErr != nil { | |||
| 91 | + | loadErr = fmt.Errorf("purego-libwayland: open libc.so.6: %w", openErr) | |||
| 92 | + | return | |||
| 93 | + | } | |||
| 94 | + | symErrnoLocation, err = purego.Dlsym(libc, "__errno_location") | |||
| 95 | + | if err != nil { | |||
| 96 | + | loadErr = fmt.Errorf("purego-libwayland: resolve __errno_location: %w", err) | |||
| 97 | + | return | |||
| 98 | + | } | |||
| 99 | + | } | |||
| 85 | 100 | ||||
| 86 | cbDispatcher = purego.NewCallbackInts(func(a *purego.CallbackArgs) uintptr { | 101 | cbDispatcher = purego.NewCallbackInts(func(a *purego.CallbackArgs) uintptr { | ||
| 87 | // C memory owned by libwayland; reinterpret pointer bits without uintptr-to-pointer conversion. | 102 | // C memory owned by libwayland; reinterpret pointer bits without uintptr-to-pointer conversion. | ||
| @@ -111,6 +126,11 @@ func wlEventLoopDispatch(loop uintptr, timeout int32) int32 { | |||||
| 111 | r, _, _ := purego.Syscall6(symEventLoopDispatch, loop, uintptr(timeout), 0, 0, 0, 0) | 126 | r, _, _ := purego.Syscall6(symEventLoopDispatch, loop, uintptr(timeout), 0, 0, 0, 0) | ||
| 112 | return int32(r) | 127 | return int32(r) | ||
| 113 | } | 128 | } | ||
| 129 | + | func lastErrno() unix.Errno { | |||
| 130 | + | p, _, _ := purego.Syscall6(symErrnoLocation, 0, 0, 0, 0, 0, 0) | |||
| 131 | + | // C memory owned by libc; reinterpret pointer bits without uintptr-to-pointer conversion. | |||
| 132 | + | return unix.Errno(**(**int32)(unsafe.Pointer(&p))) | |||
| 133 | + | } | |||
| 114 | func wlResourceCreate(client, iface uintptr, version int32, id uint32) uintptr { | 134 | func wlResourceCreate(client, iface uintptr, version int32, id uint32) uintptr { | ||
| 115 | r, _, _ := purego.Syscall6(symResourceCreate, client, iface, uintptr(version), uintptr(id), 0, 0) | 135 | r, _, _ := purego.Syscall6(symResourceCreate, client, iface, uintptr(version), uintptr(id), 0, 0) | ||
| 116 | return r | 136 | return r | ||