app.dhover×808 error×104all
// F17 Wayland demo — threading probes: exercise (and deliberately violate)
// libwayland's threading model and record exactly what happens
// (../../../features/f17-threading.md; findings in ../../f17-threading.md).
//
// libwayland-client documents wl_display as thread-safe: any thread may issue
// requests, and events are routed to wl_event_queue objects that threads
// dispatch independently (wl_display_create_queue / wl_proxy_set_queue /
// wl_display_dispatch_queue). The read-intent protocol
// (wl_display_prepare_read_queue → read_events|cancel_read) serializes the
// one socket read among any number of reader threads. These probes measure
// that story instead of trusting it.
//
// Probes (--probe=N; no argument = fork+run every probe TWICE, the CI
// default, so race-dependent outcomes show their spread):
//   1  the whole window — registry binds, surface tree, first commit, frame
//      dispatch — built on a WORKER thread against a wl_display that the
//      main thread connected, while main sleeps (no main-thread rule?)
//   2  TWO threads both calling wl_display_dispatch on the same default
//      queue concurrently while frame callbacks keep events flowing — the
//      documented multi-reader shape; how do events distribute?
//   3  the designed pattern: a worker-owned wl_event_queue. The worker makes
//      a wl_proxy_create_wrapper of the display, wl_proxy_set_queue's it,
//      chains 50 wl_display.sync callbacks through its own queue via
//      wl_display_dispatch_queue — while main dispatches the default queue
//      and renders. Prove both run concurrently.
//   4  render thread: a worker paints the wl_shm buffer and issues
//      attach/damage/frame/commit (+ its own wl_display_flush) on the SHARED
//      wl_surface proxy for 100 frames while main dispatches; watch for
//      protocol errors / corruption.
//   5  one wl_display connection per thread — the X11 display-per-thread
//      analog; trivially safe, prove it.
//   6  read-intent protocol violated: thread A holds a successful
//      wl_display_prepare_read while thread B calls wl_display_read_events
//      WITHOUT its own prepare; then a health roundtrip. Timeboxed.
//
// Every probe ends in a verdict line
//     probe n=<N> result=ok|error|crash|deadlock|silent detail=...
// that survives ANY outcome: wl_display_get_error is checked after every
// probe, SIGSEGV/SIGABRT/SIGBUS handlers turn crashes into a flushed verdict
// + _exit(0), and a SIGALRM watchdog turns hangs into result=deadlock.
// Crash probes still exit 0 — crashing is their job.
//
// All probes are self-bounded (frame counts + wall-clock caps + the
// watchdog); WSI_AUTO_EXIT=1 is accepted for uniformity with the other
// Wayland demos but changes nothing.
//
// Headless-safe: no compositor -> prints `SKIP:` and exits 0.
module 
(module) app
app
;
import c; // ImportC: <wayland-client.h> + xdg-shell glue + wsi_* wrappers
unable to read module `c` Expected 'c.d' or 'c/package.d' in one of the following import paths:
unable to read module `c` Expected 'c.d' or 'c/package.d' in one of the following import paths:
import instrument;
unable to read module `instrument` Expected 'instrument.d' or 'instrument/package.d' in one of the following import paths:
unable to read module `instrument` Expected 'instrument.d' or 'instrument/package.d' in one of the following import paths:
import
(package) core
core
.
(module) core.atomic

The atomic module provides basic support for lock-free concurrent programming.

Use the -preview=nosharedaccess compiler flag to detect unsafe individual read or write operations on shared data.

Source

core/atomic.d

Examples

int y = 2;
shared int x = y; // OK

//x++; // read modify write error
x.atomicOp!"+="(1); // OK
//y = x; // read error with preview flag
y = x.atomicLoad(); // OK
assert(y == 3);
//x = 5; // write error with preview flag
x.atomicStore(5); // OK
assert(x.atomicLoad() == 5);
@copyrightCopyright Sean Kelly 2005 - 2016.@licenseBoost License 1.0@authorsSean Kelly, Alex Rønne Petersen, Manu Evans
atomic
:
(alias template) app.atomicLoad = core.atomic.atomicLoad(MemoryOrder ms = MemoryOrder.seq, T)(auto ref return scope const T val) if (!is(T == shared(U), U) && !is(T == shared(inout(U)), U) && !is(T == shared(const(U)), U))

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
,
(alias template) app.atomicOp = core.atomic.atomicOp(string op, T, V1)(ref shared T val, V1 mod) if (__traits(compiles, mixin("*cast(T*)&val" ~ op ~ "mod")))

Performs the binary operation 'op' on val using 'mod' as the modifier.

@paramval The target variable.@parammod The modifier to apply.@returnsThe result of the operation.
atomicOp
,
(alias template) app.atomicStore = core.atomic.atomicStore(MemoryOrder ms = MemoryOrder.seq, T, V)(ref T val, V newval) if (!is(T == shared) && !is(V == shared))

Writes 'newval' into 'val'. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.rel, and MemoryOrder.seq.

@paramval The target variable.@paramnewval The value to store.
atomicStore
;
import
(package) core
core
.
(package) core.stdc
stdc
.
(module) core.stdc.stdio

D header file for C99 <stdio.h>

pubs.opengroup.org/onlinepubs/009695399/basedefs/stdio.h.html, stdio.h

Source

core/stdc/stdio.d

@copyrightCopyright Sean Kelly 2005 - 2009.@licenseDistributed under the Boost Software License 1.0. (See accompanying file LICENSE)@authorsSean Kelly, Alex Rønne Petersen@standardsISO/IEC 9899:1999 (E)
stdio
:
(alias) app.printf = int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogc
printf
,
(alias) app.snprintf = int core.stdc.stdio.snprintf(scope char* s, ulong n, scope const(char*) format, scope const ...) nothrow @nogc
snprintf
;
import
(package) core
core
.
(package) core.stdc
stdc
.
(module) core.stdc.string

D header file for C99.

pubs.opengroup.org/onlinepubs/009695399/basedefs/string.h.html, string.h

Source

core/stdc/string.d

@copyrightCopyright Sean Kelly 2005 - 2009.@licenseDistributed under the Boost Software License 1.0. (See accompanying file LICENSE)@authorsSean Kelly@standardsISO/IEC 9899:1999 (E)
string
:
(alias) app.strcmp = int core.stdc.string.strcmp(scope const(char*) s1, scope const(char*) s2) pure nothrow @nogc
strcmp
;
// glibc's <pthread.h> is not ImportC-able (linux/types.h __int128), so the // thread API comes from druntime's POSIX declarations (see f05-loop-wakeup). import
(package) core
core
.
(package) core.sys
sys
.
(package) core.sys.posix
posix
.
(module) core.sys.posix.pthread

D header file for POSIX.

@copyrightCopyright Sean Kelly 2005 - 2009.@licenseBoost License 1.0.@authorsSean Kelly, Alex Rønne Petersen@standardsThe Open Group Base Specifications Issue 6, IEEE Std 1003.1, 2004 Edition
pthread
:
(alias) app.pthread_create = int core.sys.posix.pthread.pthread_create(ulong*, scope const(core.sys.posix.sys.types.pthread_attr_t*), extern (C) void* function(void*), void*) nothrow @nogc
pthread_create
,
(alias) app.pthread_join = int core.sys.posix.pthread.pthread_join(ulong, void**) nothrow @nogc
pthread_join
,
(alias) app.pthread_self = ulong core.sys.posix.pthread.pthread_self() nothrow @nogc
pthread_self
, pthread_t;
import
(package) core
core
.
(package) core.sys
sys
.
(package) core.sys.posix
posix
.
(module) core.sys.posix.signal

D header file for POSIX.

Source

core/sys/posix/signal.d

@copyrightCopyright Sean Kelly 2005 - 2009.@licenseBoost License 1.0@authorsSean Kelly, Alex Rønne Petersen@standardsThe Open Group Base Specifications Issue 6, IEEE Std 1003.1, 2004 Edition
signal
:
(alias) app.sigaction = int core.sys.posix.signal.sigaction(int, scope const(core.sys.posix.signal.sigaction_t*), core.sys.posix.signal.sigaction_t*) nothrow @nogc
sigaction
,
(struct) core.sys.posix.signal.sigaction_t
sigaction_t
,
(alias) app.sigemptyset = int core.sys.posix.signal.sigemptyset(core.sys.posix.signal.sigset_t*) nothrow @nogc
sigemptyset
,
(alias constant) app.SIGABRT = int core.stdc.signal.SIGABRT = 6
SIGABRT
,
(alias constant) app.SIGALRM = int core.sys.posix.signal.SIGALRM = 14
SIGALRM
,
(alias constant) app.SIGBUS = int core.sys.posix.signal.SIGBUS = 7
SIGBUS
,
(alias constant) app.SIGSEGV = int core.stdc.signal.SIGSEGV = 11
SIGSEGV
;
import
(package) core
core
.
(package) core.sys
sys
.
(package) core.sys.posix
posix
.
(package) core.sys.posix.sys
sys
.
(module) core.sys.posix.sys.wait

D header file for POSIX.

@copyrightCopyright Sean Kelly 2005 - 2009.@licenseBoost License 1.0.@authorsSean Kelly, Alex Rønne Petersen@standardsThe Open Group Base Specifications Issue 6, IEEE Std 1003.1, 2004 Edition
wait
:
(alias) app.waitpid = int core.sys.posix.sys.wait.waitpid(int, int*, int) nothrow @nogc
waitpid
;
import
(package) core
core
.
(package) core.sys
sys
.
(package) core.sys.posix
posix
.
(module) core.sys.posix.unistd

D header file for POSIX.

@copyrightCopyright Sean Kelly 2005 - 2009.@licenseBoost License 1.0.@authorsSean Kelly@standardsThe Open Group Base Specifications Issue 8, IEEE Std 1003.1, 2024 Edition
unistd
:
(alias) app._exit = noreturn core.sys.posix.unistd._exit(int) nothrow @nogc @trusted
_exit
,
(alias) app.alarm = uint core.sys.posix.unistd.alarm(uint) nothrow @nogc @trusted
alarm
,
(alias) app.fork = int core.sys.posix.unistd.fork() nothrow @nogc @trusted
fork
,
(alias) app.usleep = int core.sys.posix.unistd.usleep(uint) nothrow @nogc @trusted
usleep
,
(alias) app.write = long core.sys.posix.unistd.write(int, scope const(void*), ulong) nothrow @nogc
write
;
enum int
(constant) int app.defaultWidth = 640
defaultWidth
= 640;
enum int
(constant) int app.defaultHeight = 480
defaultHeight
= 480;
// -- verdict plumbing: must survive crashes ------------------------------------ private __gshared int
(__gshared global) int app.g_probe
g_probe
;
private shared bool
(shared global) shared(bool) app.g_verdictDone
g_verdictDone
;
private __gshared const(char)*
(__gshared global) const(char)* app.g_watchdogDetail
g_watchdogDetail
= "watchdog_timeout_12s";
/// The one line the spec requires per run. Async-signal-safe on purpose /// (snprintf into a static buffer + write(2)) so the signal handlers can /// call it; the normal path uses it too so the format is identical. void
void app.verdict(scope const(char)* result, scope const(char)* detail) nothrow @nogc

The one line the spec requires per run. Async-signal-safe on purpose (snprintf into a static buffer + write(2)) so the signal handlers can call it; the normal path uses it too so the format is identical.

