test(wayland): assert explicit acquire callbacks apply commits inline
The acquire callback test now checks the commit leaves the queue in the callback's own display turn without posting a pace tick. Explicit waits returned already signalled or failed are marked explicit, so fired skips the implicit waiter's mutex; run's comments now describe implicit fence fds.
2 files · +18 -9
M internal/adapters/wayland/syncobj.go +8 -8
| @@ -247,7 +247,7 @@ type syncWaiter struct { | |||
| 247 | 247 | } | |
| 248 | 248 | ||
| 249 | 249 | // syncWait is one acquire point a queued commit waits for. An explicit wait | |
| 250 | - | // (src set) lives on the display loop only; an implicit one is polled by run. | |
| 250 | + | // lives on the display loop only; an implicit one is polled by run. | |
| 251 | 251 | type syncWait struct { | |
| 252 | 252 | efd int | |
| 253 | 253 | src acquireRemover // explicit: the display-loop registration, nil once removed | |
| @@ -276,21 +276,21 @@ func newSyncWaiter(wake func()) (*syncWaiter, error) { | |||
| 276 | 276 | func (sw *syncWaiter) watch(p syncPoint) (*syncWait, error) { | |
| 277 | 277 | ready, err := p.tl.dev.signalled(p.tl.handle, p.point) | |
| 278 | 278 | if err != nil || ready { | |
| 279 | - | return &syncWait{efd: -1, done: true, failed: err != nil}, err | |
| 279 | + | return &syncWait{efd: -1, explicit: true, done: true, failed: err != nil}, err | |
| 280 | 280 | } | |
| 281 | 281 | efd, err := unix.Eventfd(0, unix.EFD_CLOEXEC|unix.EFD_NONBLOCK) | |
| 282 | 282 | if err != nil { | |
| 283 | - | return &syncWait{efd: -1, done: true, failed: true}, err | |
| 283 | + | return &syncWait{efd: -1, explicit: true, done: true, failed: true}, err | |
| 284 | 284 | } | |
| 285 | 285 | if err := p.tl.dev.eventfd(p.tl.handle, p.point, efd); err != nil { | |
| 286 | 286 | unix.Close(efd) | |
| 287 | - | return &syncWait{efd: -1, done: true, failed: true}, err | |
| 287 | + | return &syncWait{efd: -1, explicit: true, done: true, failed: true}, err | |
| 288 | 288 | } | |
| 289 | 289 | w := &syncWait{efd: efd, explicit: true} | |
| 290 | 290 | src, err := sw.src.add(efd, func() { sw.onAcquire(w) }) | |
| 291 | 291 | if err != nil { | |
| 292 | 292 | unix.Close(efd) | |
| 293 | - | return &syncWait{efd: -1, done: true, failed: true}, err | |
| 293 | + | return &syncWait{efd: -1, explicit: true, done: true, failed: true}, err | |
| 294 | 294 | } | |
| 295 | 295 | w.src = src | |
| 296 | 296 | if sw.pending == nil { | |
| @@ -431,7 +431,7 @@ func (sw *syncWaiter) cancel(w *syncWait) { | |||
| 431 | 431 | delete(sw.ready, w) | |
| 432 | 432 | sw.mu.Unlock() | |
| 433 | 433 | w.done = true | |
| 434 | - | _, _ = sw.pipeW.Write([]byte{0}) // run closes its eventfd | |
| 434 | + | _, _ = sw.pipeW.Write([]byte{0}) // run closes its fence fd | |
| 435 | 435 | } | |
| 436 | 436 | ||
| 437 | 437 | func (sw *syncWaiter) stopped() bool { | |
| @@ -456,12 +456,12 @@ func (sw *syncWaiter) fail(waits []*syncWait) { | |||
| 456 | 456 | sw.wake() | |
| 457 | 457 | } | |
| 458 | 458 | ||
| 459 | - | // run polls every watched eventfd; it owns and closes them. | |
| 459 | + | // run polls the implicit dma-buf fence descriptors; it owns and closes them. | |
| 460 | 460 | func (sw *syncWaiter) run(ctx context.Context) { | |
| 461 | 461 | defer sw.pipeR.Close() | |
| 462 | 462 | defer sw.waiter.Close() | |
| 463 | 463 | pipeFD := int(sw.pipeR.Fd()) | |
| 464 | - | // The pipe lives as long as the poller; the eventfds come and go. The | |
| 464 | + | // The pipe lives as long as the poller; the fence fds come and go. The | |
| 465 | 465 | // poll sleeps until a fence, a new or cancelled wait (the pipe), or the | |
| 466 | 466 | // end of ctx. | |
| 467 | 467 | var waits []*syncWait | |
| @@ -247,7 +247,7 @@ type syncWaiter struct { | |||||
| 247 | } | 247 | } | ||
| 248 | 248 | ||||
| 249 | // syncWait is one acquire point a queued commit waits for. An explicit wait | 249 | // syncWait is one acquire point a queued commit waits for. An explicit wait | ||
| 250 | - | // (src set) lives on the display loop only; an implicit one is polled by run. | 250 | + | // lives on the display loop only; an implicit one is polled by run. |
| 251 | type syncWait struct { | 251 | type syncWait struct { | ||
| 252 | efd int | 252 | efd int | ||
| 253 | src acquireRemover // explicit: the display-loop registration, nil once removed | 253 | src acquireRemover // explicit: the display-loop registration, nil once removed | ||
| @@ -276,21 +276,21 @@ func newSyncWaiter(wake func()) (*syncWaiter, error) { | |||||
| 276 | func (sw *syncWaiter) watch(p syncPoint) (*syncWait, error) { | 276 | func (sw *syncWaiter) watch(p syncPoint) (*syncWait, error) { | ||
| 277 | ready, err := p.tl.dev.signalled(p.tl.handle, p.point) | 277 | ready, err := p.tl.dev.signalled(p.tl.handle, p.point) | ||
| 278 | if err != nil || ready { | 278 | if err != nil || ready { | ||
| 279 | - | return &syncWait{efd: -1, done: true, failed: err != nil}, err | 279 | + | return &syncWait{efd: -1, explicit: true, done: true, failed: err != nil}, err |
| 280 | } | 280 | } | ||
| 281 | efd, err := unix.Eventfd(0, unix.EFD_CLOEXEC|unix.EFD_NONBLOCK) | 281 | efd, err := unix.Eventfd(0, unix.EFD_CLOEXEC|unix.EFD_NONBLOCK) | ||
| 282 | if err != nil { | 282 | if err != nil { | ||
| 283 | - | return &syncWait{efd: -1, done: true, failed: true}, err | 283 | + | return &syncWait{efd: -1, explicit: true, done: true, failed: true}, err |
| 284 | } | 284 | } | ||
| 285 | if err := p.tl.dev.eventfd(p.tl.handle, p.point, efd); err != nil { | 285 | if err := p.tl.dev.eventfd(p.tl.handle, p.point, efd); err != nil { | ||
| 286 | unix.Close(efd) | 286 | unix.Close(efd) | ||
| 287 | - | return &syncWait{efd: -1, done: true, failed: true}, err | 287 | + | return &syncWait{efd: -1, explicit: true, done: true, failed: true}, err |
| 288 | } | 288 | } | ||
| 289 | w := &syncWait{efd: efd, explicit: true} | 289 | w := &syncWait{efd: efd, explicit: true} | ||
| 290 | src, err := sw.src.add(efd, func() { sw.onAcquire(w) }) | 290 | src, err := sw.src.add(efd, func() { sw.onAcquire(w) }) | ||
| 291 | if err != nil { | 291 | if err != nil { | ||
| 292 | unix.Close(efd) | 292 | unix.Close(efd) | ||
| 293 | - | return &syncWait{efd: -1, done: true, failed: true}, err | 293 | + | return &syncWait{efd: -1, explicit: true, done: true, failed: true}, err |
| 294 | } | 294 | } | ||
| 295 | w.src = src | 295 | w.src = src | ||
| 296 | if sw.pending == nil { | 296 | if sw.pending == nil { | ||
| @@ -431,7 +431,7 @@ func (sw *syncWaiter) cancel(w *syncWait) { | |||||
| 431 | delete(sw.ready, w) | 431 | delete(sw.ready, w) | ||
| 432 | sw.mu.Unlock() | 432 | sw.mu.Unlock() | ||
| 433 | w.done = true | 433 | w.done = true | ||
| 434 | - | _, _ = sw.pipeW.Write([]byte{0}) // run closes its eventfd | 434 | + | _, _ = sw.pipeW.Write([]byte{0}) // run closes its fence fd |
| 435 | } | 435 | } | ||
| 436 | 436 | ||||
| 437 | func (sw *syncWaiter) stopped() bool { | 437 | func (sw *syncWaiter) stopped() bool { | ||
| @@ -456,12 +456,12 @@ func (sw *syncWaiter) fail(waits []*syncWait) { | |||||
| 456 | sw.wake() | 456 | sw.wake() | ||
| 457 | } | 457 | } | ||
| 458 | 458 | ||||
| 459 | - | // run polls every watched eventfd; it owns and closes them. | 459 | + | // run polls the implicit dma-buf fence descriptors; it owns and closes them. |
| 460 | func (sw *syncWaiter) run(ctx context.Context) { | 460 | func (sw *syncWaiter) run(ctx context.Context) { | ||
| 461 | defer sw.pipeR.Close() | 461 | defer sw.pipeR.Close() | ||
| 462 | defer sw.waiter.Close() | 462 | defer sw.waiter.Close() | ||
| 463 | pipeFD := int(sw.pipeR.Fd()) | 463 | pipeFD := int(sw.pipeR.Fd()) | ||
| 464 | - | // The pipe lives as long as the poller; the eventfds come and go. The | 464 | + | // The pipe lives as long as the poller; the fence fds come and go. The |
| 465 | // poll sleeps until a fence, a new or cancelled wait (the pipe), or the | 465 | // poll sleeps until a fence, a new or cancelled wait (the pipe), or the | ||
| 466 | // end of ctx. | 466 | // end of ctx. | ||
| 467 | var waits []*syncWait | 467 | var waits []*syncWait | ||
M internal/adapters/wayland/syncwait_explicit_test.go +10 -1
| @@ -144,7 +144,16 @@ func TestSyncobjCommitAppliedByAcquireCallback(t *testing.T) { | |||
| 144 | 144 | t.Fatal("content before the acquire point fired") | |
| 145 | 145 | } | |
| 146 | 146 | h.fire(1) // the kernel signals: efd readable (display loop is mocked, so nothing reads it) | |
| 147 | - | h.s.display.Do(func() { fire() }) | |
| 147 | + | var applied, posted bool | |
| 148 | + | h.s.display.Do(func() { | |
| 149 | + | fire() | |
| 150 | + | // Same tick: the commit left the queue without posting a pace tick. | |
| 151 | + | applied = len(h.s.windows[h.win].xdg.surface.queue) == 0 | |
| 152 | + | posted = h.s.paceSignal.Load() | |
| 153 | + | }) | |
| 154 | + | if !applied || posted { | |
| 155 | + | t.Fatalf("acquire callback: applied=%v posted tick=%v, want applied inline", applied, posted) | |
| 156 | + | } | |
| 148 | 157 | got, ok := h.content(2 * time.Second) | |
| 149 | 158 | if !ok || got.Acquire == nil { | |
| 150 | 159 | t.Fatalf("content %+v", got) | |
| @@ -144,7 +144,16 @@ func TestSyncobjCommitAppliedByAcquireCallback(t *testing.T) { | |||||
| 144 | t.Fatal("content before the acquire point fired") | 144 | t.Fatal("content before the acquire point fired") | ||
| 145 | } | 145 | } | ||
| 146 | h.fire(1) // the kernel signals: efd readable (display loop is mocked, so nothing reads it) | 146 | h.fire(1) // the kernel signals: efd readable (display loop is mocked, so nothing reads it) | ||
| 147 | - | h.s.display.Do(func() { fire() }) | 147 | + | var applied, posted bool |
| 148 | + | h.s.display.Do(func() { | |||
| 149 | + | fire() | |||
| 150 | + | // Same tick: the commit left the queue without posting a pace tick. | |||
| 151 | + | applied = len(h.s.windows[h.win].xdg.surface.queue) == 0 | |||
| 152 | + | posted = h.s.paceSignal.Load() | |||
| 153 | + | }) | |||
| 154 | + | if !applied || posted { | |||
| 155 | + | t.Fatalf("acquire callback: applied=%v posted tick=%v, want applied inline", applied, posted) | |||
| 156 | + | } | |||
| 148 | got, ok := h.content(2 * time.Second) | 157 | got, ok := h.content(2 * time.Second) | ||
| 149 | if !ok || got.Acquire == nil { | 158 | if !ok || got.Acquire == nil { | ||
| 150 | t.Fatalf("content %+v", got) | 159 | t.Fatalf("content %+v", got) | ||