// X11 F05 demo — loop wakeup & external fds (../../../features/f05-loop-wakeup.md).
// Built on the scaffold (../scaffold/app.d): same ImportC binding style, same
// poll(2)-driven readiness loop, same instrument.d event log.
//
// What it measures (F05 requirements 1-3):
//
// * Mechanism A (`mech=clientmessage`): a second thread with its OWN
// Display* connection injects a ClientMessage into the main window via
// XSendEvent + XFlush. This is the documented thread-safe injection
// without XInitThreads: Xlib is only thread-unsafe when two threads share
// one Display, so one-connection-per-thread sidesteps locking entirely.
// The event makes a full round-trip through the X server (thread socket
// -> server -> main-loop socket).
// * Mechanism B (`mech=eventfd`): the same thread writes an eventfd(2) that
// the main loop poll(2)s alongside ConnectionNumber(dpy) — no server
// involvement, a pure kernel futex/wait wake.
// * An arbitrary external fd: a periodic timerfd(2) in the same poll set,
// logged as `fd_tick` interleaved with window events.
//
// Each mechanism fires 10x/second for 30 s (WSI_DURATION_MS overrides),
// offset by 50 ms so they interleave; every wakeup logs
// `wakeup latency_us=… mech=…` computed from a monotonic timestamp carried
// in the event (ClientMessage: split across two 32-bit data.l slots — the
// wire format truncates each slot to 32 bits) or in an atomic side-channel
// (eventfd is a counter, not a queue). min/p50/p99/max per mechanism at exit.
//
// Headless-safe: no X server -> prints `SKIP:` and exits 0. WSI_AUTO_EXIT=1
// bounds the run (exits after the wakeup phase completes). Findings:
// ../../f05-loop-wakeup.md.
module (module) appapp;
import (module) cc; // ImportC: Xlib + poll + eventfd + timerfd + unistd
import (module) instrumentinstrument;
import (package) corecore.(module) core.atomicThe 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);
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.
atomicLoad, (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.
atomicStore;
import (package) corecore.(package) core.stdcstdc.(module) core.stdc.configD compatible types that correspond to various basic types in associated
C and C++ compilers.
Source
core/stdc/config.d
config : c_long, c_ulong;
import (package) corecore.(package) core.stdcstdc.(module) core.stdc.stdioD header file for C99 <stdio.h>
pubs.opengroup.org/onlinepubs/009695399/basedefs/stdio.h.html, stdio.h
Source
core/stdc/stdio.d
stdio : (alias) app.printf = int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogcprintf;
import (package) corecore.(package) core.stdcstdc.(module) core.stdc.stdlibD header file for C99.
pubs.opengroup.org/onlinepubs/009695399/basedefs/stdlib.h.html, stdlib.h
Source
core/stdc/stdlib.d
stdlib : (alias) app.atoi = int core.stdc.stdlib.atoi(scope const(char*) nptr) nothrow @nogcatoi, (alias) app.getenv = char* core.stdc.stdlib.getenv(scope const(char*) name) nothrow @nogcgetenv, (alias) app.qsort = void core.stdc.stdlib.qsort(void* base, ulong nmemb, ulong size, extern (C) int function(const(void*), const(void*)) compar) nothrow @nogcqsort;
import (package) corecore.(package) core.threadthread.(module) core.thread.osthreadThe osthread module provides low-level, OS-dependent code
for thread creation and management.
Source
core/thread/osthread.d
osthread : (class) core.thread.osthread.ThreadThis class encapsulates all threading functionality for the D
programming language. As thread manipulation is a required facility
for garbage collection, all user threads should derive from this
class, and instances of this class should never be explicitly deleted.
A new thread may be created using either derivation or composition, as
in the following example.
Thread;
import (package) corecore.(module) core.timeModule containing core time functionality, such as Duration (which
represents a duration of time) or MonoTime (which represents a
timestamp of the system's monotonic clock).
Various functions take a string (or strings) to represent a unit of time
(e.g. convert!("days", "hours")(numDays)). The valid strings to use
with such functions are "years", "months", "weeks", "days", "hours",
"minutes", "seconds", "msecs" (milliseconds), "usecs" (microseconds),
"hnsecs" (hecto-nanoseconds - i.e. 100 ns) or some subset thereof. There
are a few functions that also allow "nsecs", but very little actually
has precision greater than hnsecs.
Symbol Description Types Duration Represents a duration of time of weeks or less (kept internally as hnsecs). (e.g. 22 days or 700 seconds). TickDuration DEPRECATED Represents a duration of time in system clock ticks, using the highest precision that the system provides. MonoTime Represents a monotonic timestamp in system clock ticks, using the highest precision that the system provides. Functions convert Generic way of converting between two time units. dur Allows constructing a Duration from the given time units with the given length. weeks days hours
minutes seconds msecs
usecs hnsecs nsecs |
Convenience aliases for dur. |
| abs | Returns the absolute value of a duration. |
From Duration
From TickDuration
From units
To Duration tickDuration.to, std,conv!Duration() dur!"msecs"(5) or 5.msecs()
| To TickDuration |
duration.to, std,conv!TickDuration() |
|
TickDuration.from!"msecs"(msecs) |
| To units |
duration.total!"days" |
tickDuration.msecs |
convert!("days", "msecs")(msecs) |
Source
core/time.d
time : msecs;
// ---------------------------------------------------------------------------
// Constants Xlib/glibc expose as macros that ImportC cannot import
// (expression macros like `(1L<<15)`); re-declared per the scaffold gotcha.
enum : c_long
{
(enum value) app.KeyPressMask = 1LKeyPressMask = 1L << 0,
(enum value) app.ExposureMask = 32768LExposureMask = 1L << 15,
(enum value) app.StructureNotifyMask = 131072LStructureNotifyMask = 1L << 17,
}
enum // XEvent.type discriminators
{
(enum value) app.KeyPress = 2KeyPress = 2,
(enum value) app.Expose = 12Expose = 12,
(enum value) app.MapNotify = 19MapNotify = 19,
(enum value) app.ConfigureNotify = 22ConfigureNotify = 22,
(enum value) app.ClientMessage = 33ClientMessage = 33,
}
enum (constant) int app.False = 0False = 0;
enum (constant) int app.True = 1True = 1;
enum (constant) int app.POLLIN = 1POLLIN = 0x001;
enum (constant) int app.CLOCK_MONOTONIC = 1CLOCK_MONOTONIC = 1;
enum (constant) int app.EFD_CLOEXEC = 524288EFD_CLOEXEC = 0x80000; // 02000000
enum (constant) int app.EFD_NONBLOCK = 2048EFD_NONBLOCK = 0x800; // 00004000
enum (constant) int app.TFD_CLOEXEC = 524288TFD_CLOEXEC = 0x80000;
enum (constant) int app.WAKE_MAGIC = 1463896901WAKE_MAGIC = 0x57414b45; // "WAKE" — data.l[0] tag of our ClientMessage
// ---------------------------------------------------------------------------
// Worker-thread <-> main-loop shared state. The worker owns its own Display;
// the only data crossing threads outside X are these atomics + the eventfd.
__gshared Window _error_ app.g_wing_win; // injection target (XIDs are connection-independent)
__gshared Atom _error_ app.g_wakeAtomg_wakeAtom; // atoms are server-global ids, safe to share
__gshared int (__gshared global) int app.g_efdg_efd = -1;
__gshared long (__gshared global) long app.g_efdStampg_efdStamp; // nowUs() at the instant of the eventfd write
__gshared int (__gshared global) int app.g_wakeupsPerMechg_wakeupsPerMech = 300; // 10/s for 30 s
__gshared bool (__gshared global) bool app.g_workerDoneg_workerDone;
/// The injector thread (F05 requirement 1). Opens its own connection — the
/// documented no-XInitThreads-needed pattern — and alternates the two
/// mechanisms on a 50 ms grid, so each one fires 10x/s.
void void app.injectorThread()The injector thread (F05 requirement 1). Opens its own connection — the
documented no-XInitThreads-needed pattern — and alternates the two
mechanisms on a 50 ms grid, so each one fires 10x/s.
injectorThread()
{
Display* (local variable) _error_ d2d2 = XOpenDisplay(null);
if (d2 is null)
{
void core.atomic.atomicStore!(MemoryOrder.seq, bool, bool)(ref bool val, bool newval) pure nothrow @nogc @trustedWrites '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.
atomicStore((__gshared global) bool app.g_workerDoneg_workerDone, true);
return;
}
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("step", "name=XOpenDisplay conn=injector fd=%d", XConnectionNumber(d2));
const (local variable) const(long) t0t0 = long instrument.nowUs() nothrow @nogcMicroseconds elapsed since initInstrument.
nowUs();
foreach ((local variable) int ii; 0 .. 2 * (__gshared global) int app.g_wakeupsPerMechg_wakeupsPerMech)
{
const (local variable) const(long) targettarget = (local variable) const(long) t0t0 + ((local variable) int ii + 1) * 50_000L;
const (local variable) const(long) waitwait = (local variable) const(long) targettarget - long instrument.nowUs() nothrow @nogcMicroseconds elapsed since initInstrument.
nowUs();
if ((local variable) const(long) waitwait > 0)
(class) core.thread.osthread.ThreadThis class encapsulates all threading functionality for the D
programming language. As thread manipulation is a required facility
for garbage collection, all user threads should derive from this
class, and instances of this class should never be explicitly deleted.
A new thread may be created using either derivation or composition, as
in the following example.
Thread.void core.thread.osthread.Thread.sleep(core.time.Duration val) nothrow @nogc @trustedSuspends the calling thread for at least the supplied period. This may
result in multiple OS calls if period is greater than the maximum sleep
duration supported by the operating system.
In
period must be non-negative.
Example
Thread.sleep( dur!("msecs")( 50 ) ); // sleep for 50 milliseconds
Thread.sleep( dur!("seconds")( 5 ) ); // sleep for 5 seconds
sleep(((local variable) const(long) waitwait / 1000).msecs);
if ((local variable) int ii % 2 == 0) // mechanism A: ClientMessage through the server
{
XEvent (local variable) _error_ evev;
ev.xclient.type = ClientMessage;
ev.xclient.window = g_win;
ev.xclient.message_type = g_wakeAtom;
ev.xclient.format = 32;
const (local variable) const(long) tsts = long instrument.nowUs() nothrow @nogcMicroseconds elapsed since initInstrument.
nowUs();
ev.xclient.data.l[0] = WAKE_MAGIC;
ev.xclient.data.l[1] = cast(c_long)(ts >> 32); // wire slots are
ev.xclient.data.l[2] = cast(c_long)(ts & 0xffff_ffffL); // 32-bit
// event_mask=0: deliver to the client that created g_win.
XSendEvent(d2, g_win, False, 0, &ev);
XFlush(d2); // scaffold gotcha: nothing moves until the flush
}
else // mechanism B: eventfd, kernel-only
{
void core.atomic.atomicStore!(MemoryOrder.seq, long, long)(ref long val, long newval) pure nothrow @nogc @trustedWrites '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.
atomicStore((__gshared global) long app.g_efdStampg_efdStamp, long instrument.nowUs() nothrow @nogcMicroseconds elapsed since initInstrument.
nowUs());
ulong (local variable) ulong oneone = 1;
write(g_efd, &one, one.one.sizeofsizeof);
}
}
XCloseDisplay(d2);
void core.atomic.atomicStore!(MemoryOrder.seq, bool, bool)(ref bool val, bool newval) pure nothrow @nogc @trustedWrites '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.
atomicStore((__gshared global) bool app.g_workerDoneg_workerDone, true);
}
// ---------------------------------------------------------------------------
// Latency bookkeeping.
struct (struct) app.SeriesSeries
{
long[4096] (field) long[4096] app.Series.vv;
int (field) int app.Series.nn;
void void app.Series.record(long x) nothrow @nogcrecord(long (parameter) long xx) @nogc nothrow
{
if ((field) int app.Series.nn < (field) long[4096] app.Series.vv.(constant) ulong long[4096].length = 4096LUlength)
(field) long[4096] app.Series.vv[(field) int app.Series.nn++] = (parameter) long xx;
}
long long app.Series.at(double q) nothrow @nogcat(double (parameter) double qq) @nogc nothrow // q in [0,1] on the sorted array
{
auto (local variable) int ii = cast(int)((parameter) double qq * ((field) int app.Series.nn - 1) + 0.5);
return (field) long[4096] app.Series.vv[(local variable) int ii < 0 ? 0 : ((local variable) int ii >= (field) int app.Series.nn ? (field) int app.Series.nn - 1 : (local variable) int ii)];
}
}
extern (C) int int app.cmpLong(const(void*) a, const(void*) b) nothrow @nogccmpLong(const void* (parameter) const(void*) aa, const void* (parameter) const(void*) bb) @nogc nothrow
{
const (local variable) const(long) xx = *cast(const long*) (parameter) const(void*) aa, (local variable) const(long) yy = *cast(const long*) (parameter) const(void*) bb;
return ((local variable) const(long) xx > (local variable) const(long) yy) - ((local variable) const(long) xx < (local variable) const(long) yy);
}
void void app.emitStats(const(char)* mech, ref app.Series s) nothrow @nogcemitStats(const(char)* (parameter) const(char)* mechmech, ref (struct) app.SeriesSeries (parameter) app.Series ss) @nogc nothrow
{
if ((parameter) app.Series ss.(field) int app.Series.nn == 0)
return;
void core.stdc.stdlib.qsort(void* base, ulong nmemb, ulong size, extern (C) int function(const(void*), const(void*)) compar) nothrow @nogcqsort((parameter) app.Series ss.(field) long[4096] app.Series.vv.(constant) long* long[4096].ptr = &s.vptr, (parameter) app.Series ss.(field) int app.Series.nn, long.(constant) ulong long.sizeof = 8LUsizeof, &int app.cmpLong(const(void*) a, const(void*) b) nothrow @nogccmpLong);
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("stats", "mech=%s n=%d min=%lld p50=%lld p99=%lld max=%lld",
(parameter) const(char)* mechmech, (parameter) app.Series ss.(field) int app.Series.nn, (parameter) app.Series ss.(field) long[4096] app.Series.vv[0], (parameter) app.Series ss.long app.Series.at(double q) nothrow @nogcat(0.50), (parameter) app.Series ss.long app.Series.at(double q) nothrow @nogcat(0.99), (parameter) app.Series ss.(field) long[4096] app.Series.vv[(parameter) app.Series ss.(field) int app.Series.nn - 1]);
}
int int D main()main()
{
void instrument.initInstrument(const(char)* demoName) nothrow @nogcNames the demo, starts the monotonic clock, and emits init_start.
Call this first, before any platform API call.
initInstrument("f05_x11");
const (local variable) const(char*) envAutoenvAuto = char* core.stdc.stdlib.getenv(scope const(char*) name) nothrow @nogcgetenv("WSI_AUTO_EXIT");
const (local variable) const(bool) autoExitautoExit = (local variable) const(char*) envAutoenvAuto !is null && (local variable) const(char*) envAutoenvAuto[0] == '1';
const (local variable) const(char*) envDurenvDur = char* core.stdc.stdlib.getenv(scope const(char*) name) nothrow @nogcgetenv("WSI_DURATION_MS");
const (local variable) const(int) durationMsdurationMs = (local variable) const(char*) envDurenvDur !is null ? int core.stdc.stdlib.atoi(scope const(char*) nptr) nothrow @nogcatoi((local variable) const(char*) envDurenvDur) : 30_000;
(__gshared global) int app.g_wakeupsPerMechg_wakeupsPerMech = (local variable) const(int) durationMsdurationMs / 100; // each mechanism fires every 100 ms
// -- Connect + window (scaffold sequence, minus the SHM backbuffer) ------
Display* (local variable) _error_ dpydpy = XOpenDisplay(null);
if (dpy is null)
{
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogcprintf("SKIP: no X11 display (XOpenDisplay returned null)\n");
return 0;
}
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("step", "name=XOpenDisplay conn=main fd=%d", XConnectionNumber(dpy));
const (local variable) const(_error_) screenscreen = XDefaultScreen(dpy);
Window (local variable) _error_ winwin = XCreateSimpleWindow(dpy, XRootWindow(dpy, screen), 0, 0,
480, 320, 1, XBlackPixel(dpy, screen), XWhitePixel(dpy, screen));
XStoreName(dpy, win, "Sparkles · X11 F05 loop wakeup");
Atom (local variable) _error_ wmDeletewmDelete = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
XSetWMProtocols(dpy, win, &wmDelete, 1);
Atom (local variable) _error_ wakeAtomwakeAtom = XInternAtom(dpy, "SPARKLES_WSI_WAKEUP", False);
XSelectInput(dpy, win, ExposureMask | KeyPressMask | StructureNotifyMask);
XMapWindow(dpy, win);
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("window_created", "xid=0x%lx size=480x320", win);
// -- The two external fds (F05 requirement 2) -----------------------------
const (local variable) const(_error_) efdefd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("step", "name=eventfd fd=%d", efd);
const (local variable) const(_error_) tfdtfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
itimerspec (local variable) _error_ itsits;
its.it_interval.tv_sec = 0;
its.it_interval.tv_nsec = 250_000_000; // 4 Hz probe tick
its.it_value = its.it_interval;
timerfd_settime(tfd, 0, &its, null);
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("step", "name=timerfd fd=%d period_ms=250", tfd);
g_win = win;
g_wakeAtom = wakeAtom;
(__gshared global) int app.g_efdg_efd = efd;
auto (local variable) core.thread.osthread.Thread workerworker = new (class) core.thread.osthread.ThreadThis class encapsulates all threading functionality for the D
programming language. As thread manipulation is a required facility
for garbage collection, all user threads should derive from this
class, and instances of this class should never be explicitly deleted.
A new thread may be created using either derivation or composition, as
in the following example.
Thread(&void app.injectorThread()The injector thread (F05 requirement 1). Opens its own connection — the
documented no-XInitThreads-needed pattern — and alternates the two
mechanisms on a 50 ms grid, so each one fires 10x/s.
injectorThread);
(local variable) core.thread.osthread.Thread workerworker.core.thread.osthread.Thread core.thread.osthread.Thread.start() nothrowStarts the thread and invokes the function or delegate passed upon
construction.
In
This routine may only be called once per thread instance.
start();
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("step", "name=thread_start wakeups_per_mech=%d period_ms=100", (__gshared global) int app.g_wakeupsPerMechg_wakeupsPerMech);
(struct) app.SeriesSeries (local variable) app.Series latClientMsglatClientMsg, (local variable) app.Series latEventfdlatEventfd;
int (local variable) int fdTicksfdTicks = 0, (local variable) int framesframes = 0;
bool (local variable) bool runningrunning = true, (local variable) bool sawFirstPixelsawFirstPixel = false;
long (local variable) long doneAtUsdoneAtUs = 0;
// -- Readiness loop: ConnectionNumber fd + eventfd + timerfd in one poll --
while ((local variable) bool runningrunning)
{
while (XPending(dpy) > 0) // also flushes the output buffer
{
XEvent (local variable) _error_ evev;
XNextEvent(dpy, &ev);
switch (ev.type)
{
case Expose:
if (ev.xexpose.ev.xexpose.countcount == 0)
{
XFillRectangle(dpy, win, XDefaultGC(dpy, screen), 20, 20, 440, 280);
XFlush(dpy);
++frames;
if (!sawFirstPixel)
{
sawFirstPixel = true;
emit("first_pixel_presented method=XFillRectangle");
}
}
break;
case ClientMessage:
if (ev.xclient.message_type == wakeAtom
&& ev.xclient.data.l[0] == WAKE_MAGIC)
{
// Reassemble the 64-bit monotonic stamp from the two
// sign-extended 32-bit wire slots.
const _error_ tsts = (cast(long) ev.xclient.data.l[1] << 32)
| cast(uint) ev.xclient.data.l[2];
const _error_ latlat = nowUs() - ts;
latClientMsg.latClientMsg.recordrecord(lat);
emitf("wakeup", "latency_us=%lld mech=clientmessage", lat);
}
else if (cast(Atom) ev.xclient.data.l[0] == wmDelete)
{
emit("close_requested via=WM_DELETE_WINDOW");
running = false;
}
break;
case KeyPress:
emit("close_requested via=KeyPress");
running = false;
break;
default:
break;
}
}
if (bool core.atomic.atomicLoad!(MemoryOrder.seq, bool)(ref return scope const(bool) val) pure nothrow @nogc @trustedLoads '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.
atomicLoad((__gshared global) bool app.g_workerDoneg_workerDone))
{
if ((local variable) long doneAtUsdoneAtUs == 0)
(local variable) long doneAtUsdoneAtUs = long instrument.nowUs() nothrow @nogcMicroseconds elapsed since initInstrument.
nowUs();
else if ((local variable) const(bool) autoExitautoExit && long instrument.nowUs() nothrow @nogcMicroseconds elapsed since initInstrument.
nowUs() - (local variable) long doneAtUsdoneAtUs > 300_000) // drain grace
{
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("auto_exit", "frames=%d", (local variable) int framesframes);
break;
}
}
pollfd[3] (local variable) _error_ pfdspfds;
pfds[0].fd = XConnectionNumber(dpy);
pfds[1].fd = efd;
pfds[2].fd = tfd;
foreach (ref (parameter) pp; pfds)
{
p.events = POLLIN;
p.revents = 0;
}
poll(pfds.pfds.ptrptr, 3, 50);
if (pfds[1].revents & POLLIN) // mechanism B fired
{
ulong (local variable) ulong countcount;
read(efd, &count, count.count.sizeofsizeof);
const (local variable) const(long) latlat = long instrument.nowUs() nothrow @nogcMicroseconds elapsed since initInstrument.
nowUs() - long core.atomic.atomicLoad!(MemoryOrder.seq, long)(ref return scope const(long) val) pure nothrow @nogc @trustedLoads '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.
atomicLoad((__gshared global) long app.g_efdStampg_efdStamp);
(local variable) app.Series latEventfdlatEventfd.void app.Series.record(long x) nothrow @nogcrecord((local variable) const(long) latlat);
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("wakeup", "latency_us=%lld mech=eventfd coalesced=%llu", (local variable) const(long) latlat, (local variable) ulong countcount);
}
if (pfds[2].revents & POLLIN) // the arbitrary-fd probe
{
ulong (local variable) ulong expirationsexpirations;
read(tfd, &expirations, expirations.expirations.sizeofsizeof);
++(local variable) int fdTicksfdTicks;
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("fd_tick", "t=%lld expirations=%llu src=timerfd", long instrument.nowUs() nothrow @nogcMicroseconds elapsed since initInstrument.
nowUs(), (local variable) ulong expirationsexpirations);
}
}
// -- Stats + teardown (F05 requirement 3) ---------------------------------
void app.emitStats(const(char)* mech, ref app.Series s) nothrow @nogcemitStats("clientmessage", (local variable) app.Series latClientMsglatClientMsg);
void app.emitStats(const(char)* mech, ref app.Series s) nothrow @nogcemitStats("eventfd", (local variable) app.Series latEventfdlatEventfd);
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("stats", "mech=timerfd ticks=%d period_ms=250", (local variable) int fdTicksfdTicks);
(local variable) core.thread.osthread.Thread workerworker.object.Throwable core.thread.osthread.Thread.join(bool rethrow = true)Waits for this thread to complete. If the thread terminated as the
result of an unhandled exception, this exception will be rethrown.
join();
close(efd);
close(tfd);
XDestroyWindow(dpy, win);
XCloseDisplay(dpy);
void instrument.emit(scope const(char)* kind) nothrow @nogcEmit an event with no key=value payload.
emit("teardown");
return 0;
}