verdict
(scope const(char)*
(parameter) const(char)* result
result
, scope const(char)*
(parameter) const(char)* detail
detail
) @nogc nothrow
{ if (
bool core.atomic.atomicLoad!(MemoryOrder.seq, bool)(ref return scope shared(const(bool)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(bool) app.g_verdictDone
g_verdictDone
))
return;
void core.atomic.atomicStore!(MemoryOrder.seq, bool, bool)(ref shared(bool) val, bool newval) pure nothrow @nogc @trusted

Writes 'newval' into 'val'. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.rel, and MemoryOrder.seq.

@paramval The target variable.@paramnewval The value to store.
atomicStore
(
(shared global) shared(bool) app.g_verdictDone
g_verdictDone
, true);
static __gshared char[320]
(__gshared global) char[320] app.verdict.buf
buf
;
const
(local variable) const(_error_) n
n
=
int core.stdc.stdio.snprintf(scope char* s, ulong n, scope const(char*) format, scope const ...) nothrow @nogc
snprintf
(
(__gshared global) char[320] app.verdict.buf
buf
.
(constant) char* char[320].ptr = &buf
ptr
,
(__gshared global) char[320] app.verdict.buf
buf
.
(constant) ulong char[320].length = 320LU
length
, "%lld f17_wayland probe n=%d result=%s detail=%s\n",
nowUs(),
(__gshared global) int app.g_probe
g_probe
,
(parameter) const(char)* result
result
,
(parameter) const(char)* detail
detail
);
undefined identifier `nowUs`
long core.sys.posix.unistd.write(int, scope const(void*), ulong) nothrow @nogc
write
(2,
(__gshared global) char[320] app.verdict.buf
buf
.
(constant) char* char[320].ptr = &buf
ptr
, n);
} extern (C) void
void app.signalHandler(int sig) nothrow @nogc
signalHandler
(int
(parameter) int sig
sig
) @nogc nothrow
{ static __gshared char[64]
(__gshared global) char[64] app.signalHandler.d
d
;
int core.stdc.stdio.snprintf(scope char* s, ulong n, scope const(char*) format, scope const ...) nothrow @nogc
snprintf
(
(__gshared global) char[64] app.signalHandler.d
d
.
(constant) char* char[64].ptr = &d
ptr
,
(__gshared global) char[64] app.signalHandler.d
d
.
(constant) ulong char[64].length = 64LU
length
, "fatal_signal=%d",
(parameter) int sig
sig
);
void app.verdict(scope const(char)* result, scope const(char)* detail) nothrow @nogc

The one line the spec requires per run. Async-signal-safe on purpose (snprintf into a static buffer + write(2)) so the signal handlers can call it; the normal path uses it too so the format is identical.

verdict
("crash",
(__gshared global) char[64] app.signalHandler.d
d
.
(constant) char* char[64].ptr = &d
ptr
);
noreturn core.sys.posix.unistd._exit(int) nothrow @nogc @trusted
_exit
(0);
} extern (C) void
void app.alarmHandler(int __param_0) nothrow @nogc
alarmHandler
(int) @nogc nothrow
{
void app.verdict(scope const(char)* result, scope const(char)* detail) nothrow @nogc

The one line the spec requires per run. Async-signal-safe on purpose (snprintf into a static buffer + write(2)) so the signal handlers can call it; the normal path uses it too so the format is identical.

verdict
("deadlock",
(__gshared global) const(char)* app.g_watchdogDetail
g_watchdogDetail
);
noreturn core.sys.posix.unistd._exit(int) nothrow @nogc @trusted
_exit
(0);
} void
void app.installCrashHandlers() nothrow @nogc
installCrashHandlers
() @nogc nothrow
{
(struct) core.sys.posix.signal.sigaction_t
sigaction_t
(local variable) core.sys.posix.signal.sigaction_t sa
sa
;
(local variable) core.sys.posix.signal.sigaction_t sa
sa
.
(field) extern (C) void function(int) core.sys.posix.signal.sigaction_t.sa_handler
sa_handler
= &
void app.signalHandler(int sig) nothrow @nogc
signalHandler
;
int core.sys.posix.signal.sigemptyset(core.sys.posix.signal.sigset_t*) nothrow @nogc
sigemptyset
(&
(local variable) core.sys.posix.signal.sigaction_t sa
sa
.
(field) core.sys.posix.signal.sigset_t core.sys.posix.signal.sigaction_t.sa_mask
sa_mask
);
(local variable) core.sys.posix.signal.sigaction_t sa
sa
.
(field) int core.sys.posix.signal.sigaction_t.sa_flags
sa_flags
= 0;
int core.sys.posix.signal.sigaction(int, scope const(core.sys.posix.signal.sigaction_t*), core.sys.posix.signal.sigaction_t*) nothrow @nogc
sigaction
(
(constant) int core.stdc.signal.SIGSEGV = 11
SIGSEGV
, &
(local variable) core.sys.posix.signal.sigaction_t sa
sa
, null);
int core.sys.posix.signal.sigaction(int, scope const(core.sys.posix.signal.sigaction_t*), core.sys.posix.signal.sigaction_t*) nothrow @nogc
sigaction
(
(constant) int core.sys.posix.signal.SIGBUS = 7
SIGBUS
, &
(local variable) core.sys.posix.signal.sigaction_t sa
sa
, null);
int core.sys.posix.signal.sigaction(int, scope const(core.sys.posix.signal.sigaction_t*), core.sys.posix.signal.sigaction_t*) nothrow @nogc
sigaction
(
(constant) int core.stdc.signal.SIGABRT = 6
SIGABRT
, &
(local variable) core.sys.posix.signal.sigaction_t sa
sa
, null);
(local variable) core.sys.posix.signal.sigaction_t sa
sa
.
(field) extern (C) void function(int) core.sys.posix.signal.sigaction_t.sa_handler
sa_handler
= &
void app.alarmHandler(int __param_0) nothrow @nogc
alarmHandler
;
int core.sys.posix.signal.sigaction(int, scope const(core.sys.posix.signal.sigaction_t*), core.sys.posix.signal.sigaction_t*) nothrow @nogc
sigaction
(
(constant) int core.sys.posix.signal.SIGALRM = 14
SIGALRM
, &
(local variable) core.sys.posix.signal.sigaction_t sa
sa
, null);
uint core.sys.posix.unistd.alarm(uint) nothrow @nogc @trusted
alarm
(12); // the deadlock watchdog
} // -- window machinery (the scaffold, re-parameterized on a Ctx*) --------------- /// One wl_shm ARGB8888 buffer. `busy` is owned by the compositor between /// wl_surface.commit and wl_buffer.release — and in probe 4 it crosses /// threads (release lands on the dispatching main thread, the render worker /// polls it), hence atomic. struct
(struct) app.Buffer

One wl_shm ARGB8888 buffer. busy is owned by the compositor between wl_surface.commit and wl_buffer.release — and in probe 4 it crosses threads (release lands on the dispatching main thread, the render worker polls it), hence atomic.

Buffer
{ wl_buffer*
(field) _error_ app.Buffer.handle
handle
;
undefined identifier `wl_buffer`
uint*
(field) uint* app.Buffer.pixels
pixels
;
(alias) object.size_t = ulong
size_t
(field) ulong app.Buffer.byteSize
byteSize
;
int
(field) int app.Buffer.width
width
,
(field) int app.Buffer.height
height
;
shared bool
(field) shared(bool) app.Buffer.busy
busy
;
} /// Everything one connection-plus-window owns. Probes share one global Ctx, /// except probe 5, which gives each thread its own. struct
(struct) app.Ctx

Everything one connection-plus-window owns. Probes share one global Ctx, except probe 5, which gives each thread its own.

Ctx
{ const(char)*
(field) const(char)* app.Ctx.tag
tag
= "main";
wl_display*
(field) _error_ app.Ctx.display
display
;
undefined identifier `wl_display`
wl_registry*
(field) _error_ app.Ctx.registry
registry
;
undefined identifier `wl_registry`
wl_compositor*
(field) _error_ app.Ctx.compositor
compositor
;
undefined identifier `wl_compositor`
wl_shm*
(field) _error_ app.Ctx.shm
shm
;
undefined identifier `wl_shm`
xdg_wm_base*
(field) _error_ app.Ctx.wmBase
wmBase
;
undefined identifier `xdg_wm_base`
wl_surface*
(field) _error_ app.Ctx.surface
surface
;
undefined identifier `wl_surface`
xdg_surface*
(field) _error_ app.Ctx.xdgSurface
xdgSurface
;
undefined identifier `xdg_surface`, did you mean variable `xdgSurface`?
xdg_toplevel*
(field) _error_ app.Ctx.toplevel
toplevel
;
undefined identifier `xdg_toplevel`
wl_callback*
(field) _error_ app.Ctx.frameCb
frameCb
;
undefined identifier `wl_callback`
(struct) app.Buffer

One wl_shm ARGB8888 buffer. busy is owned by the compositor between wl_surface.commit and wl_buffer.release — and in probe 4 it crosses threads (release lands on the dispatching main thread, the render worker polls it), hence atomic.

Buffer
[2]
(field) _error_ app.Ctx.buffers
buffers
;
int
(field) int app.Ctx.width
width
=
(constant) int app.defaultWidth = 640
defaultWidth
;
int
(field) int app.Ctx.height
height
=
(constant) int app.defaultHeight = 480
defaultHeight
;
int
(field) int app.Ctx.pendingW
pendingW
,
(field) int app.Ctx.pendingH
pendingH
;
bool
(field) bool app.Ctx.configured
configured
;
bool
(field) bool app.Ctx.autoRender
autoRender
= true; // frame callback re-renders (probes 1,2,3,5)
shared bool
(field) shared(bool) app.Ctx.running
running
= true;
shared int
(field) shared(int) app.Ctx.frames
frames
; // frame callbacks received (any thread)
int
(field) int app.Ctx.commits
commits
;
} bool
app.ensureBuffer
ensureBuffer
(
(struct) app.Ctx

Everything one connection-plus-window owns. Probes share one global Ctx, except probe 5, which gives each thread its own.

Ctx
*
(parameter) Ctx* ctx
ctx
, ref
(struct) app.Buffer

One wl_shm ARGB8888 buffer. busy is owned by the compositor between wl_surface.commit and wl_buffer.release — and in probe 4 it crosses threads (release lands on the dispatching main thread, the render worker polls it), hence atomic.

Buffer
(parameter) Buffer b
b
, int w, int h) @nogc nothrow
{ if (b.
b.handle
handle
!is null && (b.
b.width
width
!= w || b.
b.height
height
!= h))
{ wsi_buffer_destroy(b.
b.handle
handle
);
munmap(b.
b.pixels
pixels
, b.
b.byteSize
byteSize
);
b = Buffer.init; } if (b.
b.handle
handle
!is null)
return true; immutable
_error_ stride
stride
= w * 4;
immutable
_error_ size
size
= cast(
(unresolved type) size_t
size_t
) stride * h;
immutable
_error_ fd
fd
= memfd_create("wsi-f17", MFD_CLOEXEC);
if (fd < 0) return false; if (ftruncate(fd, cast(long) size) != 0) { close(fd); return false; } void*
_error_ mem
mem
= mmap(null, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (mem is cast(void*)-1) { close(fd); return false; } wl_shm_pool*
_error_ pool
pool
= wsi_shm_create_pool(ctx.
ctx.shm
shm
, fd, cast(int) size);
b.
b.handle
handle
= wsi_shm_pool_create_buffer(pool, 0, w, h, stride, WL_SHM_FORMAT_ARGB8888);
wsi_shm_pool_destroy(pool); close(fd); wsi_buffer_add_listener(b.
b.handle
handle
, &g_bufferListener, &b);
b.
b.pixels
pixels
= cast(uint*) mem;
b.
b.byteSize
byteSize
= size;
b.
b.width
width
= w;
b.
b.height
height
= h;
atomicStore(b.
b.busy
busy
, false);
return true; } /// Solid fill keyed to the frame counter — cheap, and each redraw observable. void
app.paint

Solid fill keyed to the frame counter — cheap, and each redraw observable.

paint
(ref
(struct) app.Buffer

One wl_shm ARGB8888 buffer. busy is owned by the compositor between wl_surface.commit and wl_buffer.release — and in probe 4 it crosses threads (release lands on the dispatching main thread, the render worker polls it), hence atomic.

Buffer
(parameter) Buffer b
b
, int
(parameter) int frame
frame
) @nogc nothrow
{ immutable uint
_error_ color
color
= 0xff00_0000 | ((frame * 2 & 0xff) << 16)
| ((255 - (frame * 2 & 0xff)) << 8) | 0x40; foreach (
(parameter) i
i
; 0 .. cast(
(unresolved type) size_t
size_t
) b.
b.width
width
* b.
b.height
height
)
b.
b.pixels
pixels
[i] = color;
}
(struct) app.Buffer

One wl_shm ARGB8888 buffer. busy is owned by the compositor between wl_surface.commit and wl_buffer.release — and in probe 4 it crosses threads (release lands on the dispatching main thread, the render worker polls it), hence atomic.

Buffer
*
app.freeBuffer
freeBuffer
(
(struct) app.Ctx

Everything one connection-plus-window owns. Probes share one global Ctx, except probe 5, which gives each thread its own.

Ctx
*
(parameter) Ctx* ctx
ctx
) @nogc nothrow
{ foreach (ref
(parameter) b
b
; ctx.
ctx.buffers
buffers
)
if (!atomicLoad(b.
b.busy
busy
))
return &b; return null; } /// Paint + attach/damage/commit (+ at most one frame callback in flight). /// Runs on whichever thread the probe says — that is the experiment. bool
app.render

Paint + attach/damage/commit (+ at most one frame callback in flight). Runs on whichever thread the probe says — that is the experiment.

render
(
(struct) app.Ctx

Everything one connection-plus-window owns. Probes share one global Ctx, except probe 5, which gives each thread its own.

Ctx
*
(parameter) Ctx* ctx
ctx
) @nogc nothrow
{
(unresolved type) Buffer
Buffer
*
_error_ buf
buf
= freeBuffer(ctx);
if (buf is null) return true; // both held by the compositor; release will free one if (!ensureBuffer(ctx, *buf, ctx.
ctx.width
width
, ctx.
ctx.height
height
))
return false; paint(*buf, atomicLoad(ctx.
ctx.frames
frames
));
wsi_surface_attach(ctx.
ctx.surface
surface
, buf.
buf.handle
handle
, 0, 0);
wsi_surface_damage_buffer(ctx.
ctx.surface
surface
, 0, 0, buf.
buf.width
width
, buf.
buf.height
height
);
if (ctx.
ctx.frameCb
frameCb
is null)
{ ctx.
ctx.frameCb
frameCb
= wsi_surface_frame(ctx.
ctx.surface
surface
);
wsi_callback_add_listener(ctx.
ctx.frameCb
frameCb
, &g_frameListener, ctx);
} wsi_surface_commit(ctx.
ctx.surface
surface
);
atomicStore(buf.
buf.busy
busy
, true);
ctx.
ctx.commits
commits
++;
return true; } extern (C) void
app.onGlobal
onGlobal
(void* data, wl_registry* reg, uint name,
undefined identifier `wl_registry`
const(char)* iface, uint ver) @nogc nothrow { auto
_error_ ctx
ctx
= cast(
(unresolved type) Ctx
Ctx
*) data;
if (strcmp(iface, wl_compositor_interface.name) == 0) ctx.
ctx.compositor
compositor
= cast(wl_compositor*) wsi_registry_bind(reg, name,
&wl_compositor_interface, ver < 4 ? ver : 4); else if (strcmp(iface, wl_shm_interface.name) == 0) ctx.
ctx.shm
shm
= cast(wl_shm*) wsi_registry_bind(reg, name, &wl_shm_interface, 1);
else if (strcmp(iface, xdg_wm_base_interface.name) == 0) ctx.
ctx.wmBase
wmBase
= cast(xdg_wm_base*) wsi_registry_bind(reg, name, &xdg_wm_base_interface, 1);
} extern (C) void
app.onGlobalRemove
onGlobalRemove
(void* data, wl_registry* reg, uint name) @nogc nothrow
undefined identifier `wl_registry`
{ } extern (C) void
app.onWmBasePing
onWmBasePing
(void* data, xdg_wm_base*
(parameter) xdg_wm_base* b
b
, uint serial) @nogc nothrow
undefined identifier `xdg_wm_base`
{ wsi_wm_base_pong(b, serial); } extern (C) void
app.onToplevelConfigure
onToplevelConfigure
(void* data, xdg_toplevel*
(parameter) xdg_toplevel* t
t
, int w, int h,
undefined identifier `xdg_toplevel`
wl_array* states) @nogc nothrow
undefined identifier `wl_array`
{ auto
_error_ ctx
ctx
= cast(
(unresolved type) Ctx
Ctx
*) data;
ctx.
ctx.pendingW
pendingW
= w;
ctx.
ctx.pendingH
pendingH
= h;
} extern (C) void
app.onXdgSurfaceConfigure
onXdgSurfaceConfigure
(void* data, xdg_surface* s, uint serial) @nogc nothrow
undefined identifier `xdg_surface`
{ auto
_error_ ctx
ctx
= cast(
(unresolved type) Ctx
Ctx
*) data;
wsi_xdg_surface_ack_configure(s, serial); ctx.
ctx.width
width
= ctx.
ctx.pendingW
pendingW
> 0 ? ctx.
ctx.pendingW
pendingW
: defaultWidth;
ctx.
ctx.height
height
= ctx.
ctx.pendingH
pendingH
> 0 ? ctx.
ctx.pendingH
pendingH
: defaultHeight;
if (!ctx.
ctx.configured
configured
)
{ ctx.
ctx.configured
configured
= true;
emitf("first_configure", "tag=%s size=%dx%d", ctx.
ctx.tag
tag
, ctx.
ctx.width
width
, ctx.
ctx.height
height
);
render(ctx); } } extern (C) void
app.onToplevelClose
onToplevelClose
(void* data, xdg_toplevel*
(parameter) xdg_toplevel* t
t
) @nogc nothrow
undefined identifier `xdg_toplevel`
{ atomicStore((cast(
(unresolved type) Ctx
Ctx
*) data).
(cast(Ctx*)data).running
running
, false);
} extern (C) void
app.onToplevelConfigureBounds
onToplevelConfigureBounds
(void* data, xdg_toplevel*
(parameter) xdg_toplevel* t
t
, int w, int h) @nogc nothrow
undefined identifier `xdg_toplevel`
{ } extern (C) void
app.onToplevelWmCapabilities
onToplevelWmCapabilities
(void* data, xdg_toplevel*
(parameter) xdg_toplevel* t
t
, wl_array* caps) @nogc nothrow
undefined identifier `xdg_toplevel`
undefined identifier `wl_array`
{ } extern (C) void
app.onBufferRelease
onBufferRelease
(void* data, wl_buffer*
(parameter) wl_buffer* b
b
) @nogc nothrow
undefined identifier `wl_buffer`
{ atomicStore((cast(
(unresolved type) Buffer
Buffer
*) data).
(cast(Buffer*)data).busy
busy
, false);
} // Probe-2 bookkeeping: which thread ran the frame-callback handler? private __gshared pthread_t[2]
(__gshared global) ulong[2] app.g_dispThreads
g_dispThreads
;
private shared int[3]
(shared global) shared(int[3]) app.g_framesOnThread
g_framesOnThread
; // [main, dispatcher a, dispatcher b]
extern (C) void
app.onFrameDone
onFrameDone
(void* data, wl_callback*
(parameter) wl_callback* cb
cb
, uint timeMs) @nogc nothrow
undefined identifier `wl_callback`
{ auto
_error_ ctx
ctx
= cast(
(unresolved type) Ctx
Ctx
*) data;
wsi_callback_destroy(cb); ctx.
ctx.frameCb
frameCb
= null;
(template instance) atomicOp!"+="
atomicOp
!"+="(ctx.
ctx.frames
frames
, 1);
const
_error_ self
self
= pthread_self();
if (self == g_dispThreads[0])
(template instance) atomicOp!"+="
atomicOp
!"+="(g_framesOnThread[1], 1);
else if (self == g_dispThreads[1])
(template instance) atomicOp!"+="
atomicOp
!"+="(g_framesOnThread[2], 1);
else
(template instance) atomicOp!"+="
atomicOp
!"+="(g_framesOnThread[0], 1);
if (ctx.
ctx.autoRender
autoRender
&& atomicLoad(ctx.
ctx.running
running
))
render(ctx); } __gshared wl_registry_listener
_error_ app.g_registryListener
g_registryListener
= {&onGlobal, &onGlobalRemove};
undefined identifier `wl_registry_listener`
__gshared xdg_wm_base_listener
_error_ app.g_wmBaseListener
g_wmBaseListener
= {&onWmBasePing};
undefined identifier `xdg_wm_base_listener`
__gshared xdg_surface_listener
_error_ app.g_xdgSurfaceListener
g_xdgSurfaceListener
= {&onXdgSurfaceConfigure};
undefined identifier `xdg_surface_listener`
__gshared xdg_toplevel_listener
_error_ app.g_toplevelListener
g_toplevelListener
= {
undefined identifier `xdg_toplevel_listener`
&onToplevelConfigure, &onToplevelClose, &onToplevelConfigureBounds, &onToplevelWmCapabilities }; __gshared wl_buffer_listener
_error_ app.g_bufferListener
g_bufferListener
= {&onBufferRelease};
undefined identifier `wl_buffer_listener`
__gshared wl_callback_listener
_error_ app.g_frameListener
g_frameListener
= {&onFrameDone};
undefined identifier `wl_callback_listener`
/// Registry roundtrip + surface tree + initial no-buffer commit + dispatch /// until the first configure is acked (which commits the first buffer). /// Runs on whichever thread the probe says. bool
app.setupWindow

