app.dhover×207 error×71all
// F05 — loop wakeup & external handles, Win32 implementation
// (../../../features/f05-loop-wakeup.md). Extends the scaffold
// (../scaffold/app.d) in three directions:
//
//   * Cross-thread wakeup, mechanism A: a worker thread (raw CreateThread —
//     no druntime registration needed, the thread only touches user32 and
//     QueryPerformanceCounter) posts WM_APP+1 to the window 10x/second for
//     30 s with PostMessageW; the wParam indexes a __gshared QPC-timestamp
//     slot, so the WndProc can compute "wakeup latency_us=... mech=postmessage".
//   * Cross-thread wakeup, mechanism B: the same worker also posts WM_APP+2
//     with PostThreadMessageW to the UI thread id. Thread messages have
//     msg.hwnd == null, so DispatchMessageW would drop them on the floor —
//     they MUST be handled in the pump itself (and an hwnd-filtered
//     GetMessage/PeekMessage never retrieves them: the filter probe below
//     proves it). Latency is logged as mech=threadmessage.
//   * External-handle waiting: the pump is not GetMessageW but
//     MsgWaitForMultipleObjectsEx(1, &timer, INFINITE, QS_ALLINPUT,
//     MWMO_INPUTAVAILABLE) over a CreateWaitableTimerW handle ticking at
//     7 Hz — Win32's answer to "add an arbitrary fd to the loop" is an ARRAY
//     of kernel handles, capped at MAXIMUM_WAIT_OBJECTS-1 = 63. A start-up
//     probe calls the wait with 64 handles to demonstrate the hard failure.
//
// Exit prints min/median/p99/max latency per mechanism plus the waitable
// timer's observed tick-interval distribution. WSI_AUTO_EXIT=1 destroys the
// window once the worker is done (bounded ~31 s run, exit 0).
//
// Only druntime's built-in core.sys.windows bindings — no third-party packages.
module 
(module) app
app
;
import
(package) core
core
.
(package) core.sys
sys
.
(package) core.sys.windows
windows
.
(module) core.sys.windows.windows

Windows API header module

Translated from MinGW API for MS-Windows 4.0

Source

core/sys/windows/windows.d

