1package wayland
2
3import (
4 "context"
5 "os"
6 "strings"
7 "sync"
8 "testing"
9 "time"
10 "unsafe"
11
12 "github.com/bnema/go-wayland-bindings/server/committiming"
13 "github.com/bnema/go-wayland-bindings/server/fifo"
14 "github.com/bnema/go-wayland-bindings/server/linuxdmabuf"
15 "github.com/bnema/go-wayland-bindings/server/linuxdrmsyncobj"
16 "github.com/bnema/go-wayland-bindings/server/wayland"
17 "github.com/bnema/neferwl/internal/adapters/logging"
18 "github.com/bnema/neferwl/internal/ports"
19 "github.com/bnema/wlturbo"
20 "github.com/stretchr/testify/mock"
21 "golang.org/x/sys/unix"
22)
23
24func TestSyncobjStructSizes(t *testing.T) {
25 for _, tc := range []struct {
26 name string
27 got uintptr
28 req uintptr
29 }{
30 {"destroy", unsafe.Sizeof(syncobjDestroy{}), ioctlSyncobjDestroy},
31 {"handle_to_fd", unsafe.Sizeof(syncobjHandle{}), ioctlSyncobjHandleToFD},
32 {"fd_to_handle", unsafe.Sizeof(syncobjHandle{}), ioctlSyncobjFDToHandle},
33 {"timeline_signal", unsafe.Sizeof(syncobjTimelineArray{}), ioctlSyncobjTimelineSignal},
34 {"timeline_wait", unsafe.Sizeof(syncobjTimelineWait{}), ioctlSyncobjTimelineWait},
35 {"eventfd", unsafe.Sizeof(syncobjEventfd{}), ioctlSyncobjEventfd},
36 {"get_cap", unsafe.Sizeof(drmCap{}), ioctlGetCap},
37 } {
38 if want := (tc.req >> 16) & 0x3fff; tc.got != want {
39 t.Errorf("%s: size %d, ioctl encodes %d", tc.name, tc.got, want)
40 }
41 }
42}
43
44// syncHarness is a server with a mocked syncobj device and a client with
45// a mapped window, a dmabuf buffer and the syncobj objects.
46type syncHarness struct {
47 t *testing.T
48 s *Server
49 c *wlturbo.Display
50 dev *mocksyncobjDevice
51 contents chan ports.SurfaceContent
52 presented chan ports.OutputPresented
53 surf uint32
54 syncSurf uint32
55 timeline uint32
56 win ports.WindowID
57 mu sync.Mutex
58 efds map[uint64]int // eventfd by acquire point
59 signals []uint64
60 fences []*os.File
61 releases map[uint32]*syncReleaseProxy
62 mgr uint32
63}
64
65func newSyncHarness(t *testing.T) *syncHarness {
66 h := &syncHarness{t: t, efds: map[uint64]int{}}
67 h.dev = newMocksyncobjDevice(t)
68 h.dev.EXPECT().fdToHandle(mock.Anything).Return(7, nil).Maybe()
69 h.dev.EXPECT().destroy(mock.Anything).Return(nil).Maybe()
70 h.dev.EXPECT().signalled(uint32(7), mock.Anything).Return(false, nil).Maybe()
71 h.dev.EXPECT().eventfd(uint32(7), mock.Anything, mock.Anything).RunAndReturn(func(_ uint32, point uint64, efd int) error {
72 // Keep a duplicate: the waiter closes its own when it fires.
73 dup, err := unix.Dup(efd)
74 if err != nil {
75 return err
76 }
77 h.mu.Lock()
78 h.efds[point] = dup
79 h.mu.Unlock()
80 return nil
81 }).Maybe()
82 h.dev.EXPECT().exportSyncFile(uint32(7), mock.Anything).RunAndReturn(func(uint32, uint64) (*os.File, error) {
83 r, w, err := os.Pipe()
84 if err != nil {
85 return nil, err
86 }
87 w.Close()
88 h.mu.Lock()
89 h.fences = append(h.fences, r)
90 h.mu.Unlock()
91 return r, nil
92 }).Maybe()
93 h.dev.EXPECT().signal(uint32(7), mock.Anything).RunAndReturn(func(_ uint32, point uint64) error {
94 h.mu.Lock()
95 h.signals = append(h.signals, point)
96 h.mu.Unlock()
97 return nil
98 }).Maybe()
99 dir := t.TempDir()
100 events := make(chan ports.ClientEvent, 16)
101 commands := make(chan ports.ClientCommand, 16)
102 h.contents = make(chan ports.SurfaceContent, 64)
103 h.presented = make(chan ports.OutputPresented, 16)
104 sup := ports.DMABufSupport{Formats: []ports.DMABufFormat{linearARGB}}
105 s, err := New(Options{RuntimeDir: dir, Outputs: testOutputs, DMABuf: sup, syncDev: h.dev}, Channels{Events: events, Commands: commands, Contents: h.contents, Presented: h.presented}, logging.For(context.Background(), "wayland"))
106 if err != nil {
107 t.Fatal(err)
108 }
109 ctx, cancel := context.WithCancel(context.Background())
110 done := make(chan error, 1)
111 go func() { done <- s.Run(ctx) }()
112 t.Cleanup(func() {
113 cancel()
114 <-done
115 h.mu.Lock()
116 for _, fd := range h.efds {
117 unix.Close(fd)
118 }
119 h.mu.Unlock()
120 })
121 h.s = s
122 h.c = protocolClient(t, s, dir)
123 w, surf, xdg := surfaceMapper(t, h.c, events)()
124 registerProtocol(t, h.c, xdg)
125 h.win, h.surf = w.ID, surf
126 commands <- ports.ConfigureWindow{ID: w.ID, Width: 64, Height: 16, Output: "HEADLESS-1", Visible: true}
127 mgr := bindProtocol(t, h.c, "wp_linux_drm_syncobj_manager_v1")
128 h.syncSurf = h.c.AllocateID()
129 requestProtocol(t, h.c, mgr, linuxdrmsyncobj.WpLinuxDrmSyncobjManagerV1RequestGetSurface, h.syncSurf, surf)
130 registerProtocol(t, h.c, h.syncSurf)
131 fd, err := unix.MemfdCreate("timeline", 0)
132 if err != nil {
133 t.Fatal(err)
134 }
135 defer unix.Close(fd)
136 h.timeline = h.c.AllocateID()
137 registerProtocol(t, h.c, h.timeline)
138 if err := wireRequest(h.c, mgr, uint16(linuxdrmsyncobj.WpLinuxDrmSyncobjManagerV1RequestImportTimeline), []int{fd}, h.timeline); err != nil {
139 t.Fatal(err)
140 }
141 for deadline := time.Now().Add(2 * time.Second); ; {
142 var out string
143 s.display.Do(func() { out = s.frameOutput(s.windows[w.ID].xdg.surface) })
144 if out != "" {
145 break
146 }
147 if time.Now().After(deadline) {
148 t.Fatal("configure not applied")
149 }
150 time.Sleep(2 * time.Millisecond)
151 }
152 return h
153}
154
155// dmabuf makes a client dmabuf wl_buffer (a memfd stands in for it).
156func (h *syncHarness) dmabuf() uint32 {
157 t, c := h.t, h.c
158 dm := bindVersion(t, c, "zwp_linux_dmabuf_v1", dmabufVersion)
159 fd, err := unix.MemfdCreate("buf", 0)
160 if err != nil {
161 t.Fatal(err)
162 }
163 defer unix.Close(fd)
164 if err := unix.Ftruncate(fd, 256*16); err != nil {
165 t.Fatal(err)
166 }
167 p := c.AllocateID()
168 requestProtocol(t, c, dm, linuxdmabuf.ZwpLinuxDmabufV1RequestCreateParams, p)
169 registerProtocol(t, c, p)
170 if err := wireRequest(c, p, uint16(linuxdmabuf.ZwpLinuxBufferParamsV1RequestAdd), []int{fd}, uint32(0), uint32(0), uint32(256), uint32(0), uint32(0)); err != nil {
171 t.Fatal(err)
172 }
173 b := c.AllocateID()
174 rel := &syncReleaseProxy{released: make(chan struct{}, 4)}
175 rel.SetID(b)
176 registerWireProxy(c, rel)
177 requestProtocol(t, c, p, linuxdmabuf.ZwpLinuxBufferParamsV1RequestCreateImmed, b, int32(64), int32(16), linearARGB.Format, uint32(0))
178 h.releases[b] = rel
179 return b
180}
181
182type syncReleaseProxy struct {
183 wlturbo.BaseProxy
184 released chan struct{}
185}
186
187func (p *syncReleaseProxy) Dispatch(e *wlturbo.Event) {
188 if uint32(e.Opcode) == wayland.BufferEventRelease {
189 p.released <- struct{}{}
190 }
191}
192
193func (h *syncHarness) setPoints(acquire, release uint64) {
194 requestProtocol(h.t, h.c, h.syncSurf, linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1RequestSetAcquirePoint, h.timeline, uint32(acquire>>32), uint32(acquire))
195 requestProtocol(h.t, h.c, h.syncSurf, linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1RequestSetReleasePoint, h.timeline, uint32(release>>32), uint32(release))
196}
197
198func (h *syncHarness) commit(buf uint32, acquire, release uint64) {
199 t, c := h.t, h.c
200 requestProtocol(t, c, h.surf, wayland.SurfaceRequestAttach, buf, int32(0), int32(0))
201 h.setPoints(acquire, release)
202 requestProtocol(t, c, h.surf, wayland.SurfaceRequestCommit)
203 if err := c.Roundtrip(); err != nil {
204 t.Fatal(err)
205 }
206}
207
208// content waits for a DMABuf content of the window.
209func (h *syncHarness) content(wait time.Duration) (ports.SurfaceContent, bool) {
210 deadline := time.After(wait)
211 for {
212 select {
213 case c := <-h.contents:
214 if c.ID == h.win && c.DMABuf != nil {
215 return c, true
216 }
217 case <-deadline:
218 return ports.SurfaceContent{}, false
219 }
220 }
221}
222
223// fire makes the kernel's eventfd for an acquire point readable.
224func (h *syncHarness) fire(point uint64) {
225 h.t.Helper()
226 deadline := time.Now().Add(2 * time.Second)
227 for {
228 h.mu.Lock()
229 fd, ok := h.efds[point]
230 h.mu.Unlock()
231 if ok {
232 var one [8]byte
233 one[0] = 1
234 if _, err := unix.Write(fd, one[:]); err != nil {
235 h.t.Fatal(err)
236 }
237 return
238 }
239 if time.Now().After(deadline) {
240 h.t.Fatalf("no eventfd for point %d", point)
241 }
242 time.Sleep(2 * time.Millisecond)
243 }
244}
245
246func (h *syncHarness) signalled() []uint64 {
247 h.mu.Lock()
248 defer h.mu.Unlock()
249 return append([]uint64(nil), h.signals...)
250}
251
252// A commit waits for its acquire point; the content carries the fence;
253// replacing it signals the release point once outputs moved on, and
254// wl_buffer.release is never sent. The acquire fence closes only then.
255func TestSyncobjAcquireAndRelease(t *testing.T) {
256 h := newSyncHarness(t)
257 h.releases = map[uint32]*syncReleaseProxy{}
258 a, b := h.dmabuf(), h.dmabuf()
259 h.commit(a, 1, 2)
260 if _, ok := h.content(50 * time.Millisecond); ok {
261 t.Fatal("content before the acquire point had a fence")
262 }
263 h.fire(1)
264 first, ok := h.content(2 * time.Second)
265 if !ok || first.Acquire == nil {
266 t.Fatalf("content %+v", first)
267 }
268 h.commit(b, 3, 4)
269 h.fire(3)
270 second, ok := h.content(2 * time.Second)
271 if !ok {
272 t.Fatal("second content")
273 }
274 if _, err := first.Acquire.Stat(); err != nil {
275 t.Fatal("acquire fence closed before release")
276 }
277 // The output read the second content: the first buffer is released.
278 h.presented <- ports.OutputPresented{Output: "HEADLESS-1", Seen: map[ports.WindowID]uint64{h.win: second.Seq}}
279 deadline := time.Now().Add(2 * time.Second)
280 for len(h.signalled()) == 0 && time.Now().Before(deadline) {
281 if err := h.c.Roundtrip(); err != nil {
282 t.Fatal(err)
283 }
284 time.Sleep(2 * time.Millisecond)
285 }
286 if got := h.signalled(); len(got) != 1 || got[0] != 2 {
287 t.Fatalf("signalled %v", got)
288 }
289 if _, err := first.Acquire.Stat(); err == nil {
290 t.Fatal("acquire fence left open after release")
291 }
292 if err := h.c.Roundtrip(); err != nil {
293 t.Fatal(err)
294 }
295 select {
296 case <-h.releases[a].released:
297 t.Fatal("wl_buffer.release sent under explicit sync")
298 default:
299 }
300}
301
302// FIFO, commit timing and acquire are independent gates on the same ordered
303// update: clearing the first two cannot publish before its acquire fires.
304func TestQueuedCommitReadinessGatesInOrder(t *testing.T) {
305 h := newSyncHarness(t)
306 h.releases = map[uint32]*syncReleaseProxy{}
307 first, second := h.dmabuf(), h.dmabuf()
308 fm := bindProtocol(t, h.c, "wp_fifo_manager_v1")
309 f := h.c.AllocateID()
310 registerProtocol(t, h.c, f)
311 requestProtocol(t, h.c, fm, fifo.WpFifoManagerV1RequestGetFifo, f, h.surf)
312 tm := bindProtocol(t, h.c, "wp_commit_timing_manager_v1")
313 timer := h.c.AllocateID()
314 registerProtocol(t, h.c, timer)
315 requestProtocol(t, h.c, tm, committiming.WpCommitTimingManagerV1RequestGetTimer, timer, h.surf)
316
317 requestProtocol(t, h.c, f, fifo.WpFifoV1RequestSetBarrier)
318 h.commit(first, 1, 2)
319 h.fire(1)
320 one, ok := h.content(2 * time.Second)
321 if !ok {
322 t.Fatal("first update did not publish")
323 }
324 var now unix.Timespec
325 if err := unix.ClockGettime(unix.CLOCK_MONOTONIC, &now); err != nil {
326 t.Fatal(err)
327 }
328 target := time.Duration(now.Nano()) + 180*time.Millisecond
329 sec := uint64(target / time.Second)
330 requestProtocol(t, h.c, timer, committiming.WpCommitTimerV1RequestSetTimestamp, uint32(sec>>32), uint32(sec), uint32(target%time.Second))
331 requestProtocol(t, h.c, f, fifo.WpFifoV1RequestWaitBarrier)
332 h.commit(second, 3, 4)
333 // Even with an acquire fence available, timestamp and barrier hold it.
334 var waiting bool
335 h.s.display.Do(func() {
336 surf := h.s.windows[h.win].xdg.surface
337 waiting = len(surf.queue) == 1 && surf.barrier
338 })
339 if !waiting {
340 t.Fatal("update did not wait behind the FIFO barrier")
341 }
342 if _, ok := h.content(200 * time.Millisecond); ok {
343 t.Fatal("update published before its acquire fence")
344 }
345 var gatedByAcquire bool
346 h.s.display.Do(func() {
347 surf := h.s.windows[h.win].xdg.surface
348 gatedByAcquire = len(surf.queue) == 1 && !surf.barrier && !surf.tooEarly(surf.queue[0].at, time.Now()) && !surf.syncReady(surf.queue[0].sync)
349 })
350 if !gatedByAcquire {
351 t.Fatal("FIFO/timing did not clear while acquire still waited")
352 }
353 h.fire(3)
354 two, ok := h.content(2 * time.Second)
355 if !ok || two.Seq <= one.Seq || two.DMABuf.ID == one.DMABuf.ID {
356 t.Fatalf("ordered publication: first=%+v second=%+v", one, two)
357 }
358}
359
360// A destroyed queued buffer must not hold later commits behind its acquire.
361// Its release point cannot be advanced before the acquire is ready.
362func TestSyncobjDestroyedQueuedAcquire(t *testing.T) {
363 h := newSyncHarness(t)
364 h.releases = map[uint32]*syncReleaseProxy{}
365 first, dead, next := h.dmabuf(), h.dmabuf(), h.dmabuf()
366 h.commit(first, 1, 2)
367 h.fire(1)
368 if _, ok := h.content(2 * time.Second); !ok {
369 t.Fatal("initial content missing")
370 }
371 fm := bindProtocol(t, h.c, "wp_fifo_manager_v1")
372 f := h.c.AllocateID()
373 registerProtocol(t, h.c, f)
374 requestProtocol(t, h.c, fm, fifo.WpFifoManagerV1RequestGetFifo, f, h.surf)
375 requestProtocol(t, h.c, f, fifo.WpFifoV1RequestSetBarrier)
376 h.commit(dead, 3, 4)
377 requestProtocol(t, h.c, dead, wayland.BufferRequestDestroy)
378 if err := h.c.Roundtrip(); err != nil {
379 t.Fatal(err)
380 }
381 // The dead attachment is skipped; it does not replace the current one.
382 // A later attachment is queued while the old fence remains unsignalled.
383 h.commit(next, 5, 6)
384 h.fire(5)
385 got, ok := h.content(2 * time.Second)
386 if !ok || got.DMABuf == nil {
387 t.Fatalf("later content blocked: %+v", got)
388 }
389 for _, p := range h.signalled() {
390 if p == 4 {
391 t.Fatal("skipped buffer release signalled before acquire")
392 }
393 }
394}
395
396func TestSyncobjProtocolErrors(t *testing.T) {
397 for _, tc := range []struct {
398 name string
399 run func(h *syncHarness)
400 obj func(h *syncHarness) uint32
401 code uint32
402 }{
403 {"conflicting points", func(h *syncHarness) {
404 b := h.dmabuf()
405 requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestAttach, b, int32(0), int32(0))
406 requestProtocol(t, h.c, h.syncSurf, linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1RequestSetAcquirePoint, h.timeline, uint32(0), uint32(5))
407 requestProtocol(t, h.c, h.syncSurf, linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1RequestSetReleasePoint, h.timeline, uint32(0), uint32(5))
408 requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestCommit)
409 }, func(h *syncHarness) uint32 { return h.syncSurf }, uint32(linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1ErrorConflictingPoints)},
410 {"no release point", func(h *syncHarness) {
411 b := h.dmabuf()
412 requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestAttach, b, int32(0), int32(0))
413 requestProtocol(t, h.c, h.syncSurf, linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1RequestSetAcquirePoint, h.timeline, uint32(0), uint32(5))
414 requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestCommit)
415 }, func(h *syncHarness) uint32 { return h.syncSurf }, uint32(linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1ErrorNoReleasePoint)},
416 {"buffer without points", func(h *syncHarness) {
417 b := h.dmabuf()
418 requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestAttach, b, int32(0), int32(0))
419 requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestCommit)
420 }, func(h *syncHarness) uint32 { return h.syncSurf }, uint32(linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1ErrorNoAcquirePoint)},
421 {"no acquire point", func(h *syncHarness) {
422 b := h.dmabuf()
423 requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestAttach, b, int32(0), int32(0))
424 requestProtocol(t, h.c, h.syncSurf, linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1RequestSetReleasePoint, h.timeline, uint32(0), uint32(5))
425 requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestCommit)
426 }, func(h *syncHarness) uint32 { return h.syncSurf }, uint32(linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1ErrorNoAcquirePoint)},
427 {"points without attach", func(h *syncHarness) {
428 b := h.dmabuf()
429 h.commit(b, 1, 2)
430 h.fire(1)
431 h.setPoints(3, 4)
432 requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestCommit)
433 }, func(h *syncHarness) uint32 { return h.syncSurf }, uint32(linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1ErrorNoBuffer)},
434 {"points with null attach", func(h *syncHarness) {
435 requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestAttach, uint32(0), int32(0), int32(0))
436 h.setPoints(1, 2)
437 requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestCommit)
438 }, func(h *syncHarness) uint32 { return h.syncSurf }, uint32(linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1ErrorNoBuffer)},
439 {"wl_shm buffer", func(h *syncHarness) {
440 b := shmBuffer(t, h.c)
441 requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestAttach, b, int32(0), int32(0))
442 h.setPoints(1, 2)
443 requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestCommit)
444 }, func(h *syncHarness) uint32 { return h.syncSurf }, uint32(linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1ErrorUnsupportedBuffer)},
445 {"surface destroyed", func(h *syncHarness) {
446 requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestDestroy)
447 h.setPoints(1, 2)
448 }, func(h *syncHarness) uint32 { return h.syncSurf }, uint32(linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1ErrorNoSurface)},
449 {"surface exists", func(h *syncHarness) {
450 mgr := bindProtocol(t, h.c, "wp_linux_drm_syncobj_manager_v1")
451 h.mgr = mgr
452 requestProtocol(t, h.c, mgr, linuxdrmsyncobj.WpLinuxDrmSyncobjManagerV1RequestGetSurface, h.c.AllocateID(), h.surf)
453 }, func(h *syncHarness) uint32 { return h.mgr }, uint32(linuxdrmsyncobj.WpLinuxDrmSyncobjManagerV1ErrorSurfaceExists)},
454 } {
455 t.Run(tc.name, func(t *testing.T) {
456 h := newSyncHarness(t)
457 h.releases = map[uint32]*syncReleaseProxy{}
458 h.t = t
459 tc.run(h)
460 expectProtocolError(t, h.c, tc.obj(h), tc.code)
461 })
462 }
463}
464
465// An invalid timeline fd is a protocol error.
466func TestSyncobjInvalidTimeline(t *testing.T) {
467 dev := newMocksyncobjDevice(t)
468 dev.EXPECT().fdToHandle(mock.Anything).Return(0, unix.EINVAL).Once()
469 dir := t.TempDir()
470 s, err := New(Options{RuntimeDir: dir, Outputs: testOutputs, syncDev: dev}, Channels{}, logging.For(context.Background(), "wayland"))
471 if err != nil {
472 t.Fatal(err)
473 }
474 ctx, cancel := context.WithCancel(context.Background())
475 done := make(chan error, 1)
476 go func() { done <- s.Run(ctx) }()
477 t.Cleanup(func() { cancel(); <-done })
478 c := protocolClient(t, s, dir)
479 mgr := bindProtocol(t, c, "wp_linux_drm_syncobj_manager_v1")
480 fd, _ := unix.MemfdCreate("bad", 0)
481 defer unix.Close(fd)
482 if err := wireRequest(c, mgr, uint16(linuxdrmsyncobj.WpLinuxDrmSyncobjManagerV1RequestImportTimeline), []int{fd}, c.AllocateID()); err != nil {
483 t.Fatal(err)
484 }
485 expectProtocolError(t, c, mgr, uint32(linuxdrmsyncobj.WpLinuxDrmSyncobjManagerV1ErrorInvalidTimeline))
486}
487
488// A synchronized subsurface dropped before its acquire is ready must
489// retire its release point without sending wl_buffer.release.
490func TestSyncobjDroppedSubsurfaceCommit(t *testing.T) {
491 h := newSyncHarness(t)
492 h.releases = map[uint32]*syncReleaseProxy{}
493 c := h.c
494 comp := bindProtocol(t, c, "wl_compositor")
495 subc := bindProtocol(t, c, "wl_subcompositor")
496 mgr := bindProtocol(t, c, "wp_linux_drm_syncobj_manager_v1")
497 child := c.AllocateID()
498 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, child)
499 registerProtocol(t, c, child)
500 sub := c.AllocateID()
501 requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, sub, child, h.surf)
502 registerProtocol(t, c, sub)
503 syncChild := c.AllocateID()
504 requestProtocol(t, c, mgr, linuxdrmsyncobj.WpLinuxDrmSyncobjManagerV1RequestGetSurface, syncChild, child)
505 registerProtocol(t, c, syncChild)
506 b := h.dmabuf()
507 requestProtocol(t, c, child, wayland.SurfaceRequestAttach, b, int32(0), int32(0))
508 requestProtocol(t, c, syncChild, linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1RequestSetAcquirePoint, h.timeline, uint32(0), uint32(21))
509 requestProtocol(t, c, syncChild, linuxdrmsyncobj.WpLinuxDrmSyncobjSurfaceV1RequestSetReleasePoint, h.timeline, uint32(0), uint32(22))
510 requestProtocol(t, c, child, wayland.SurfaceRequestCommit)
511 requestProtocol(t, c, sub, wayland.SubsurfaceRequestDestroy)
512 if err := c.Roundtrip(); err != nil {
513 t.Fatal(err)
514 }
515 h.fire(21) // dropping before acquire means the later signal must not release
516 if err := c.Roundtrip(); err != nil {
517 t.Fatal(err)
518 }
519 if got := h.signalled(); len(got) != 0 {
520 t.Fatalf("premature release point: %v", got)
521 }
522 select {
523 case <-h.releases[b].released:
524 t.Fatal("explicit sync sent wl_buffer.release")
525 default:
526 }
527}
528
529// A commit dropped before its acquire point has a fence signals nothing
530// (the client has not submitted that point) and sends no wl_buffer.release;
531// the waiter closes its eventfd and no acquire file is left open.
532func TestSyncobjDroppedCommit(t *testing.T) {
533 h := newSyncHarness(t)
534 h.releases = map[uint32]*syncReleaseProxy{}
535 a := h.dmabuf()
536 before := openFDs(t)
537 h.commit(a, 1, 2)
538 requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestDestroy)
539 if err := h.c.Roundtrip(); err != nil {
540 t.Fatal(err)
541 }
542 // The point never gets a fence: the waiter must still stop polling it.
543 time.Sleep(250 * time.Millisecond)
544 if err := h.c.Roundtrip(); err != nil {
545 t.Fatal(err)
546 }
547 if got := h.signalled(); len(got) != 0 {
548 t.Fatalf("signalled %v for a commit whose acquire point never arrived", got)
549 }
550 select {
551 case <-h.releases[a].released:
552 t.Fatal("wl_buffer.release sent under explicit sync")
553 default:
554 }
555 // The harness keeps its own duplicate of the eventfd.
556 if after := openFDs(t); after > before+1 {
557 t.Fatalf("eventfds and sync files %d -> %d", before, after)
558 }
559}
560
561// A commit dropped after its acquire point fired signals its release point.
562func TestSyncobjDroppedReadyCommitReleases(t *testing.T) {
563 h := newSyncHarness(t)
564 h.releases = map[uint32]*syncReleaseProxy{}
565 a := h.dmabuf()
566 h.commit(a, 1, 2)
567 h.fire(1)
568 if _, ok := h.content(2 * time.Second); !ok {
569 t.Fatal("content")
570 }
571 requestProtocol(t, h.c, h.surf, wayland.SurfaceRequestDestroy)
572 deadline := time.Now().Add(2 * time.Second)
573 for len(h.signalled()) == 0 && time.Now().Before(deadline) {
574 if err := h.c.Roundtrip(); err != nil {
575 t.Fatal(err)
576 }
577 time.Sleep(2 * time.Millisecond)
578 }
579 if got := h.signalled(); len(got) != 1 || got[0] != 2 {
580 t.Fatalf("signalled %v", got)
581 }
582}
583
584// openFDs counts the eventfds and sync files (pipes stand in for them)
585// the process holds.
586func openFDs(t *testing.T) int {
587 t.Helper()
588 ents, err := os.ReadDir("/proc/self/fd")
589 if err != nil {
590 t.Fatal(err)
591 }
592 n := 0
593 for _, e := range ents {
594 l, _ := os.Readlink("/proc/self/fd/" + e.Name())
595 if strings.Contains(l, "eventfd") || strings.HasPrefix(l, "pipe:") {
596 n++
597 }
598 }
599 return n
600}