Registry roundtrip + surface tree + initial no-buffer commit + dispatch until the first configure is acked (which commits the first buffer). Runs on whichever thread the probe says.

setupWindow
(
(struct) app.Ctx

Everything one connection-plus-window owns. Probes share one global Ctx, except probe 5, which gives each thread its own.

Ctx
*
(parameter) Ctx* ctx
ctx
, const(char)* title) @nogc nothrow
{ ctx.
ctx.registry
registry
= wsi_display_get_registry(ctx.
ctx.display
display
);
wsi_registry_add_listener(ctx.
ctx.registry
registry
, &g_registryListener, ctx);
if (wl_display_roundtrip(ctx.
ctx.display
display
) < 0)
return false; if (ctx.
ctx.compositor
compositor
is null || ctx.
ctx.shm
shm
is null || ctx.
ctx.wmBase
wmBase
is null)
return false; wsi_wm_base_add_listener(ctx.
ctx.wmBase
wmBase
, &g_wmBaseListener, ctx);
ctx.
ctx.surface
surface
= wsi_compositor_create_surface(ctx.
ctx.compositor
compositor
);
ctx.
ctx.xdgSurface
xdgSurface
= wsi_wm_base_get_xdg_surface(ctx.
ctx.wmBase
wmBase
, ctx.
ctx.surface
surface
);
wsi_xdg_surface_add_listener(ctx.
ctx.xdgSurface
xdgSurface
, &g_xdgSurfaceListener, ctx);
ctx.
ctx.toplevel
toplevel
= wsi_xdg_surface_get_toplevel(ctx.
ctx.xdgSurface
xdgSurface
);
wsi_toplevel_add_listener(ctx.
ctx.toplevel
toplevel
, &g_toplevelListener, ctx);
wsi_toplevel_set_title(ctx.
ctx.toplevel
toplevel
, title);
wsi_toplevel_set_app_id(ctx.
ctx.toplevel
toplevel
, title);
wsi_surface_commit(ctx.
ctx.surface
surface
); // the mandatory no-buffer initial commit
emitf("window_created", "tag=%s", ctx.
ctx.tag
tag
);
while (!ctx.
ctx.configured
configured
)
if (wl_display_dispatch(ctx.
ctx.display
display
) < 0)
return false; return true; } /// Blocking-dispatch the default queue until `nFrames` frame callbacks or /// `capUs` elapse. bool
app.dispatchFrames