windows
;
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:
enum UINT
(constant) _error_ app.WM_WAKEUP_POST = __error__
WM_WAKEUP_POST
= WM_APP + 1; // mech A: PostMessageW(hwnd, ...)
undefined identifier `UINT`
undefined identifier `WM_APP`
enum UINT
(constant) _error_ app.WM_WAKEUP_THREAD = __error__
WM_WAKEUP_THREAD
= WM_APP + 2; // mech B: PostThreadMessageW(tid, ...)
undefined identifier `UINT`
undefined identifier `WM_APP`
enum UINT
(constant) _error_ app.WM_WORKER_DONE = __error__
WM_WORKER_DONE
= WM_APP + 3;
undefined identifier `UINT`
undefined identifier `WM_APP`
enum
(constant) int app.POSTS_PER_SECOND = 10
POSTS_PER_SECOND
= 10;
enum
(constant) int app.RUN_SECONDS = 30
RUN_SECONDS
= 30;
enum
(constant) int app.TOTAL_POSTS = 300
TOTAL_POSTS
=
(constant) int app.POSTS_PER_SECOND = 10
POSTS_PER_SECOND
*
(constant) int app.RUN_SECONDS = 30
RUN_SECONDS
; // 300 per mechanism
enum
(constant) int app.TIMER_HZ = 7
TIMER_HZ
= 7; // waitable-timer tick rate
enum
(constant) int app.TIMER_PERIOD_MS = 142
TIMER_PERIOD_MS
= 1000 /
(constant) int app.TIMER_HZ = 7
TIMER_HZ
; // 142 ms (7.04 Hz nominal)
enum
(constant) int app.FILTER_PROBE_SEQ = 150
FILTER_PROBE_SEQ
= 150; // mid-run hwnd-filter probe (see wndProc)
// --------------------------------------------------------------------------- // Shared state. QPC timestamps cross the thread boundary through per-sequence // slots indexed by the message's wParam, so a latency sample never races with // the next post (the worker writes slot i strictly before posting seq i). struct
(struct) app.Demo
Demo
{ HWND
(field) _error_ app.Demo.hwnd
hwnd
;
undefined identifier `HWND`
DWORD
(field) _error_ app.Demo.uiThreadId
uiThreadId
;
undefined identifier `DWORD`
HANDLE
(field) _error_ app.Demo.timer
timer
; // auto-reset waitable timer, 7 Hz
undefined identifier `HANDLE`
HANDLE
(field) _error_ app.Demo.worker
worker
; // CreateThread handle
undefined identifier `HANDLE`
long
(field) long app.Demo.qpcFreq
qpcFreq
; // QueryPerformanceFrequency, counts/s
long
(field) long app.Demo.lastTickQpc
lastTickQpc
; // previous fd_tick, for interval stats
uint
(field) uint app.Demo.tickCount
tickCount
;
bool
(field) bool app.Demo.autoExit
autoExit
;
bool
(field) bool app.Demo.workerDone
workerDone
;
bool
(field) bool app.Demo.probeDone
probeDone
;
} __gshared
(struct) app.Demo
Demo
_error_ app.g
g
;
__gshared long[
(constant) int app.TOTAL_POSTS = 300
TOTAL_POSTS
]
(__gshared global) long[300] app.postStampQpc
postStampQpc
; // written by worker, read by UI thread
__gshared long[
(constant) int app.TOTAL_POSTS = 300
TOTAL_POSTS
]
(__gshared global) long[300] app.threadStampQpc
threadStampQpc
;
// Fixed-capacity sample sets (no allocation; the WndProc is nothrow). struct
(struct) app.Samples
Samples
{ long[
(constant) int app.TOTAL_POSTS = 300
TOTAL_POSTS
]
(field) long[300] app.Samples.buf
buf
;
int
(field) int app.Samples.n
n
;
void
void app.Samples.add(long v) nothrow @nogc
add
(long
(parameter) long v
v
) nothrow @nogc
{ if (
(field) int app.Samples.n
n
<
(field) long[300] app.Samples.buf
buf
.
(constant) ulong long[300].length = 300LU
length
)
(field) long[300] app.Samples.buf
buf
[
(field) int app.Samples.n
n
++] =
(parameter) long v
v
;
} } __gshared
(struct) app.Samples
Samples
(__gshared global) app.Samples app.postLat
postLat
,
(__gshared global) app.Samples app.threadLat
threadLat
,
(__gshared global) app.Samples app.tickIntervals
tickIntervals
;
long
long app.qpcNow() nothrow @nogc
qpcNow
() nothrow @nogc
{ LARGE_INTEGER
(local variable) _error_ t
t
;
undefined identifier `LARGE_INTEGER`
QueryPerformanceCounter(&t);
undefined identifier `QueryPerformanceCounter`
return t.QuadPart; } long
long app.qpcToUs(long delta) nothrow @nogc
qpcToUs
(long
(parameter) long delta
delta
) nothrow @nogc
{ return
(parameter) long delta
delta
* 1_000_000 / g.
(field) _error_ g.qpcFreq
qpcFreq
;
} // --------------------------------------------------------------------------- // Worker thread: a raw kernel thread (CreateThread, extern(Windows) entry). // Every ~100 ms it stamps QPC and fires both mechanisms back to back; both // are documented as callable from any thread targeting another thread's queue. extern (Windows) DWORD
app.workerMain
workerMain
(LPVOID) nothrow
undefined identifier `DWORD`
undefined identifier `LPVOID`
{ foreach (
(parameter) i
i
; 0 .. TOTAL_POSTS)
{ Sleep(1000 / POSTS_PER_SECOND); postStampQpc[i] = qpcNow(); if (!PostMessageW(g.
g.hwnd
hwnd
, WM_WAKEUP_POST, cast(WPARAM) i, 0))
logEvent("error what=PostMessageW seq=%d code=%lu", cast(int) i, GetLastError()); threadStampQpc[i] = qpcNow(); if (!PostThreadMessageW(g.
g.uiThreadId
uiThreadId
, WM_WAKEUP_THREAD, cast(WPARAM) i, 0))
logEvent("error what=PostThreadMessageW seq=%d code=%lu", cast(int) i, GetLastError()); } PostMessageW(g.
g.hwnd
hwnd
, WM_WORKER_DONE, 0, 0);
return 0; } // --------------------------------------------------------------------------- // The 63-handle ceiling, demonstrated: MsgWaitForMultipleObjectsEx accepts at // most MAXIMUM_WAIT_OBJECTS-1 = 63 handles (the message queue itself occupies // the 64th slot). 64 handles fail hard with ERROR_INVALID_PARAMETER. void
void app.probeHandleLimit() nothrow
probeHandleLimit
() nothrow
{ HANDLE[64]
(local variable) _error_ ev
ev
;
undefined identifier `HANDLE`
foreach (
(local variable) int i
i
; 0 .. 64)
ev[i] = CreateEventW(null, FALSE, FALSE, null); SetLastError(0);
undefined identifier `SetLastError`
const
(local variable) const(_error_) r64
r64
= MsgWaitForMultipleObjectsEx(64, ev.ptr, 0, QS_ALLINPUT, 0);
undefined identifier `MsgWaitForMultipleObjectsEx`
logEvent("handle_limit_probe n=64 result=0x%08lx err=%lu", r64, GetLastError());
undefined identifier `logEvent`
SetLastError(0);
undefined identifier `SetLastError`
const
(local variable) const(_error_) r63
r63
= MsgWaitForMultipleObjectsEx(63, ev.ptr, 0, QS_ALLINPUT, 0);
undefined identifier `MsgWaitForMultipleObjectsEx`
logEvent("handle_limit_probe n=63 result=0x%08lx err=%lu", r63, GetLastError());
undefined identifier `logEvent`
foreach (
(local variable) int i
i
; 0 .. 64)
CloseHandle(ev[i]);
undefined identifier `CloseHandle`
} // --------------------------------------------------------------------------- // Stats: insertion sort (300 elements, exit path only) + percentile report. void
void app.sortSamples(ref app.Samples s) nothrow @nogc
sortSamples
(ref
(struct) app.Samples
Samples
(parameter) app.Samples s
s
) nothrow @nogc
{ foreach (
(local variable) int i
i
; 1 ..
(parameter) app.Samples s
s
.
(field) int app.Samples.n
n
)
{ const
(local variable) const(long) v
v
=
(parameter) app.Samples s
s
.
(field) long[300] app.Samples.buf
buf
[
(local variable) int i
i
];
int
(local variable) int j
j
=
(local variable) int i
i
- 1;
while (
(local variable) int j
j
>= 0 &&
(parameter) app.Samples s
s
.
(field) long[300] app.Samples.buf
buf
[
(local variable) int j
j
] >
(local variable) const(long) v
v
)
{
(parameter) app.Samples s
s
.
(field) long[300] app.Samples.buf
buf
[
(local variable) int j
j
+ 1] =
(parameter) app.Samples s
s
.
(field) long[300] app.Samples.buf
buf
[
(local variable) int j
j
];
(local variable) int j
j
--;
}
(parameter) app.Samples s
s
.
(field) long[300] app.Samples.buf
buf
[
(local variable) int j
j
+ 1] =
(local variable) const(long) v
v
;
} } void
void app.reportStats(ref app.Samples s, const(char)* name) nothrow
reportStats
(ref
(struct) app.Samples
Samples
(parameter) app.Samples s
s
, const(char)*
(parameter) const(char)* name
name
) nothrow
{ if (
(parameter) app.Samples s
s
.
(field) int app.Samples.n
n
== 0)
{ logEvent("latency_stats mech=%s n=0", name);
undefined identifier `logEvent`
return; }
void app.sortSamples(ref app.Samples s) nothrow @nogc
sortSamples
(
(parameter) app.Samples s
s
);
const
(local variable) const(long) min
min
=
(parameter) app.Samples s
s
.
(field) long[300] app.Samples.buf
buf
[0];
const
(local variable) const(long) median
median
=
(parameter) app.Samples s
s
.
(field) long[300] app.Samples.buf
buf
[
(parameter) app.Samples s
s
.
(field) int app.Samples.n
n
/ 2];
const
(local variable) const(long) p99
p99
=
(parameter) app.Samples s
s
.
(field) long[300] app.Samples.buf
buf
[(
(parameter) app.Samples s
s
.
(field) int app.Samples.n
n
* 99) / 100];
const
(local variable) const(long) max
max
=
(parameter) app.Samples s
s
.
(field) long[300] app.Samples.buf
buf
[
(parameter) app.Samples s
s
.
(field) int app.Samples.n
n
- 1];
logEvent("latency_stats mech=%s n=%d min_us=%lld median_us=%lld p99_us=%lld max_us=%lld",
undefined identifier `logEvent`
name, s.
s.n
n
, min, median, p99, max);
} // --------------------------------------------------------------------------- // WndProc: WM_WAKEUP_POST arrives here through DispatchMessageW like any // window message. WM_WAKEUP_THREAD never does — see the pump. void
void app.recordWakeup(ref app.Samples s, long stamp, const(char)* mech, ulong seq) nothrow
recordWakeup
(ref
(struct) app.Samples
Samples
(parameter) app.Samples s
s
, long
(parameter) long stamp
stamp
, const(char)*
(parameter) const(char)* mech
mech
,
(alias) object.size_t = ulong
size_t
(parameter) ulong seq
seq
) nothrow
{ const
(local variable) const(long) lat
lat
=
long app.qpcToUs(long delta) nothrow @nogc
qpcToUs
(
long app.qpcNow() nothrow @nogc
qpcNow
() -
(parameter) long stamp
stamp
);
(parameter) app.Samples s
s
.
void app.Samples.add(long v) nothrow @nogc
add
(
(local variable) const(long) lat
lat
);
logEvent("wakeup latency_us=%lld mech=%s seq=%d", lat, mech, cast(int) seq);
undefined identifier `logEvent`
} extern (Windows) LRESULT
app.wndProc
wndProc
(HWND
(parameter) HWND hwnd
hwnd
, UINT
(parameter) UINT msg
msg
, WPARAM wParam, LPARAM lParam) nothrow
undefined identifier `LRESULT`
undefined identifier `HWND`
undefined identifier `UINT`
undefined identifier `WPARAM`
undefined identifier `LPARAM`
{ switch (msg) { case WM_WAKEUP_POST: recordWakeup(postLat, postStampQpc[wParam], "postmessage", wParam); if (wParam == FILTER_PROBE_SEQ && !g.
g.probeDone
probeDone
)
{ // The matching WM_WAKEUP_THREAD was posted right behind this // message, so it is (almost certainly) sitting in the queue now: // an hwnd-filtered peek cannot see it, a null-filtered one can. // This is why thread messages are lost inside any modal loop that // pumps with an hwnd filter (dialogs, menus, DefWindowProc's // move/size loop): no hwnd matches a message that has none. g.
g.probeDone
probeDone
= true;
MSG
_error_ probe
probe
;
const
_error_ filtered
filtered
= PeekMessageW(&probe, hwnd,
WM_WAKEUP_THREAD, WM_WAKEUP_THREAD, PM_NOREMOVE); const
_error_ open
open
= PeekMessageW(&probe, null,
WM_WAKEUP_THREAD, WM_WAKEUP_THREAD, PM_NOREMOVE); logEvent("thread_msg_filter_probe hwnd_filtered=%d null_filtered=%d", cast(int) filtered, cast(int) open); } return 0; case WM_WORKER_DONE: logEvent("worker_done posts=%d", TOTAL_POSTS); g.
g.workerDone
workerDone
= true;
if (g.
g.autoExit
autoExit
)
DestroyWindow(hwnd); return 0; case WM_PAINT: PAINTSTRUCT
_error_ ps
ps
;
HDC
_error_ hdc
hdc
= BeginPaint(hwnd, &ps);
FillRect(hdc, &ps.rcPaint, cast(HBRUSH)(COLOR_WINDOW + 1)); EndPaint(hwnd, &ps); return 0; case WM_CLOSE: logEvent("close_requested"); goto default; case WM_DESTROY: logEvent("msg name=WM_DESTROY"); PostQuitMessage(0); return 0; default: return DefWindowProcW(hwnd, msg, wParam, lParam); } } // --------------------------------------------------------------------------- bool
bool app.wantAutoExit() nothrow
wantAutoExit
() nothrow
{ WCHAR[8]
(local variable) _error_ buf
buf
;
undefined identifier `WCHAR`
const
(local variable) const(_error_) n
n
= GetEnvironmentVariableW("WSI_AUTO_EXIT"w.ptr, buf.ptr, buf.
buf.length
length
);
undefined identifier `GetEnvironmentVariableW`
return n >= 1 && n < buf.
(field) _error_ buf.length
length
&& buf[0] == '1';
} int
int D main()
main
()
{ instrumentInit("f05_loop_wakeup_win32");
undefined identifier `instrumentInit`
logEvent("init_start");
undefined identifier `logEvent`
g.
g.autoExit
autoExit
= wantAutoExit();
logEvent("mode auto_exit=%d", g.
g.autoExit
autoExit
? 1 : 0);
undefined identifier `logEvent`
LARGE_INTEGER
(local variable) _error_ freq
freq
;
undefined identifier `LARGE_INTEGER`
QueryPerformanceFrequency(&freq);
undefined identifier `QueryPerformanceFrequency`
g.
g.qpcFreq
qpcFreq
= freq.QuadPart;
logEvent("qpc_freq hz=%lld", g.
g.qpcFreq
qpcFreq
);
undefined identifier `logEvent`
g.
g.uiThreadId
uiThreadId
= GetCurrentThreadId();
HINSTANCE
(local variable) _error_ hInst
hInst
= GetModuleHandleW(null);
undefined identifier `HINSTANCE`
undefined identifier `GetModuleHandleW`
auto
(local variable) wstring clsName
clsName
= "wsi-f05-class"w;
WNDCLASSEXW
(local variable) _error_ wc
wc
;
undefined identifier `WNDCLASSEXW`
wc.cbSize = WNDCLASSEXW.sizeof; wc.lpfnWndProc = &wndProc; wc.hInstance = hInst; wc.lpszClassName = clsName.ptr; wc.hCursor = LoadCursorW(null, IDC_ARROW); if (!RegisterClassExW(&wc))
undefined identifier `RegisterClassExW`
{ logEvent("error what=RegisterClassExW code=%lu", GetLastError());
undefined identifier `logEvent`
return 1; } g.
g.hwnd
hwnd
= CreateWindowExW(0, clsName.ptr, "wsi-f05-loop-wakeup"w.ptr,
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 480, 320, null, null, hInst, null); if (g.
(field) _error_ g.hwnd
hwnd
is null)
{ logEvent("error what=CreateWindowExW code=%lu", GetLastError());
undefined identifier `logEvent`
return 1; } logEvent("window_created");
undefined identifier `logEvent`
ShowWindow(g.
g.hwnd
hwnd
, SW_SHOW);
undefined identifier `ShowWindow`
UpdateWindow(g.
g.hwnd
hwnd
);
undefined identifier `UpdateWindow`
void app.probeHandleLimit() nothrow
probeHandleLimit
();
// The "arbitrary fd": an auto-reset waitable timer at ~7 Hz. Auto-reset // means a satisfied wait consumes the signal — no manual ResetEvent dance. g.
g.timer
timer
= CreateWaitableTimerW(null, FALSE, null);
LARGE_INTEGER
(local variable) _error_ due
due
;
undefined identifier `LARGE_INTEGER`
due.QuadPart = -10_000L * TIMER_PERIOD_MS; // relative, 100 ns units if (g.
(field) _error_ g.timer
timer
is null || !SetWaitableTimer(g.
g.timer
timer
, &due, TIMER_PERIOD_MS, null, null, FALSE))
undefined identifier `SetWaitableTimer`
{ logEvent("error what=SetWaitableTimer code=%lu", GetLastError());
undefined identifier `logEvent`
return 1; } logEvent("step name=SetWaitableTimer period_ms=%d", TIMER_PERIOD_MS);
undefined identifier `logEvent`
g.
g.worker
worker
= CreateThread(null, 0, &workerMain, null, 0, null);
if (g.
(field) _error_ g.worker
worker
is null)
{ logEvent("error what=CreateThread code=%lu", GetLastError());
undefined identifier `logEvent`
return 1; } logEvent("step name=CreateThread rate_hz=%d duration_s=%d", POSTS_PER_SECOND, RUN_SECONDS);
undefined identifier `logEvent`
// The pump: wait on {timer} + the message queue in one call. QS_ALLINPUT // wakes for any queued message; MWMO_INPUTAVAILABLE closes the race where // a message arrived between the drain below and re-entering the wait // (without it, already-queued-but-already-seen input would not satisfy // the wait and a wakeup could stall until the next timer tick). int
(local variable) int exitCode
exitCode
= 0;
pump: while (true) { const
(local variable) const(_error_) r
r
= MsgWaitForMultipleObjectsEx(1, &g.
g.timer
timer
, INFINITE,
undefined identifier `MsgWaitForMultipleObjectsEx`
QS_ALLINPUT, MWMO_INPUTAVAILABLE); if (r == WAIT_OBJECT_0) // the timer handle, not the queue
undefined identifier `WAIT_OBJECT_0`
{ const
(local variable) const(long) t
t
=
long app.qpcNow() nothrow @nogc
qpcNow
();
++g.
(field) _error_ g.tickCount
tickCount
;
logEvent("fd_tick t=%lld n=%u", nowUs(), g.
g.tickCount
tickCount
);
undefined identifier `logEvent`
if (g.
(field) _error_ g.lastTickQpc
lastTickQpc
!= 0)
(__gshared global) app.Samples app.tickIntervals
tickIntervals
.
tickIntervals.add
add
(
long app.qpcToUs(long delta) nothrow @nogc
qpcToUs
(
(local variable) const(long) t
t
- g.
(field) _error_ g.lastTickQpc
lastTickQpc
));
g.
g.lastTickQpc
lastTickQpc
= t;
} else if (r == WAIT_FAILED)
undefined identifier `WAIT_FAILED`
{ logEvent("error what=MsgWaitForMultipleObjectsEx code=%lu", GetLastError());
undefined identifier `logEvent`
(local variable) int exitCode
exitCode
= 1;
break; } // r == WAIT_OBJECT_0 + 1: queue input. Drain it fully either way — // a timer wake may coincide with pending messages. MSG
(local variable) _error_ msg
msg
;
undefined identifier `MSG`
while (PeekMessageW(&msg, null, 0, 0, PM_REMOVE))
undefined identifier `PeekMessageW`
{ if (msg.message == WM_QUIT)
undefined identifier `WM_QUIT`
{
(local variable) int exitCode
exitCode
= cast(int) msg.wParam;
break pump; } if (msg.
(field) _error_ msg.hwnd
hwnd
is null)
{ // A thread message: DispatchMessageW would silently drop it // (no hwnd -> no WndProc). Handle it here, in the pump. if (msg.message == WM_WAKEUP_THREAD)
void app.recordWakeup(ref app.Samples s, long stamp, const(char)* mech, ulong seq) nothrow
recordWakeup
(
(__gshared global) app.Samples app.threadLat
threadLat
,
(__gshared global) long[300] app.threadStampQpc
threadStampQpc
[msg.wParam],
"threadmessage", msg.wParam); continue; } TranslateMessage(&msg);
undefined identifier `TranslateMessage`
DispatchMessageW(&msg);
undefined identifier `DispatchMessageW`
} } WaitForSingleObject(g.
g.worker
worker
, 5000);
undefined identifier `WaitForSingleObject`
CloseHandle(g.
g.worker
worker
);
undefined identifier `CloseHandle`
CancelWaitableTimer(g.
g.timer
timer
);
undefined identifier `CancelWaitableTimer`
CloseHandle(g.
g.timer
timer
);
undefined identifier `CloseHandle`
void app.reportStats(ref app.Samples s, const(char)* name) nothrow
reportStats
(
(__gshared global) app.Samples app.postLat
postLat
, "postmessage");
void app.reportStats(ref app.Samples s, const(char)* name) nothrow
reportStats
(
(__gshared global) app.Samples app.threadLat
threadLat
, "threadmessage");
void app.reportStats(ref app.Samples s, const(char)* name) nothrow
reportStats
(
(__gshared global) app.Samples app.tickIntervals
tickIntervals
, "handle_tick_interval");
logEvent("tick_total n=%u nominal_period_ms=%d", g.
g.tickCount
tickCount
, TIMER_PERIOD_MS);
undefined identifier `logEvent`
logEvent("exit code=%d", exitCode);
undefined identifier `logEvent`
return
(local variable) int exitCode
exitCode
;
}