detached 5c1298d

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.
commit
5c1298d894d2df910df351c347672964f1147a92
author
brice · · 2026-10-22 18:58 UTC
parent
d8d545e
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
checks
1/1
2 files · +18 -9
M internal/adapters/wayland/syncobj.go +8 -8
@@ -247,7 +247,7 @@ type syncWaiter struct {
247247 }
248248
249249 // 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.
251251 type syncWait struct {
252252 efd int
253253 src acquireRemover // explicit: the display-loop registration, nil once removed
@@ -276,21 +276,21 @@ func newSyncWaiter(wake func()) (*syncWaiter, error) {
276276 func (sw *syncWaiter) watch(p syncPoint) (*syncWait, error) {
277277 ready, err := p.tl.dev.signalled(p.tl.handle, p.point)
278278 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
280280 }
281281 efd, err := unix.Eventfd(0, unix.EFD_CLOEXEC|unix.EFD_NONBLOCK)
282282 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
284284 }
285285 if err := p.tl.dev.eventfd(p.tl.handle, p.point, efd); err != nil {
286286 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
288288 }
289289 w := &syncWait{efd: efd, explicit: true}
290290 src, err := sw.src.add(efd, func() { sw.onAcquire(w) })
291291 if err != nil {
292292 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
294294 }
295295 w.src = src
296296 if sw.pending == nil {
@@ -431,7 +431,7 @@ func (sw *syncWaiter) cancel(w *syncWait) {
431431 delete(sw.ready, w)
432432 sw.mu.Unlock()
433433 w.done = true
434- _, _ = sw.pipeW.Write([]byte{0}) // run closes its eventfd
434+ _, _ = sw.pipeW.Write([]byte{0}) // run closes its fence fd
435435 }
436436
437437 func (sw *syncWaiter) stopped() bool {
@@ -456,12 +456,12 @@ func (sw *syncWaiter) fail(waits []*syncWait) {
456456 sw.wake()
457457 }
458458
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.
460460 func (sw *syncWaiter) run(ctx context.Context) {
461461 defer sw.pipeR.Close()
462462 defer sw.waiter.Close()
463463 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
465465 // poll sleeps until a fence, a new or cancelled wait (the pipe), or the
466466 // end of ctx.
467467 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 wait249 // 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 int252 efd int
253 src acquireRemover // explicit: the display-loop registration, nil once removed253 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}, err279+ 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}, err283+ 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}, err287+ 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}, err293+ return &syncWait{efd: -1, explicit: true, done: true, failed: true}, err
294 }294 }
295 w.src = src295 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 = true433 w.done = true
434- _, _ = sw.pipeW.Write([]byte{0}) // run closes its eventfd434+ _, _ = 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. The464+ // 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 the465 // 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 []*syncWait467 var waits []*syncWait
M internal/adapters/wayland/syncwait_explicit_test.go +10 -1
@@ -144,7 +144,16 @@ func TestSyncobjCommitAppliedByAcquireCallback(t *testing.T) {
144144 t.Fatal("content before the acquire point fired")
145145 }
146146 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+ }
148157 got, ok := h.content(2 * time.Second)
149158 if !ok || got.Acquire == nil {
150159 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)

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 25

Tags 10

↑↓ moveenter openesc close