Blocking-dispatch the default queue until nFrames frame callbacks or capUs elapse.

dispatchFrames
(
(struct) app.Ctx

Everything one connection-plus-window owns. Probes share one global Ctx, except probe 5, which gives each thread its own.

Ctx
*
(parameter) Ctx* ctx
ctx
, int nFrames, long capUs) @nogc nothrow
{ const
_error_ deadline
deadline
= nowUs() + capUs;
while (atomicLoad(ctx.
ctx.frames
frames
) < nFrames && nowUs() < deadline)
if (wl_display_dispatch(ctx.
ctx.display
display
) < 0)
return false; return true; } void
app.teardownCtx
teardownCtx
(
(struct) app.Ctx

Everything one connection-plus-window owns. Probes share one global Ctx, except probe 5, which gives each thread its own.

Ctx
*
(parameter) Ctx* ctx
ctx
, bool disconnect) @nogc nothrow
{ foreach (ref
(parameter) b
b
; ctx.
ctx.buffers
buffers
)
if (b.
b.handle
handle
!is null)
{ wsi_buffer_destroy(b.
b.handle
handle
);
munmap(b.
b.pixels
pixels
, b.
b.byteSize
byteSize
);
b = Buffer.init; } if (ctx.
ctx.frameCb
frameCb
!is null)
wsi_callback_destroy(ctx.
ctx.frameCb
frameCb
);
if (ctx.
ctx.toplevel
toplevel
!is null)
wsi_toplevel_destroy(ctx.
ctx.toplevel
toplevel
);
if (ctx.
ctx.xdgSurface
xdgSurface
!is null)
wsi_xdg_surface_destroy(ctx.
ctx.xdgSurface
xdgSurface
);
if (ctx.
ctx.surface
surface
!is null)
wsi_surface_destroy(ctx.
ctx.surface
surface
);
if (ctx.
ctx.wmBase
wmBase
!is null)
wsi_wm_base_destroy(ctx.
ctx.wmBase
wmBase
);
if (ctx.
ctx.shm
shm
!is null)
wl_proxy_destroy(cast(wl_proxy*) ctx.
ctx.shm
shm
);
if (ctx.
ctx.compositor
compositor
!is null)
wl_proxy_destroy(cast(wl_proxy*) ctx.
ctx.compositor
compositor
);
if (ctx.
ctx.registry
registry
!is null)
wl_proxy_destroy(cast(wl_proxy*) ctx.
ctx.registry
registry
);
if (disconnect && ctx.
ctx.display
display
!is null)
wl_display_disconnect(ctx.
ctx.display
display
);
} /// Post-probe health check shared by every probe that keeps its connection. const(char)*
app.protocolErrorText

Post-probe health check shared by every probe that keeps its connection.

protocolErrorText
(
(struct) app.Ctx

Everything one connection-plus-window owns. Probes share one global Ctx, except probe 5, which gives each thread its own.

Ctx
*
(parameter) Ctx* ctx
ctx
) @nogc nothrow
{ return wl_display_get_error(ctx.
ctx.display
display
) == 0 ? "0".
"0".ptr
ptr
: "SET".
"SET".ptr
ptr
;
} private __gshared
(struct) app.Ctx

Everything one connection-plus-window owns. Probes share one global Ctx, except probe 5, which gives each thread its own.

Ctx
_error_ app.g_ctx
g_ctx
;
// -- probe 1: the whole window built on a worker thread, main sleeps ----------- private shared bool
(shared global) shared(bool) app.g_workerDone
g_workerDone
;
private shared bool
(shared global) shared(bool) app.g_workerOk
g_workerOk
;
extern (C) void*
void* app.windowWorker(void* __param_0) nothrow @nogc
windowWorker
(void*) @nogc nothrow
{ emit("thread=worker action=setup_window_start");
undefined identifier `emit`
bool
(local variable) bool ok
ok
= setupWindow(&g_ctx, "wsi-f17-threading");
if (
(local variable) bool ok
ok
)
(local variable) bool ok
ok
= dispatchFrames(&g_ctx, 30, 4_000_000);
emitf("thread=worker", "action=done ok=%d frames=%d commits=%d",
undefined identifier `emitf`
cast(int) ok, atomicLoad(g_ctx.
g_ctx.frames
frames
), g_ctx.
g_ctx.commits
commits
);
teardownCtx(&g_ctx, false); // even teardown happens off-main
(template function) core.atomic.atomicStore(MemoryOrder ms = MemoryOrder.seq, T, V)(ref T val, V newval) if (!is(T == shared) && !is(V == shared))
atomicStore
(
(shared global) shared(bool) app.g_workerOk
g_workerOk
,
(local variable) bool ok
ok
&&
(template function) core.atomic.atomicLoad(MemoryOrder ms = MemoryOrder.seq, T)(auto ref return scope const T val) if (!is(T == shared(U), U) && !is(T == shared(inout(U)), U) && !is(T == shared(const(U)), U))
atomicLoad
(g_ctx.
(field) _error_ g_ctx.frames
frames
) >= 30);
void core.atomic.atomicStore!(MemoryOrder.seq, bool, bool)(ref shared(bool) val, bool newval) pure nothrow @nogc @trusted

Writes 'newval' into 'val'. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.rel, and MemoryOrder.seq.

@paramval The target variable.@paramnewval The value to store.
atomicStore
(
(shared global) shared(bool) app.g_workerDone
g_workerDone
, true);
return null; } int
int app.probeWindowOnWorker() nothrow @nogc
probeWindowOnWorker
() @nogc nothrow
{ g_ctx.
g_ctx.display
display
= wl_display_connect(null); // connected on MAIN …
if (g_ctx.
(field) _error_ g_ctx.display
display
is null)
{
void app.verdict(scope const(char)* result, scope const(char)* detail) nothrow @nogc

The one line the spec requires per run. Async-signal-safe on purpose (snprintf into a static buffer + write(2)) so the signal handlers can call it; the normal path uses it too so the format is identical.

verdict
("error", "no_compositor_in_child");
return 0; } emit("step name=wl_display_connect thread=main");
undefined identifier `emit`
pthread_t
(local variable) ulong t
t
;
int core.sys.posix.pthread.pthread_create(ulong*, scope const(core.sys.posix.sys.types.pthread_attr_t*), extern (C) void* function(void*), void*) nothrow @nogc
pthread_create
(&
(local variable) ulong t
t
, null, &
void* app.windowWorker(void* __param_0) nothrow @nogc
windowWorker
, null);
// … and never touched by main again until the worker is done: main SLEEPS. while (!
bool core.atomic.atomicLoad!(MemoryOrder.seq, bool)(ref return scope shared(const(bool)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(bool) app.g_workerDone
g_workerDone
))
int core.sys.posix.unistd.usleep(uint) nothrow @nogc @trusted
usleep
(10_000);
int core.sys.posix.pthread.pthread_join(ulong, void**) nothrow @nogc
pthread_join
(
(local variable) ulong t
t
, null);
static __gshared char[192]
(__gshared global) char[192] app.probeWindowOnWorker.d
d
;
int core.stdc.stdio.snprintf(scope char* s, ulong n, scope const(char*) format, scope const ...) nothrow @nogc
snprintf
(
(__gshared global) char[192] app.probeWindowOnWorker.d
d
.
(constant) char* char[192].ptr = &d
ptr
,
(__gshared global) char[192] app.probeWindowOnWorker.d
d
.
(constant) ulong char[192].length = 192LU
length
,
"window_and_30_frames_entirely_on_worker=%d connect_thread=main protocol_error=%s", cast(int)
bool core.atomic.atomicLoad!(MemoryOrder.seq, bool)(ref return scope shared(const(bool)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(bool) app.g_workerOk
g_workerOk
), protocolErrorText(&g_ctx));
void app.verdict(scope const(char)* result, scope const(char)* detail) nothrow @nogc

The one line the spec requires per run. Async-signal-safe on purpose (snprintf into a static buffer + write(2)) so the signal handlers can call it; the normal path uses it too so the format is identical.

verdict
(
bool core.atomic.atomicLoad!(MemoryOrder.seq, bool)(ref return scope shared(const(bool)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(bool) app.g_workerOk
g_workerOk
) ? "ok" : "error",
(__gshared global) char[192] app.probeWindowOnWorker.d
d
.
(constant) char* char[192].ptr = &d
ptr
);
wl_display_disconnect(g_ctx.
g_ctx.display
display
);
undefined identifier `wl_display_disconnect`
return 0; } // -- probe 2: two threads dispatching the same default queue ------------------- private shared bool
(shared global) shared(bool) app.g_p2Running
g_p2Running
;
private shared int
(shared global) shared(int) app.g_alive
g_alive
;
private shared int[2]
(shared global) shared(int[2]) app.g_dispatched
g_dispatched
; // events returned by wl_display_dispatch
private shared int[2]
(shared global) shared(int[2]) app.g_dispatchCalls
g_dispatchCalls
;
private shared int
(shared global) shared(int) app.g_syncDone
g_syncDone
;
extern (C) void
app.onSyncCount
onSyncCount
(void* data, wl_callback*
(parameter) wl_callback* cb
cb
, uint
(parameter) uint t
t
) @nogc nothrow
undefined identifier `wl_callback`
{ wsi_callback_destroy(cb);
(template instance) atomicOp!"+="
atomicOp
!"+="(g_syncDone, 1);
} __gshared wl_callback_listener
_error_ app.g_syncCountListener
g_syncCountListener
= {&onSyncCount};
undefined identifier `wl_callback_listener`
extern (C) void*
void* app.dispatcher(void* arg) nothrow @nogc
dispatcher
(void*
(parameter) void* arg
arg
) @nogc nothrow
{ const
(local variable) const(int) idx
idx
= cast(int) cast(
(alias) object.size_t = ulong
size_t
)
(parameter) void* arg
arg
;
int core.atomic.atomicOp!("+=", int, int)(ref shared(int) val, int mod) pure nothrow @nogc @safe

Performs the binary operation 'op' on val using 'mod' as the modifier.

@paramval The target variable.@parammod The modifier to apply.@returnsThe result of the operation.
atomicOp
!"+="(
(shared global) shared(int) app.g_alive
g_alive
, 1);
emitf("thread_start", "thread=dispatcher_%c", cast(char)('a' + idx));
undefined identifier `emitf`
while (
bool core.atomic.atomicLoad!(MemoryOrder.seq, bool)(ref return scope shared(const(bool)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(bool) app.g_p2Running
g_p2Running
))
{ const
(local variable) const(_error_) n
n
= wl_display_dispatch(g_ctx.
g_ctx.display
display
); // blocking, default queue
undefined identifier `wl_display_dispatch`
if (n < 0) break;
(template function) core.atomic.atomicOp(string op, T, V1)(ref shared T val, V1 mod) if (__traits(compiles, mixin("*cast(T*)&val" ~ op ~ "mod")))
atomicOp
!"+="(
(shared global) shared(int[2]) app.g_dispatched
g_dispatched
[
(local variable) const(int) idx
idx
], n);
int core.atomic.atomicOp!("+=", int, int)(ref shared(int) val, int mod) pure nothrow @nogc @safe

Performs the binary operation 'op' on val using 'mod' as the modifier.

@paramval The target variable.@parammod The modifier to apply.@returnsThe result of the operation.
atomicOp
!"+="(
(shared global) shared(int[2]) app.g_dispatchCalls
g_dispatchCalls
[
(local variable) const(int) idx
idx
], 1);
} emitf("thread_done", "thread=dispatcher_%c events=%d calls=%d",
undefined identifier `emitf`
cast(char)('a' + idx), atomicLoad(g_dispatched[idx]), atomicLoad(g_dispatchCalls[idx]));
int core.atomic.atomicOp!("-=", int, int)(ref shared(int) val, int mod) pure nothrow @nogc @safe

Performs the binary operation 'op' on val using 'mod' as the modifier.

@paramval The target variable.@parammod The modifier to apply.@returnsThe result of the operation.
atomicOp
!"-="(
(shared global) shared(int) app.g_alive
g_alive
, 1);
return null; } int
int app.probeConcurrentDispatch() nothrow @nogc
probeConcurrentDispatch
() @nogc nothrow
{ g_ctx.
g_ctx.display
display
= wl_display_connect(null);
if (g_ctx.
(field) _error_ g_ctx.display
display
is null || !setupWindow(&g_ctx, "wsi-f17-threading"))
{
void app.verdict(scope const(char)* result, scope const(char)* detail) nothrow @nogc

The one line the spec requires per run. Async-signal-safe on purpose (snprintf into a static buffer + write(2)) so the signal handlers can call it; the normal path uses it too so the format is identical.

verdict
("error", "setup_failed");
return 0; } // autoRender keeps frame callbacks (≈60 Hz events) flowing; the frame // handler runs on whichever thread happens to dispatch it.
void core.atomic.atomicStore!(MemoryOrder.seq, bool, bool)(ref shared(bool) val, bool newval) pure nothrow @nogc @trusted

Writes 'newval' into 'val'. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.rel, and MemoryOrder.seq.

@paramval The target variable.@paramnewval The value to store.
atomicStore
(
(shared global) shared(bool) app.g_p2Running
g_p2Running
, true);
pthread_t[2]
(local variable) ulong[2] ts
ts
;
int core.sys.posix.pthread.pthread_create(ulong*, scope const(core.sys.posix.sys.types.pthread_attr_t*), extern (C) void* function(void*), void*) nothrow @nogc
pthread_create
(&
(local variable) ulong[2] ts
ts
[0], null, &
void* app.dispatcher(void* arg) nothrow @nogc
dispatcher
, cast(void*) 0);
int core.sys.posix.pthread.pthread_create(ulong*, scope const(core.sys.posix.sys.types.pthread_attr_t*), extern (C) void* function(void*), void*) nothrow @nogc
pthread_create
(&
(local variable) ulong[2] ts
ts
[1], null, &
void* app.dispatcher(void* arg) nothrow @nogc
dispatcher
, cast(void*) 1);
(__gshared global) ulong[2] app.g_dispThreads
g_dispThreads
[0] =
(local variable) ulong[2] ts
ts
[0];
(__gshared global) ulong[2] app.g_dispThreads
g_dispThreads
[1] =
(local variable) ulong[2] ts
ts
[1];
int core.sys.posix.unistd.usleep(uint) nothrow @nogc @trusted
usleep
(2_000_000); // main does NOT dispatch — only the two workers do
void core.atomic.atomicStore!(MemoryOrder.seq, bool, bool)(ref shared(bool) val, bool newval) pure nothrow @nogc @trusted

Writes 'newval' into 'val'. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.rel, and MemoryOrder.seq.

@paramval The target variable.@paramnewval The value to store.
atomicStore
(
(shared global) shared(bool) app.g_p2Running
g_p2Running
, false);
// A thread blocked in wl_display_dispatch only wakes when an event // arrives — feed wl_display.sync done events until both exit (cap 400). int
(local variable) int wakes
wakes
= 0;
while (
int core.atomic.atomicLoad!(MemoryOrder.seq, int)(ref return scope shared(const(int)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(int) app.g_alive
g_alive
) > 0 &&
(local variable) int wakes
wakes
< 400)
{ auto
(local variable) _error_ cb
cb
= wsi_display_sync(g_ctx.
g_ctx.display
display
);
undefined identifier `wsi_display_sync`
wsi_callback_add_listener(cb, &g_syncCountListener, null);
undefined identifier `wsi_callback_add_listener`
wl_display_flush(g_ctx.
g_ctx.display
display
);
undefined identifier `wl_display_flush`
++
(local variable) int wakes
wakes
;
int core.sys.posix.unistd.usleep(uint) nothrow @nogc @trusted
usleep
(5000);
} const
(local variable) const(int) stuck
stuck
=
int core.atomic.atomicLoad!(MemoryOrder.seq, int)(ref return scope shared(const(int)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(int) app.g_alive
g_alive
);
static __gshared char[256]
(__gshared global) char[256] app.probeConcurrentDispatch.d
d
;
if (
(local variable) const(int) stuck
stuck
> 0)
{
int core.stdc.stdio.snprintf(scope char* s, ulong n, scope const(char*) format, scope const ...) nothrow @nogc
snprintf
(
(__gshared global) char[256] app.probeConcurrentDispatch.d
d
.
(constant) char* char[256].ptr = &d
ptr
,
(__gshared global) char[256] app.probeConcurrentDispatch.d
d
.
(constant) ulong char[256].length = 256LU
length
,
"threads_stuck_in_dispatch=%d after_%d_sync_wakes events_a=%d events_b=%d",
(local variable) const(int) stuck
stuck
,
(local variable) int wakes
wakes
,
int core.atomic.atomicLoad!(MemoryOrder.seq, int)(ref return scope shared(const(int)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(int[2]) app.g_dispatched
g_dispatched
[0]),
int core.atomic.atomicLoad!(MemoryOrder.seq, int)(ref return scope shared(const(int)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(int[2]) app.g_dispatched
g_dispatched
[1]));
void app.verdict(scope const(char)* result, scope const(char)* detail) nothrow @nogc

The one line the spec requires per run. Async-signal-safe on purpose (snprintf into a static buffer + write(2)) so the signal handlers can call it; the normal path uses it too so the format is identical.

verdict
("deadlock",
(__gshared global) char[256] app.probeConcurrentDispatch.d
d
.
(constant) char* char[256].ptr = &d
ptr
);
noreturn core.sys.posix.unistd._exit(int) nothrow @nogc @trusted
_exit
(0); // cannot join a stuck thread; verdict is flushed
}
int core.sys.posix.pthread.pthread_join(ulong, void**) nothrow @nogc
pthread_join
(
(local variable) ulong[2] ts
ts
[0], null);
int core.sys.posix.pthread.pthread_join(ulong, void**) nothrow @nogc
pthread_join
(
(local variable) ulong[2] ts
ts
[1], null);
int core.stdc.stdio.snprintf(scope char* s, ulong n, scope const(char*) format, scope const ...) nothrow @nogc
snprintf
(
(__gshared global) char[256] app.probeConcurrentDispatch.d
d
.
(constant) char* char[256].ptr = &d
ptr
,
(__gshared global) char[256] app.probeConcurrentDispatch.d
d
.
(constant) ulong char[256].length = 256LU
length
,
"events_a=%d events_b=%d frames=%d frames_handled_on_a=%d on_b=%d sync_wakes_to_unblock=%d protocol_error=%s",
int core.atomic.atomicLoad!(MemoryOrder.seq, int)(ref return scope shared(const(int)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(int[2]) app.g_dispatched
g_dispatched
[0]),
int core.atomic.atomicLoad!(MemoryOrder.seq, int)(ref return scope shared(const(int)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(int[2]) app.g_dispatched
g_dispatched
[1]),
(template function) core.atomic.atomicLoad(MemoryOrder ms = MemoryOrder.seq, T)(auto ref return scope const T val) if (!is(T == shared(U), U) && !is(T == shared(inout(U)), U) && !is(T == shared(const(U)), U))
atomicLoad
(g_ctx.
(field) _error_ g_ctx.frames
frames
),
int core.atomic.atomicLoad!(MemoryOrder.seq, int)(ref return scope shared(const(int)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(int[3]) app.g_framesOnThread
g_framesOnThread
[1]),
int core.atomic.atomicLoad!(MemoryOrder.seq, int)(ref return scope shared(const(int)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(int[3]) app.g_framesOnThread
g_framesOnThread
[2]),
(local variable) int wakes
wakes
, protocolErrorText(&g_ctx));
void app.verdict(scope const(char)* result, scope const(char)* detail) nothrow @nogc

The one line the spec requires per run. Async-signal-safe on purpose (snprintf into a static buffer + write(2)) so the signal handlers can call it; the normal path uses it too so the format is identical.

verdict
(wl_display_get_error(g_ctx.
g_ctx.display
display
) == 0 ? "ok" : "error",
(__gshared global) char[256] app.probeConcurrentDispatch.d
d
.
(constant) char* char[256].ptr = &d
ptr
);
undefined identifier `wl_display_get_error`
teardownCtx(&g_ctx, true); return 0; } // -- probe 3: the designed pattern — a worker-owned wl_event_queue ------------- private shared bool
(shared global) shared(bool) app.g_qSyncSeen
g_qSyncSeen
;
private shared int
(shared global) shared(int) app.g_workerSyncs
g_workerSyncs
;
private shared long
(shared global) shared(long) app.g_workerFirstUs
g_workerFirstUs
,
(shared global) shared(long) app.g_workerLastUs
g_workerLastUs
;
private shared bool
(shared global) shared(bool) app.g_p3WorkerDone
g_p3WorkerDone
;
extern (C) void
app.onQueueSync
onQueueSync
(void* data, wl_callback*
(parameter) wl_callback* cb
cb
, uint
(parameter) uint t
t
) @nogc nothrow
undefined identifier `wl_callback`
{ wsi_callback_destroy(cb); const
_error_ now
now
= nowUs();
if (atomicLoad(g_workerFirstUs) == 0) atomicStore(g_workerFirstUs, now); atomicStore(g_workerLastUs, now); atomicStore(g_qSyncSeen, true); } __gshared wl_callback_listener
_error_ app.g_queueSyncListener
g_queueSyncListener
= {&onQueueSync};
undefined identifier `wl_callback_listener`
extern (C) void*
void* app.queueWorker(void* __param_0) nothrow @nogc
queueWorker
(void*) @nogc nothrow
{ auto
(local variable) _error_ display
display
= g_ctx.
(field) _error_ g_ctx.display
display
;
// The worker's own queue, and a display *wrapper* to assign it through — // wl_proxy_set_queue on a wrapper is the race-free idiom: objects created // via the wrapper are born on the worker's queue, never the default one. auto
(local variable) _error_ queue
queue
= wl_display_create_queue(display);
undefined identifier `wl_display_create_queue`
auto
(local variable) _error_ wrapper
wrapper
= cast(wl_display*) wl_proxy_create_wrapper(display);
undefined identifier `wl_display`
wl_proxy_set_queue(cast(wl_proxy*) wrapper, queue);
undefined identifier `wl_proxy_set_queue`
emit("thread=worker action=queue_and_wrapper_created");
undefined identifier `emit`
foreach (
(local variable) int i
i
; 0 .. 50)
{
void core.atomic.atomicStore!(MemoryOrder.seq, bool, bool)(ref shared(bool) val, bool newval) pure nothrow @nogc @trusted

Writes 'newval' into 'val'. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.rel, and MemoryOrder.seq.

@paramval The target variable.@paramnewval The value to store.
atomicStore
(
(shared global) shared(bool) app.g_qSyncSeen
g_qSyncSeen
, false);
auto
(local variable) _error_ cb
cb
= wsi_display_sync(wrapper); // done event -> worker's queue
undefined identifier `wsi_display_sync`
wsi_callback_add_listener(cb, &g_queueSyncListener, null);
undefined identifier `wsi_callback_add_listener`
bool
(local variable) bool failed
failed
= false;
while (!
bool core.atomic.atomicLoad!(MemoryOrder.seq, bool)(ref return scope shared(const(bool)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(bool) app.g_qSyncSeen
g_qSyncSeen
))
if (wl_display_dispatch_queue(display, queue) < 0)
undefined identifier `wl_display_dispatch_queue`
{
(local variable) bool failed
failed
= true;
break; } if (
(local variable) bool failed
failed
)
break;
int core.atomic.atomicOp!("+=", int, int)(ref shared(int) val, int mod) pure nothrow @nogc @safe

Performs the binary operation 'op' on val using 'mod' as the modifier.

@paramval The target variable.@parammod The modifier to apply.@returnsThe result of the operation.
atomicOp
!"+="(
(shared global) shared(int) app.g_workerSyncs
g_workerSyncs
, 1);
int core.sys.posix.unistd.usleep(uint) nothrow @nogc @trusted
usleep
(10_000); // ~10 ms apart, so the runs overlap main's frames
} wl_proxy_wrapper_destroy(cast(wl_proxy*) wrapper);
undefined identifier `wl_proxy_wrapper_destroy`
wl_event_queue_destroy(queue);
undefined identifier `wl_event_queue_destroy`
emitf("thread=worker", "action=done syncs=%d", atomicLoad(g_workerSyncs));
undefined identifier `emitf`
void core.atomic.atomicStore!(MemoryOrder.seq, bool, bool)(ref shared(bool) val, bool newval) pure nothrow @nogc @trusted

Writes 'newval' into 'val'. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.rel, and MemoryOrder.seq.

@paramval The target variable.@paramnewval The value to store.
atomicStore
(
(shared global) shared(bool) app.g_p3WorkerDone
g_p3WorkerDone
, true);
return null; } int
int app.probePerThreadQueue() nothrow @nogc
probePerThreadQueue
() @nogc nothrow
{ g_ctx.
g_ctx.display
display
= wl_display_connect(null);
if (g_ctx.
(field) _error_ g_ctx.display
display
is null || !setupWindow(&g_ctx, "wsi-f17-threading"))
{
void app.verdict(scope const(char)* result, scope const(char)* detail) nothrow @nogc

The one line the spec requires per run. Async-signal-safe on purpose (snprintf into a static buffer + write(2)) so the signal handlers can call it; the normal path uses it too so the format is identical.

verdict
("error", "setup_failed");
return 0; } const
(local variable) const(_error_) mainFirstUs
mainFirstUs
= nowUs();
undefined identifier `nowUs`
pthread_t
(local variable) ulong t
t
;
int core.sys.posix.pthread.pthread_create(ulong*, scope const(core.sys.posix.sys.types.pthread_attr_t*), extern (C) void* function(void*), void*) nothrow @nogc
pthread_create
(&
(local variable) ulong t
t
, null, &
void* app.queueWorker(void* __param_0) nothrow @nogc
queueWorker
, null);
// Main dispatches the DEFAULT queue (frame callbacks + rendering) while // the worker dispatches ITS queue — concurrently, on one connection. while (!
bool core.atomic.atomicLoad!(MemoryOrder.seq, bool)(ref return scope shared(const(bool)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(bool) app.g_p3WorkerDone
g_p3WorkerDone
))
if (wl_display_dispatch(g_ctx.
g_ctx.display
display
) < 0)
undefined identifier `wl_display_dispatch`
break;
int core.sys.posix.pthread.pthread_join(ulong, void**) nothrow @nogc
pthread_join
(
(local variable) ulong t
t
, null);
const
(local variable) const(_error_) mainLastUs
mainLastUs
= nowUs();
undefined identifier `nowUs`
// Concurrency evidence: the worker's 50 sync round-trips and main's frame // stream span overlapping time windows on the same wl_display. const
(local variable) const(_error_) overlap
overlap
=
long core.atomic.atomicLoad!(MemoryOrder.seq, long)(ref return scope shared(const(long)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(long) app.g_workerFirstUs
g_workerFirstUs
) < mainLastUs
&& mainFirstUs <
long core.atomic.atomicLoad!(MemoryOrder.seq, long)(ref return scope shared(const(long)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(long) app.g_workerLastUs
g_workerLastUs
)
&&
(template function) core.atomic.atomicLoad(MemoryOrder ms = MemoryOrder.seq, T)(auto ref return scope const T val) if (!is(T == shared(U), U) && !is(T == shared(inout(U)), U) && !is(T == shared(const(U)), U))
atomicLoad
(g_ctx.
(field) _error_ g_ctx.frames
frames
) > 0;
static __gshared char[256]
(__gshared global) char[256] app.probePerThreadQueue.d
d
;
int core.stdc.stdio.snprintf(scope char* s, ulong n, scope const(char*) format, scope const ...) nothrow @nogc
snprintf
(
(__gshared global) char[256] app.probePerThreadQueue.d
d
.
(constant) char* char[256].ptr = &d
ptr
,
(__gshared global) char[256] app.probePerThreadQueue.d
d
.
(constant) ulong char[256].length = 256LU
length
,
"worker_syncs=%d/50 main_frames=%d overlap=%d worker_window_us=%lld..%lld protocol_error=%s",
int core.atomic.atomicLoad!(MemoryOrder.seq, int)(ref return scope shared(const(int)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(int) app.g_workerSyncs
g_workerSyncs
),
(template function) core.atomic.atomicLoad(MemoryOrder ms = MemoryOrder.seq, T)(auto ref return scope const T val) if (!is(T == shared(U), U) && !is(T == shared(inout(U)), U) && !is(T == shared(const(U)), U))
atomicLoad
(g_ctx.
(field) _error_ g_ctx.frames
frames
), cast(int) overlap,
long core.atomic.atomicLoad!(MemoryOrder.seq, long)(ref return scope shared(const(long)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(long) app.g_workerFirstUs
g_workerFirstUs
),
long core.atomic.atomicLoad!(MemoryOrder.seq, long)(ref return scope shared(const(long)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(long) app.g_workerLastUs
g_workerLastUs
), protocolErrorText(&g_ctx));
void app.verdict(scope const(char)* result, scope const(char)* detail) nothrow @nogc

The one line the spec requires per run. Async-signal-safe on purpose (snprintf into a static buffer + write(2)) so the signal handlers can call it; the normal path uses it too so the format is identical.

verdict
(
int core.atomic.atomicLoad!(MemoryOrder.seq, int)(ref return scope shared(const(int)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(int) app.g_workerSyncs
g_workerSyncs
) == 50 && overlap
&& wl_display_get_error(g_ctx.
g_ctx.display
display
) == 0 ? "ok" : "error",
(__gshared global) char[256] app.probePerThreadQueue.d
d
.
(constant) char* char[256].ptr = &d
ptr
);
undefined identifier `wl_display_get_error`
teardownCtx(&g_ctx, true); return 0; } // -- probe 4: render thread committing the shared wl_surface ------------------- private shared int
(shared global) shared(int) app.g_renderFrames
g_renderFrames
; // frame callbacks for worker commits
private shared int
(shared global) shared(int) app.g_renderCommits
g_renderCommits
;
private shared bool
(shared global) shared(bool) app.g_p4WorkerDone
g_p4WorkerDone
;
extern (C) void
app.onRenderFrame
onRenderFrame
(void* data, wl_callback*
(parameter) wl_callback* cb
cb
, uint
(parameter) uint t
t
) @nogc nothrow
undefined identifier `wl_callback`
{ wsi_callback_destroy(cb);
(template instance) atomicOp!"+="
atomicOp
!"+="(g_renderFrames, 1);
} __gshared wl_callback_listener
_error_ app.g_renderFrameListener
g_renderFrameListener
= {&onRenderFrame};
undefined identifier `wl_callback_listener`
extern (C) void*
void* app.renderWorker(void* __param_0) nothrow @nogc
renderWorker
(void*) @nogc nothrow
{ emit("thread=render action=start frames_target=100");
undefined identifier `emit`
foreach (
(local variable) int frame
frame
; 0 .. 100)
{ // Wait for a free buffer (wl_buffer.release lands on the main pump).
(struct) app.Buffer

One wl_shm ARGB8888 buffer. busy is owned by the compositor between wl_surface.commit and wl_buffer.release — and in probe 4 it crosses threads (release lands on the dispatching main thread, the render worker polls it), hence atomic.

Buffer
*
(local variable) _error_ buf
buf
= null;
const
(local variable) const(_error_) bufDeadline
bufDeadline
= nowUs() + 1_000_000;
undefined identifier `nowUs`
while (buf is null && nowUs() < bufDeadline)
undefined identifier `nowUs`
{ buf = freeBuffer(&g_ctx); if (buf is null)
int core.sys.posix.unistd.usleep(uint) nothrow @nogc @trusted
usleep
(500);
} if (buf is null) break; if (!ensureBuffer(&g_ctx, *buf, g_ctx.
g_ctx.width
width
, g_ctx.
g_ctx.height
height
))
break; paint(*buf, frame); // Requests on the shared wl_surface proxy, from the worker: wsi_surface_attach(g_ctx.
g_ctx.surface
surface
, buf.
buf.handle
handle
, 0, 0);
undefined identifier `wsi_surface_attach`
wsi_surface_damage_buffer(g_ctx.
g_ctx.surface
surface
, 0, 0, buf.
buf.width
width
, buf.
buf.height
height
);
undefined identifier `wsi_surface_damage_buffer`
auto
(local variable) _error_ cb
cb
= wsi_surface_frame(g_ctx.
g_ctx.surface
surface
);
undefined identifier `wsi_surface_frame`
wsi_callback_add_listener(cb, &g_renderFrameListener, null);
undefined identifier `wsi_callback_add_listener`
wsi_surface_commit(g_ctx.
g_ctx.surface
surface
);
undefined identifier `wsi_surface_commit`
(template function) core.atomic.atomicStore(MemoryOrder ms = MemoryOrder.seq, T, V)(ref T val, V newval) if (!is(T == shared) && !is(V == shared))
atomicStore
(buf.
(field) _error_ buf.busy
busy
, true);
wl_display_flush(g_ctx.
g_ctx.display
display
); // each thread flushes its own requests
undefined identifier `wl_display_flush`
int core.atomic.atomicOp!("+=", int, int)(ref shared(int) val, int mod) pure nothrow @nogc @safe

Performs the binary operation 'op' on val using 'mod' as the modifier.

@paramval The target variable.@parammod The modifier to apply.@returnsThe result of the operation.
atomicOp
!"+="(
(shared global) shared(int) app.g_renderCommits
g_renderCommits
, 1);
// Throttle on presentation: the frame callback is dispatched by MAIN. const
(local variable) const(_error_) frameDeadline
frameDeadline
= nowUs() + 1_000_000;
undefined identifier `nowUs`
while (
int core.atomic.atomicLoad!(MemoryOrder.seq, int)(ref return scope shared(const(int)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(int) app.g_renderFrames
g_renderFrames
) <
int core.atomic.atomicLoad!(MemoryOrder.seq, int)(ref return scope shared(const(int)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(int) app.g_renderCommits
g_renderCommits
)
&& nowUs() < frameDeadline)
undefined identifier `nowUs`
int core.sys.posix.unistd.usleep(uint) nothrow @nogc @trusted
usleep
(500);
} emitf("thread=render", "action=done commits=%d frames_acked=%d",
undefined identifier `emitf`
atomicLoad(g_renderCommits), atomicLoad(g_renderFrames));
void core.atomic.atomicStore!(MemoryOrder.seq, bool, bool)(ref shared(bool) val, bool newval) pure nothrow @nogc @trusted

Writes 'newval' into 'val'. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.rel, and MemoryOrder.seq.

@paramval The target variable.@paramnewval The value to store.
atomicStore
(
(shared global) shared(bool) app.g_p4WorkerDone
g_p4WorkerDone
, true);
// One last sync so the main dispatcher wakes and sees the done flag. auto
(local variable) _error_ cb
cb
= wsi_display_sync(g_ctx.
g_ctx.display
display
);
undefined identifier `wsi_display_sync`
wsi_callback_add_listener(cb, &g_syncCountListener, null);
undefined identifier `wsi_callback_add_listener`
wl_display_flush(g_ctx.
g_ctx.display
display
);
undefined identifier `wl_display_flush`
return null; } int
int app.probeRenderThread() nothrow @nogc
probeRenderThread
() @nogc nothrow
{ g_ctx.
g_ctx.autoRender
autoRender
= false; // main only pumps; the worker owns rendering
g_ctx.
g_ctx.display
display
= wl_display_connect(null);
if (g_ctx.
(field) _error_ g_ctx.display
display
is null || !setupWindow(&g_ctx, "wsi-f17-threading"))
{
void app.verdict(scope const(char)* result, scope const(char)* detail) nothrow @nogc

The one line the spec requires per run. Async-signal-safe on purpose (snprintf into a static buffer + write(2)) so the signal handlers can call it; the normal path uses it too so the format is identical.

verdict
("error", "setup_failed");
return 0; } pthread_t
(local variable) ulong t
t
;
int core.sys.posix.pthread.pthread_create(ulong*, scope const(core.sys.posix.sys.types.pthread_attr_t*), extern (C) void* function(void*), void*) nothrow @nogc
pthread_create
(&
(local variable) ulong t
t
, null, &
void* app.renderWorker(void* __param_0) nothrow @nogc
renderWorker
, null);
while (!
bool core.atomic.atomicLoad!(MemoryOrder.seq, bool)(ref return scope shared(const(bool)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(bool) app.g_p4WorkerDone
g_p4WorkerDone
))
if (wl_display_dispatch(g_ctx.
g_ctx.display
display
) < 0)
undefined identifier `wl_display_dispatch`
break;
int core.sys.posix.pthread.pthread_join(ulong, void**) nothrow @nogc
pthread_join
(
(local variable) ulong t
t
, null);
const
(local variable) const(int) commits
commits
=
int core.atomic.atomicLoad!(MemoryOrder.seq, int)(ref return scope shared(const(int)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(int) app.g_renderCommits
g_renderCommits
);
const
(local variable) const(int) acked
acked
=
int core.atomic.atomicLoad!(MemoryOrder.seq, int)(ref return scope shared(const(int)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(int) app.g_renderFrames
g_renderFrames
);
static __gshared char[224]
(__gshared global) char[224] app.probeRenderThread.d
d
;
int core.stdc.stdio.snprintf(scope char* s, ulong n, scope const(char*) format, scope const ...) nothrow @nogc
snprintf
(
(__gshared global) char[224] app.probeRenderThread.d
d
.
(constant) char* char[224].ptr = &d
ptr
,
(__gshared global) char[224] app.probeRenderThread.d
d
.
(constant) ulong char[224].length = 224LU
length
,
"commits_from_render_thread=%d/100 frame_callbacks=%d protocol_error=%s",
(local variable) const(int) commits
commits
,
(local variable) const(int) acked
acked
, protocolErrorText(&g_ctx));
void app.verdict(scope const(char)* result, scope const(char)* detail) nothrow @nogc

The one line the spec requires per run. Async-signal-safe on purpose (snprintf into a static buffer + write(2)) so the signal handlers can call it; the normal path uses it too so the format is identical.

verdict
(
(local variable) const(int) commits
commits
== 100 &&
(local variable) const(int) acked
acked
>= 99 && wl_display_get_error(g_ctx.
g_ctx.display
display
) == 0
undefined identifier `wl_display_get_error`
? "ok" : "error",
(__gshared global) char[224] app.probeRenderThread.d
d
.
(constant) char* char[224].ptr = &d
ptr
);
teardownCtx(&g_ctx, true); return 0; } // -- probe 5: one wl_display connection per thread ------------------------------ private __gshared
(struct) app.Ctx

Everything one connection-plus-window owns. Probes share one global Ctx, except probe 5, which gives each thread its own.

Ctx
[2]
_error_ app.g_p5ctx
g_p5ctx
;
private shared int
(shared global) shared(int) app.g_p5Ok
g_p5Ok
;
extern (C) void*
void* app.connectionPerThread(void* arg) nothrow @nogc
connectionPerThread
(void*
(parameter) void* arg
arg
) @nogc nothrow
{ const
(local variable) const(int) idx
idx
= cast(int) cast(
(alias) object.size_t = ulong
size_t
)
(parameter) void* arg
arg
;
auto
(local variable) _error_ ctx
ctx
= &g_p5ctx[idx];
ctx.
ctx.tag
tag
= idx == 0 ? "t0".
"t0".ptr
ptr
: "t1".
"t1".ptr
ptr
;
ctx.
ctx.display
display
= wl_display_connect(null); // private connection: nothing shared
if (ctx.
(field) _error_ ctx.display
display
is null)
return null; emitf("thread_connect", "tag=%s fd=%d", ctx.
ctx.tag
tag
, wl_display_get_fd(ctx.
ctx.display
display
));
undefined identifier `emitf`
bool
(local variable) bool ok
ok
= setupWindow(ctx, idx == 0 ? "wsi-f17-t0".
"wsi-f17-t0".ptr
ptr
: "wsi-f17-t1".
"wsi-f17-t1".ptr
ptr
);
if (
(local variable) bool ok
ok
)
(local variable) bool ok
ok
= dispatchFrames(ctx, 20, 3_000_000) &&
(template function) core.atomic.atomicLoad(MemoryOrder ms = MemoryOrder.seq, T)(auto ref return scope const T val) if (!is(T == shared(U), U) && !is(T == shared(inout(U)), U) && !is(T == shared(const(U)), U))
atomicLoad
(ctx.
(field) _error_ ctx.frames
frames
) >= 20;
emitf("thread_done", "tag=%s ok=%d frames=%d", ctx.
ctx.tag
tag
, cast(int) ok,
undefined identifier `emitf`
atomicLoad(ctx.
ctx.frames
frames
));
teardownCtx(ctx, true); if (
(local variable) bool ok
ok
)
int core.atomic.atomicOp!("+=", int, int)(ref shared(int) val, int mod) pure nothrow @nogc @safe

Performs the binary operation 'op' on val using 'mod' as the modifier.

@paramval The target variable.@parammod The modifier to apply.@returnsThe result of the operation.
atomicOp
!"+="(
(shared global) shared(int) app.g_p5Ok
g_p5Ok
, 1);
return null; } int
int app.probeConnectionPerThread() nothrow @nogc
probeConnectionPerThread
() @nogc nothrow
{ pthread_t[2]
(local variable) ulong[2] ts
ts
;
int core.sys.posix.pthread.pthread_create(ulong*, scope const(core.sys.posix.sys.types.pthread_attr_t*), extern (C) void* function(void*), void*) nothrow @nogc
pthread_create
(&
(local variable) ulong[2] ts
ts
[0], null, &
void* app.connectionPerThread(void* arg) nothrow @nogc
connectionPerThread
, cast(void*) 0);
int core.sys.posix.pthread.pthread_create(ulong*, scope const(core.sys.posix.sys.types.pthread_attr_t*), extern (C) void* function(void*), void*) nothrow @nogc
pthread_create
(&
(local variable) ulong[2] ts
ts
[1], null, &
void* app.connectionPerThread(void* arg) nothrow @nogc
connectionPerThread
, cast(void*) 1);
int core.sys.posix.pthread.pthread_join(ulong, void**) nothrow @nogc
pthread_join
(
(local variable) ulong[2] ts
ts
[0], null);
int core.sys.posix.pthread.pthread_join(ulong, void**) nothrow @nogc
pthread_join
(
(local variable) ulong[2] ts
ts
[1], null);
static __gshared char[96]
(__gshared global) char[96] app.probeConnectionPerThread.d
d
;
int core.stdc.stdio.snprintf(scope char* s, ulong n, scope const(char*) format, scope const ...) nothrow @nogc
snprintf
(
(__gshared global) char[96] app.probeConnectionPerThread.d
d
.
(constant) char* char[96].ptr = &d
ptr
,
(__gshared global) char[96] app.probeConnectionPerThread.d
d
.
(constant) ulong char[96].length = 96LU
length
, "threads_completed=%d/2 model=connection_per_thread",
int core.atomic.atomicLoad!(MemoryOrder.seq, int)(ref return scope shared(const(int)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(int) app.g_p5Ok
g_p5Ok
));
void app.verdict(scope const(char)* result, scope const(char)* detail) nothrow @nogc

The one line the spec requires per run. Async-signal-safe on purpose (snprintf into a static buffer + write(2)) so the signal handlers can call it; the normal path uses it too so the format is identical.

verdict
(
int core.atomic.atomicLoad!(MemoryOrder.seq, int)(ref return scope shared(const(int)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(int) app.g_p5Ok
g_p5Ok
) == 2 ? "ok" : "error",
(__gshared global) char[96] app.probeConnectionPerThread.d
d
.
(constant) char* char[96].ptr = &d
ptr
);
return 0; } // -- probe 6: read-intent protocol violated ------------------------------------- private shared bool
(shared global) shared(bool) app.g_prepared
g_prepared
;
private shared bool
(shared global) shared(bool) app.g_holderDone
g_holderDone
;
private shared bool
(shared global) shared(bool) app.g_violatorDone
g_violatorDone
;
private shared int
(shared global) shared(int) app.g_violatorRet
g_violatorRet
;
private shared long
(shared global) shared(long) app.g_violatorUs
g_violatorUs
;
extern (C) void*
void* app.intentHolder(void* __param_0) nothrow @nogc
intentHolder
(void*) @nogc nothrow
{ auto
(local variable) _error_ display
display
= g_ctx.
(field) _error_ g_ctx.display
display
;
while (wl_display_prepare_read(display) != 0)
undefined identifier `wl_display_prepare_read`
wl_display_dispatch_pending(display);
undefined identifier `wl_display_dispatch_pending`
emit("thread=holder action=prepare_read_acquired");
undefined identifier `emit`
void core.atomic.atomicStore!(MemoryOrder.seq, bool, bool)(ref shared(bool) val, bool newval) pure nothrow @nogc @trusted

Writes 'newval' into 'val'. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.rel, and MemoryOrder.seq.

@paramval The target variable.@paramnewval The value to store.
atomicStore
(
(shared global) shared(bool) app.g_prepared
g_prepared
, true);
int core.sys.posix.unistd.usleep(uint) nothrow @nogc @trusted
usleep
(600_000); // hold the read intent while the violator strikes
wl_display_cancel_read(display);
undefined identifier `wl_display_cancel_read`
emit("thread=holder action=cancel_read_returned");
undefined identifier `emit`
void core.atomic.atomicStore!(MemoryOrder.seq, bool, bool)(ref shared(bool) val, bool newval) pure nothrow @nogc @trusted

Writes 'newval' into 'val'. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.rel, and MemoryOrder.seq.

@paramval The target variable.@paramnewval The value to store.
atomicStore
(
(shared global) shared(bool) app.g_holderDone
g_holderDone
, true);
return null; } extern (C) void*
void* app.readViolator(void* __param_0) nothrow @nogc
readViolator
(void*) @nogc nothrow
{ const
(local variable) const(_error_) t0
t0
= nowUs();
undefined identifier `nowUs`
// The violation: read_events without this thread ever calling // prepare_read. The documented contract pairs them strictly 1:1. const
(local variable) const(_error_) r
r
= wl_display_read_events(g_ctx.
g_ctx.display
display
);
undefined identifier `wl_display_read_events`
(template function) core.atomic.atomicStore(MemoryOrder ms = MemoryOrder.seq, T, V)(ref T val, V newval) if (!is(T == shared) && !is(V == shared))
atomicStore
(
(shared global) shared(long) app.g_violatorUs
g_violatorUs
, nowUs() - t0);
undefined identifier `nowUs`
(template function) core.atomic.atomicStore(MemoryOrder ms = MemoryOrder.seq, T, V)(ref T val, V newval) if (!is(T == shared) && !is(V == shared))
atomicStore
(
(shared global) shared(int) app.g_violatorRet
g_violatorRet
, r);
emitf("thread=violator", "action=read_events_returned ret=%d took_us=%lld",
undefined identifier `emitf`
r, atomicLoad(g_violatorUs));
void core.atomic.atomicStore!(MemoryOrder.seq, bool, bool)(ref shared(bool) val, bool newval) pure nothrow @nogc @trusted

Writes 'newval' into 'val'. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.rel, and MemoryOrder.seq.

@paramval The target variable.@paramnewval The value to store.
atomicStore
(
(shared global) shared(bool) app.g_violatorDone
g_violatorDone
, true);
return null; } int
int app.probePrepareReadViolation() nothrow @nogc
probePrepareReadViolation
() @nogc nothrow
{ g_ctx.
g_ctx.display
display
= wl_display_connect(null);
if (g_ctx.
(field) _error_ g_ctx.display
display
is null)
{
void app.verdict(scope const(char)* result, scope const(char)* detail) nothrow @nogc

The one line the spec requires per run. Async-signal-safe on purpose (snprintf into a static buffer + write(2)) so the signal handlers can call it; the normal path uses it too so the format is identical.

verdict
("error", "no_compositor_in_child");
return 0; } pthread_t
(local variable) ulong holder
holder
,
(local variable) ulong violator
violator
;
int core.sys.posix.pthread.pthread_create(ulong*, scope const(core.sys.posix.sys.types.pthread_attr_t*), extern (C) void* function(void*), void*) nothrow @nogc
pthread_create
(&
(local variable) ulong holder
holder
, null, &
void* app.intentHolder(void* __param_0) nothrow @nogc
intentHolder
, null);
while (!
bool core.atomic.atomicLoad!(MemoryOrder.seq, bool)(ref return scope shared(const(bool)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(bool) app.g_prepared
g_prepared
))
int core.sys.posix.unistd.usleep(uint) nothrow @nogc @trusted
usleep
(1000);
int core.sys.posix.pthread.pthread_create(ulong*, scope const(core.sys.posix.sys.types.pthread_attr_t*), extern (C) void* function(void*), void*) nothrow @nogc
pthread_create
(&
(local variable) ulong violator
violator
, null, &
void* app.readViolator(void* __param_0) nothrow @nogc
readViolator
, null);
// Timebox the violator: it may block forever inside read_events. const
(local variable) const(_error_) deadline
deadline
= nowUs() + 3_000_000;
undefined identifier `nowUs`
while (!
bool core.atomic.atomicLoad!(MemoryOrder.seq, bool)(ref return scope shared(const(bool)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(bool) app.g_violatorDone
g_violatorDone
) && nowUs() < deadline)
undefined identifier `nowUs`
int core.sys.posix.unistd.usleep(uint) nothrow @nogc @trusted
usleep
(10_000);
static __gshared char[256]
(__gshared global) char[256] app.probePrepareReadViolation.d
d
;
if (!
bool core.atomic.atomicLoad!(MemoryOrder.seq, bool)(ref return scope shared(const(bool)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(bool) app.g_violatorDone
g_violatorDone
))
{
int core.stdc.stdio.snprintf(scope char* s, ulong n, scope const(char*) format, scope const ...) nothrow @nogc
snprintf
(
(__gshared global) char[256] app.probePrepareReadViolation.d
d
.
(constant) char* char[256].ptr = &d
ptr
,
(__gshared global) char[256] app.probePrepareReadViolation.d
d
.
(constant) ulong char[256].length = 256LU
length
,
"read_events_without_prepare_blocked_3s holder_done=%d", cast(int)
bool core.atomic.atomicLoad!(MemoryOrder.seq, bool)(ref return scope shared(const(bool)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(bool) app.g_holderDone
g_holderDone
));
void app.verdict(scope const(char)* result, scope const(char)* detail) nothrow @nogc

The one line the spec requires per run. Async-signal-safe on purpose (snprintf into a static buffer + write(2)) so the signal handlers can call it; the normal path uses it too so the format is identical.

verdict
("deadlock",
(__gshared global) char[256] app.probePrepareReadViolation.d
d
.
(constant) char* char[256].ptr = &d
ptr
);
noreturn core.sys.posix.unistd._exit(int) nothrow @nogc @trusted
_exit
(0); // cannot join the stuck thread; verdict is flushed
}
int core.sys.posix.pthread.pthread_join(ulong, void**) nothrow @nogc
pthread_join
(
(local variable) ulong violator
violator
, null);
while (!
bool core.atomic.atomicLoad!(MemoryOrder.seq, bool)(ref return scope shared(const(bool)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(bool) app.g_holderDone
g_holderDone
))
int core.sys.posix.unistd.usleep(uint) nothrow @nogc @trusted
usleep
(10_000);
int core.sys.posix.pthread.pthread_join(ulong, void**) nothrow @nogc
pthread_join
(
(local variable) ulong holder
holder
, null);
// Health check: does the connection still work after the violation?
(__gshared global) const(char)* app.g_watchdogDetail
g_watchdogDetail
= "health_roundtrip_hung_after_violation";
uint core.sys.posix.unistd.alarm(uint) nothrow @nogc @trusted
alarm
(4);
const
(local variable) const(_error_) rt
rt
= wl_display_roundtrip(g_ctx.
g_ctx.display
display
);
undefined identifier `wl_display_roundtrip`
uint core.sys.posix.unistd.alarm(uint) nothrow @nogc @trusted
alarm
(0);
const
(local variable) const(_error_) err
err
= wl_display_get_error(g_ctx.
g_ctx.display
display
);
undefined identifier `wl_display_get_error`
int core.stdc.stdio.snprintf(scope char* s, ulong n, scope const(char*) format, scope const ...) nothrow @nogc
snprintf
(
(__gshared global) char[256] app.probePrepareReadViolation.d
d
.
(constant) char* char[256].ptr = &d
ptr
,
(__gshared global) char[256] app.probePrepareReadViolation.d
d
.
(constant) ulong char[256].length = 256LU
length
,
"read_events_no_prepare ret=%d took_us=%lld health_roundtrip=%d display_error=%d (nondeterministic)",
int core.atomic.atomicLoad!(MemoryOrder.seq, int)(ref return scope shared(const(int)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(int) app.g_violatorRet
g_violatorRet
),
long core.atomic.atomicLoad!(MemoryOrder.seq, long)(ref return scope shared(const(long)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(long) app.g_violatorUs
g_violatorUs
), rt, err);
void app.verdict(scope const(char)* result, scope const(char)* detail) nothrow @nogc

The one line the spec requires per run. Async-signal-safe on purpose (snprintf into a static buffer + write(2)) so the signal handlers can call it; the normal path uses it too so the format is identical.

verdict
(err != 0 || rt < 0 ? "error" : "silent",
(__gshared global) char[256] app.probePrepareReadViolation.d
d
.
(constant) char* char[256].ptr = &d
ptr
);
wl_display_disconnect(g_ctx.
g_ctx.display
display
);
undefined identifier `wl_display_disconnect`
return 0; } // --------------------------------------------------------------------------- int
int app.runProbe(int n) nothrow @nogc
runProbe
(int
(parameter) int n
n
) @nogc nothrow
{
(__gshared global) int app.g_probe
g_probe
=
(parameter) int n
n
;
void app.installCrashHandlers() nothrow @nogc
installCrashHandlers
();
emitf("probe_start", "n=%d", n);
undefined identifier `emitf`
switch (
(parameter) int n
n
)
{ case 1:
int app.probeWindowOnWorker() nothrow @nogc
probeWindowOnWorker
();
break; case 2:
int app.probeConcurrentDispatch() nothrow @nogc
probeConcurrentDispatch
();
break; case 3:
int app.probePerThreadQueue() nothrow @nogc
probePerThreadQueue
();
break; case 4:
int app.probeRenderThread() nothrow @nogc
probeRenderThread
();
break; case 5:
int app.probeConnectionPerThread() nothrow @nogc
probeConnectionPerThread
();
break; case 6:
int app.probePrepareReadViolation() nothrow @nogc
probePrepareReadViolation
();
break; default:
void app.verdict(scope const(char)* result, scope const(char)* detail) nothrow @nogc

The one line the spec requires per run. Async-signal-safe on purpose (snprintf into a static buffer + write(2)) so the signal handlers can call it; the normal path uses it too so the format is identical.

verdict
("error", "unknown_probe");
}
uint core.sys.posix.unistd.alarm(uint) nothrow @nogc @trusted
alarm
(0);
return 0; } int
int D main(string[] args)
main
(
(alias) object.string = string
string
[]
(parameter) string[] args
args
)
{ initInstrument("f17_wayland");
undefined identifier `initInstrument`
int
(local variable) int probe
probe
= 0;
foreach (
(parameter) string a
a
;
(parameter) string[] args
args
[1 .. $])
if (
(local variable) string a
a
.
(field) ulong string.length
length
> 8 &&
(local variable) string a
a
[0 .. 8] == "--probe=")
(local variable) int probe
probe
=
(local variable) string a
a
[8] - '0';
// Capability gate in the parent, before any fork. wl_display*
(local variable) _error_ test
test
= wl_display_connect(null);
undefined identifier `wl_display`
undefined identifier `wl_display_connect`
if (test is null) {
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogc
printf
("SKIP: no Wayland compositor (wl_display_connect returned null)\n");
return 0; } wl_display_disconnect(test);
undefined identifier `wl_display_disconnect`
if (
(local variable) int probe
probe
!= 0)
return
int app.runProbe(int n) nothrow @nogc
runProbe
(
(local variable) int probe
probe
);
// No argument: run the full matrix, each probe TWICE (the F17 spec's // nondeterminism rule), each in a forked child so a wedged libwayland or // a caught crash never poisons the next probe. Children always _exit(0). foreach (
(local variable) int n
n
; 1 .. 7)
foreach (
(local variable) int run
run
; 1 .. 3)
{ const
(local variable) const(int) pid
pid
=
int core.sys.posix.unistd.fork() nothrow @nogc @trusted
fork
();
if (
(local variable) const(int) pid
pid
== 0)
{
int app.runProbe(int n) nothrow @nogc
runProbe
(
(local variable) int n
n
);
noreturn core.sys.posix.unistd._exit(int) nothrow @nogc @trusted
_exit
(0);
} int
(local variable) int status
status
;
int core.sys.posix.sys.wait.waitpid(int, int*, int) nothrow @nogc
waitpid
(
(local variable) const(int) pid
pid
, &
(local variable) int status
status
, 0);
emitf("probe_child", "n=%d run=%d wait_status=%d", n, run, status);
undefined identifier `emitf`
} emit("teardown all_probes_done");
undefined identifier `emit`
return 0; }