// Win32 F17 — threading probes (../../../features/f17-threading.md), built on
// the scaffold (../scaffold/app.d). Six `--probe=N` run modes, each ending in a
// flushed verdict line:
//
// probe n=... result=ok|error|crash|deadlock|silent detail=...
//
// 1. Window created on a WORKER thread while MAIN tries to receive its
// messages: proves HWND message routing follows the *creating thread's*
// queue (PostMessage'd messages are invisible to every other thread's
// PeekMessage/GetMessage, even when filtered by that exact HWND).
// 2. Worker creates AND pumps its own window while main pumps another —
// the legal multi-window multi-thread model, both painting concurrently.
// 3. Cross-thread SendMessage vs PostMessage: SendMessage blocks the sender
// until the owning thread's pump dispatches (measured against a
// deliberate 400 ms non-pumping gap); PostMessage latency for contrast.
// 4. The deadlock recipes: (a) two threads SendMessage each other
// simultaneously — does the documented "incoming nonqueued messages are
// processed while waiting" rule resolve it? (b) SendMessage to a thread
// parked in WaitForSingleObject — SendMessageTimeout first (mitigation),
// then a plain SendMessage captured by the 3 s watchdog as
// result=deadlock.
// 5. BitBlt into a window DC acquired on a NON-owning thread, 100 frames,
// while the owner pumps and paints — the GDI thread rules, measured.
// 6. AttachThreadInput: is GetFocus() per-queue state, and does attaching
// the worker's input queue to main's make main's focus visible?
//
// Crash discipline (the spec's warning): a SetUnhandledExceptionFilter SEH
// hook turns any crash into a flushed `result=crash` verdict + ExitProcess(0),
// and a per-run watchdog thread turns hangs into `result=deadlock` +
// ExitProcess(0). Probe 4b *relies* on the watchdog — deadlocking is its job.
// Every child therefore exits 0; so does the driver.
//
// The no-argument run (what CI executes) spawns itself with --probe=N twice
// per probe (CreateProcessW, inherited stderr), per the spec's run-twice
// nondeterminism rule, and exits 0 regardless of child outcomes.
//
// Only druntime's core.sys.windows bindings. Worker threads are raw
// CreateThread threads that never touch the D GC (logEvent is @nogc nothrow).
module (module) appapp;
import (package) corecore.(package) core.syssys.(package) core.sys.windowswindows.(module) core.sys.windows.windowsWindows API header module
Translated from MinGW API for MS-Windows 4.0
Source
core/sys/windows/windows.d
windows;
import instrument;
enum UINT (constant) _error_ app.WM_PING = __error__WM_PING = WM_APP + 1; // SendMessage payload (handler returns 42)
enum UINT (constant) _error_ app.WM_POSTED = __error__WM_POSTED = WM_APP + 2; // PostMessage latency probe
enum UINT (constant) _error_ app.WM_MUTUAL = __error__WM_MUTUAL = WM_APP + 3; // probe 4a mutual send
enum UINT (constant) _error_ app.WM_DONE = __error__WM_DONE = WM_APP + 4; // worker -> main "stop pumping"
struct (struct) app.StateState
{
HINSTANCE (field) _error_ app.State.instinst;
int (field) int app.State.probeprobe;
HWND (field) _error_ app.State.mainWndmainWnd, (field) _error_ app.State.workerWndworkerWnd;
DWORD (field) _error_ app.State.mainTidmainTid, (field) _error_ app.State.workerTidworkerTid;
HANDLE (field) _error_ app.State.evReadyevReady, (field) _error_ app.State.evGoevGo, (field) _error_ app.State.evDoneevDone, (field) _error_ app.State.evNeverevNever;
long (field) long app.State.postT0postT0; // PostMessage send timestamp (probe 3)
long (field) long app.State.sendLatencyUssendLatencyUs; // measured SendMessage block time (probe 3)
int (field) int app.State.mainPaintsmainPaints, (field) int app.State.workerPaintsworkerPaints; // probe 2
int (field) int app.State.draineddrained; // probe 1
LONG (field) _error_ app.State.mutualRecvmutualRecv; // probe 4a: WM_MUTUAL deliveries
const(char)* (field) const(char)* app.State.stagestage = "init"; // what the watchdog reports
int (field) int app.State.watchdogMswatchdogMs = 15000;
}
__gshared (struct) app.StateState _error_ app.gg;
// ---------------------------------------------------------------------------
// Crash + hang capture: the verdict line must survive anything.
extern (Windows) LONG app.sehFiltersehFilter(EXCEPTION_POINTERS* ep) nothrow
{
const _error_ codecode = ep && ep.ExceptionRecord ? ep.ExceptionRecord.ExceptionCode : 0;
logEvent("probe n=%d result=crash detail=seh code=0x%08lx stage=%s",
g.g.probeprobe, code, g.g.stagestage);
ExitProcess(0);
return EXCEPTION_EXECUTE_HANDLER; // not reached
}
extern (Windows) uint uint app.watchdogProc(void* arg) nothrowwatchdogProc(void* (parameter) void* argarg) nothrow
{
Sleep(cast(DWORD) cast(size_t) arg);
// Probe 4b *expects* to land here: the deadlock is the finding.
logEvent("probe n=%d result=deadlock detail=watchdog_fired stage=%s",
g.g.probeprobe, g.g.stagestage);
ExitProcess(0);
return 0;
}
void void app.armWatchdog(int ms) nothrowarmWatchdog(int (parameter) int msms) nothrow
{
CloseHandle(CreateThread(null, 0, &watchdogProc,
cast(void*) cast(size_t) ms, 0, null));
}
// ---------------------------------------------------------------------------
// One WndProc for every probe window; per-message logging keyed by thread id.
extern (Windows) LRESULT app.wndProcwndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) nothrow
{
switch (msg)
{
case WM_PING:
logEvent("recv msg=WM_PING dispatched_on_thread=%lu", GetCurrentThreadId());
return 42;
case WM_POSTED:
logEvent("post_latency_us=%lld dispatched_on_thread=%lu",
nowUs() - g.g.postT0postT0, GetCurrentThreadId());
return 0;
case WM_MUTUAL:
import core.atomic : atomicOp;
(template instance) atomicOp!"+="atomicOp!"+="(*cast(shared LONG*)&g.g.mutualRecvmutualRecv, 1);
logEvent("recv msg=WM_MUTUAL dispatched_on_thread=%lu", GetCurrentThreadId());
return 7;
case WM_DONE:
PostQuitMessage(0);
return 0;
case WM_TIMER:
InvalidateRect(hwnd, null, FALSE); // probe 5: owner keeps repainting
return 0;
case WM_PAINT:
PAINTSTRUCT _error_ psps;
HDC _error_ dcdc = BeginPaint(hwnd, &ps);
const _error_ tidtid = GetCurrentThreadId();
// Visible activity per thread; the count is the probe-2 evidence.
RECT _error_ rcrc;
GetClientRect(hwnd, &rc);
FillRect(dc, &rc, cast(HBRUSH)(COLOR_WINDOW + (tid & 1)));
EndPaint(hwnd, &ps);
if (tid == g.g.mainTidmainTid)
++g.g.mainPaintsmainPaints;
else
++g.g.workerPaintsworkerPaints;
return 0;
default:
return DefWindowProcW(hwnd, msg, wp, lp);
}
}
HWND app.makeWindowmakeWindow(const(wchar)* title) nothrow
{
HWND _error_ hh = CreateWindowExW(0, "wsi-f17-class"w.ptr, title,
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 320, 240,
null, null, g.g.instinst, null);
if (h !is null)
ShowWindow(h, SW_SHOW);
return h;
}
// Pump the calling thread's queue until WM_QUIT or timeout.
int int app.pumpUntilQuit(int timeoutMs) nothrowpumpUntilQuit(int (parameter) int timeoutMstimeoutMs) nothrow
{
const (local variable) const(_error_) deadlinedeadline = nowUs() + cast(long) (parameter) int timeoutMstimeoutMs * 1000;
MSG (local variable) _error_ mm;
int (local variable) int dispatcheddispatched;
while (nowUs() < deadline)
{
while (PeekMessageW(&m, null, 0, 0, PM_REMOVE))
{
if (m.message == WM_QUIT)
return (local variable) int dispatcheddispatched;
TranslateMessage(&m);
DispatchMessageW(&m);
++(local variable) int dispatcheddispatched;
}
MsgWaitForMultipleObjects(0, null, FALSE, 20, QS_ALLINPUT);
}
return (local variable) int dispatcheddispatched;
}
// ---------------------------------------------------------------------------
// Probe 1 — window created on worker, messages posted to it; main must not
// see them. Worker does NOT pump until told; then it drains its own queue.
extern (Windows) uint uint app.worker1(void* arg) nothrowworker1(void* (parameter) void* argarg) nothrow
{
g.g.workerWndworkerWnd = makeWindow("wsi-f17-worker"w.ptr);
logEvent("thread=worker action=window_created hwnd=%p tid=%lu ok=%d",
g.g.workerWndworkerWnd, GetCurrentThreadId(), g.g.workerWndworkerWnd !is null ? 1 : 0);
foreach ((local variable) int ii; 0 .. 10)
PostMessageW(g.g.workerWndworkerWnd, WM_PING, i, 0);
logEvent("thread=worker action=posted count=10 to_own_window=1");
SetEvent(g.g.evReadyevReady);
WaitForSingleObject(g.g.evGoevGo, 10000);
// Drain with a WM_PING..WM_PING filter: an unfiltered PeekMessage(PM_REMOVE)
// spins forever here, because WM_PAINT is only cleared from the queue by
// validating the region (BeginPaint) — observed first-hand under Wine.
MSG (local variable) _error_ mm;
while (PeekMessageW(&m, null, WM_PING, WM_PING, PM_REMOVE))
++g.(field) _error_ g.draineddrained;
logEvent("thread=worker action=drained wm_ping=%d", g.g.draineddrained);
DestroyWindow(g.g.workerWndworkerWnd); // must happen on the creating thread
SetEvent(g.g.evDoneevDone);
return 0;
}
void void app.probe1() nothrowprobe1() nothrow
{
g.g.stagestage = "p1_create_on_worker";
HANDLE (local variable) _error_ tt = CreateThread(null, 0, &worker1, null, 0, &g.g.workerTidworkerTid);
WaitForSingleObject(g.g.evReadyevReady, 10000);
// Main hunts for the worker-window messages for 500 ms: thread-wide peek
// AND a peek filtered by the worker's HWND specifically.
MSG (local variable) _error_ mm;
int (local variable) int sawThreadWidesawThreadWide, (local variable) int sawHwndFilteredsawHwndFiltered;
const (local variable) const(_error_) deadlinedeadline = nowUs() + 500_000;
while (nowUs() < deadline)
{
while (PeekMessageW(&m, null, WM_PING, WM_PING, PM_REMOVE))
++(local variable) int sawThreadWidesawThreadWide;
if (PeekMessageW(&m, g.g.workerWndworkerWnd, 0, 0, PM_NOREMOVE))
++(local variable) int sawHwndFilteredsawHwndFiltered;
Sleep(10);
}
logEvent("main_hunt thread_wide=%d hwnd_filtered=%d tid=%lu",
sawThreadWide, sawHwndFiltered, GetCurrentThreadId());
SetEvent(g.g.evGoevGo);
WaitForSingleObject(g.g.evDoneevDone, 10000);
WaitForSingleObject(t, 5000);
CloseHandle(t);
const (local variable) const(_error_) okok = (local variable) int sawThreadWidesawThreadWide == 0 && (local variable) int sawHwndFilteredsawHwndFiltered == 0 && g.(field) _error_ g.draineddrained == 10;
logEvent("probe n=1 result=%s detail=posted=10 main_saw=%d main_saw_hwnd_filtered=%d worker_drained=%d",
ok ? "ok".ptr : "error".ptr, sawThreadWide, sawHwndFiltered, g.g.draineddrained);
}
// ---------------------------------------------------------------------------
// Probe 2 — worker creates and pumps its own window while main does the same.
extern (Windows) uint uint app.worker2(void* arg) nothrowworker2(void* (parameter) void* argarg) nothrow
{
g.g.workerWndworkerWnd = makeWindow("wsi-f17-worker"w.ptr);
logEvent("thread=worker action=window_created hwnd=%p tid=%lu",
g.g.workerWndworkerWnd, GetCurrentThreadId());
SetEvent(g.g.evReadyevReady);
foreach ((local variable) int ii; 0 .. 30)
{
InvalidateRect(g.g.workerWndworkerWnd, null, TRUE);
MSG (local variable) _error_ mm;
while (PeekMessageW(&m, null, 0, 0, PM_REMOVE))
DispatchMessageW(&m);
Sleep(5);
}
DestroyWindow(g.g.workerWndworkerWnd);
SetEvent(g.g.evDoneevDone);
return 0;
}
void void app.probe2() nothrowprobe2() nothrow
{
g.g.stagestage = "p2_two_pumps";
g.g.mainWndmainWnd = makeWindow("wsi-f17-main"w.ptr);
HANDLE (local variable) _error_ tt = CreateThread(null, 0, &worker2, null, 0, &g.g.workerTidworkerTid);
WaitForSingleObject(g.g.evReadyevReady, 10000);
foreach ((local variable) int ii; 0 .. 30)
{
InvalidateRect(g.g.mainWndmainWnd, null, TRUE);
MSG (local variable) _error_ mm;
while (PeekMessageW(&m, null, 0, 0, PM_REMOVE))
DispatchMessageW(&m);
Sleep(5);
}
WaitForSingleObject(g.g.evDoneevDone, 10000);
WaitForSingleObject(t, 5000);
CloseHandle(t);
DestroyWindow(g.g.mainWndmainWnd);
const (local variable) const(_error_) okok = g.(field) _error_ g.mainPaintsmainPaints > 0 && g.(field) _error_ g.workerPaintsworkerPaints > 0;
logEvent("probe n=2 result=%s detail=main_paints=%d worker_paints=%d concurrent_pumps=2",
ok ? "ok".ptr : "error".ptr, g.g.mainPaintsmainPaints, g.g.workerPaintsworkerPaints);
}
// ---------------------------------------------------------------------------
// Probe 3 — SendMessage blocks until the owner pumps; PostMessage does not.
extern (Windows) uint uint app.worker3(void* arg) nothrowworker3(void* (parameter) void* argarg) nothrow
{
WaitForSingleObject(g.g.evReadyevReady, 10000);
logEvent("thread=worker action=send_begin t=%lld owner_sleeping_ms=400", nowUs());
const (local variable) const(_error_) t0t0 = nowUs();
const (local variable) const(_error_) rr = SendMessageW(g.g.mainWndmainWnd, WM_PING, 0, 0); // blocks: owner not pumping yet
g.g.sendLatencyUssendLatencyUs = nowUs() - t0;
logEvent("thread=worker action=send_returned ret=%lld blocked_us=%lld",
cast(long) r, g.g.sendLatencyUssendLatencyUs);
g.g.postT0postT0 = nowUs();
PostMessageW(g.g.mainWndmainWnd, WM_POSTED, 0, 0); // returns immediately
logEvent("thread=worker action=post_returned after_us=%lld", nowUs() - g.g.postT0postT0);
PostMessageW(g.g.mainWndmainWnd, WM_DONE, 0, 0);
return 0;
}
void void app.probe3() nothrowprobe3() nothrow
{
g.g.stagestage = "p3_send_vs_post";
g.g.mainWndmainWnd = makeWindow("wsi-f17-main"w.ptr);
HANDLE (local variable) _error_ tt = CreateThread(null, 0, &worker3, null, 0, &g.g.workerTidworkerTid);
SetEvent(g.g.evReadyevReady);
logEvent("main action=sleep_no_pump ms=400"); // the measured gap
Sleep(400);
int app.pumpUntilQuit(int timeoutMs) nothrowpumpUntilQuit(5000);
WaitForSingleObject(t, 5000);
CloseHandle(t);
DestroyWindow(g.g.mainWndmainWnd);
const (local variable) const(_error_) okok = g.(field) _error_ g.sendLatencyUssendLatencyUs >= 300_000; // blocked across most of the gap
logEvent("probe n=3 result=%s detail=send_blocked_us=%lld post_dispatch=see_post_latency_line",
ok ? "ok".ptr : "error".ptr, g.g.sendLatencyUssendLatencyUs);
}
// ---------------------------------------------------------------------------
// Probe 4 — deadlock recipes.
// 4a: both threads SendMessage each other at once. The SendMessage docs say a
// thread blocked in SendMessage still processes incoming *nonqueued*
// (sent) messages — so this should resolve, not deadlock.
// 4b: SendMessage to a thread parked in WaitForSingleObject(INFINITE) — no
// pump, no SendMessage wait, nothing processes the sent message.
// SendMessageTimeout demonstrates the mitigation; the plain SendMessage
// that follows is ended by the watchdog (result=deadlock — expected).
extern (Windows) uint uint app.worker4(void* arg) nothrowworker4(void* (parameter) void* argarg) nothrow
{
g.g.workerWndworkerWnd = makeWindow("wsi-f17-worker"w.ptr);
SetEvent(g.g.evReadyevReady);
WaitForSingleObject(g.g.evGoevGo, 10000); // barrier: fire together with main
logEvent("thread=worker action=mutual_send_begin t=%lld", nowUs());
const (local variable) const(_error_) t0t0 = nowUs();
const (local variable) const(_error_) rr = SendMessageW(g.g.mainWndmainWnd, WM_MUTUAL, 0, 0);
logEvent("thread=worker action=mutual_send_returned ret=%lld blocked_us=%lld",
cast(long) r, nowUs() - t0);
SetEvent(g.g.evDoneevDone);
// 4b: park hard — not pumping, not in SendMessage, just a kernel wait.
logEvent("thread=worker action=park_in_WaitForSingleObject infinite=1");
WaitForSingleObject(g.g.evNeverevNever, INFINITE);
return 0;
}
void void app.probe4() nothrowprobe4() nothrow
{
g.g.stagestage = "p4a_mutual_send";
g.g.mainWndmainWnd = makeWindow("wsi-f17-main"w.ptr);
HANDLE (local variable) _error_ tt = CreateThread(null, 0, &worker4, null, 0, &g.g.workerTidworkerTid);
WaitForSingleObject(g.g.evReadyevReady, 10000);
SetEvent(g.g.evGoevGo);
logEvent("main action=mutual_send_begin t=%lld", nowUs());
const (local variable) const(_error_) t0t0 = nowUs();
const (local variable) const(_error_) rr = SendMessageW(g.g.workerWndworkerWnd, WM_MUTUAL, 0, 0);
const (local variable) const(_error_) mainBlockedmainBlocked = nowUs() - t0;
logEvent("main action=mutual_send_returned ret=%lld blocked_us=%lld",
cast(long) r, mainBlocked);
WaitForSingleObject(g.g.evDoneevDone, 5000);
logEvent("probe n=4 stage=mutual_send result=%s detail=both_returned wm_mutual_recv=%ld main_blocked_us=%lld",
g.g.mutualRecvmutualRecv == 2 ? "ok".ptr : "error".ptr, g.g.mutualRecvmutualRecv, mainBlocked);
// 4b — worker is now parked in WaitForSingleObject(INFINITE).
Sleep(100); // let it reach the wait
g.g.stagestage = "p4b_send_to_blocked_thread";
DWORD (local variable) _error_ resres; // druntime declares the out param as PDWORD (PDWORD_PTR upstream)
SetLastError(0);
const (local variable) const(_error_) okok = SendMessageTimeoutW(g.g.workerWndworkerWnd, WM_PING, 0, 0,
SMTO_NORMAL, 1500, &res);
logEvent("main action=SendMessageTimeout ret=%d err=%lu timeout_ms=1500",
cast(int) ok, GetLastError());
logEvent("main action=plain_send_begin expect=deadlock watchdog_ms=3000");
void app.armWatchdog(int ms) nothrowarmWatchdog(3000); // THIS ends the probe: verdict result=deadlock
SendMessageW(g.g.workerWndworkerWnd, WM_PING, 0, 0); // never returns
logEvent("probe n=4 stage=send_to_blocked result=silent detail=send_unexpectedly_returned");
}
// ---------------------------------------------------------------------------
// Probe 5 — BitBlt into the window DC from a non-owning thread, 100 frames,
// while the owning (main) thread pumps and repaints concurrently.
extern (Windows) uint uint app.worker5(void* arg) nothrowworker5(void* (parameter) void* argarg) nothrow
{
HDC (local variable) _error_ wdcwdc = GetDC(g.g.mainWndmainWnd); // window DC acquired on THIS thread
logEvent("thread=worker action=GetDC hdc=%p err=%lu", wdc, GetLastError());
HDC (local variable) _error_ memmem = CreateCompatibleDC(wdc);
enum (constant) int app.worker5.W = 200W = 200, (constant) int app.worker5.H = 150H = 150;
BITMAPINFO (local variable) _error_ bmibmi;
bmi.bmiHeader.biSize = BITMAPINFOHEADER.sizeof;
bmi.bmiHeader.biWidth = W;
bmi.bmiHeader.biHeight = -H;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
void* (local variable) void* bitsbits;
HBITMAP (local variable) _error_ dibdib = CreateDIBSection(null, &bmi, DIB_RGB_COLORS, &bits, null, 0);
auto (local variable) _error_ oldold = SelectObject(mem, dib);
int (local variable) int okCountokCount, (local variable) int failCountfailCount;
DWORD (local variable) _error_ firstErrfirstErr;
foreach ((local variable) int ii; 0 .. 100)
{
auto (local variable) uint* pxpx = cast(uint*) (local variable) void* bitsbits;
(local variable) uint* pxpx[0 .. (constant) int app.worker5.W = 200W * (constant) int app.worker5.H = 150H] = 0xff0000 | (cast(uint) (local variable) int ii * 2 << 8); // frame-varying fill
SetLastError(0);
if (BitBlt(wdc, 20, 20, W, H, mem, 0, 0, SRCCOPY))
++(local variable) int okCountokCount;
else
{
if ((local variable) int failCountfailCount == 0)
firstErr = GetLastError();
++(local variable) int failCountfailCount;
}
Sleep(3);
}
SelectObject(mem, old);
DeleteObject(dib);
DeleteDC(mem);
ReleaseDC(g.g.mainWndmainWnd, wdc);
logEvent("thread=worker action=blits done ok=%d fail=%d first_err=%lu",
okCount, failCount, failCount ? firstErr : 0);
g.g.draineddrained = okCount; // reuse the slot for the verdict
PostMessageW(g.g.mainWndmainWnd, WM_DONE, 0, 0);
return 0;
}
void void app.probe5() nothrowprobe5() nothrow
{
g.g.stagestage = "p5_cross_thread_bitblt";
g.g.mainWndmainWnd = makeWindow("wsi-f17-main"w.ptr);
HANDLE (local variable) _error_ tt = CreateThread(null, 0, &worker5, null, 0, &g.g.workerTidworkerTid);
// Owner keeps pumping AND repainting underneath the foreign BitBlts.
SetTimer(g.g.mainWndmainWnd, 1, 16, null);
const (local variable) const(int) dispatcheddispatched = int app.pumpUntilQuit(int timeoutMs) nothrowpumpUntilQuit(10000);
KillTimer(g.g.mainWndmainWnd, 1);
WaitForSingleObject(t, 5000);
CloseHandle(t);
DestroyWindow(g.g.mainWndmainWnd);
logEvent("probe n=5 result=%s detail=blits_ok=%d/100 owner_dispatched=%d owner_paints=%d",
g.g.draineddrained == 100 ? "ok".ptr : "error".ptr, g.g.draineddrained, dispatched, g.g.mainPaintsmainPaints);
}
// ---------------------------------------------------------------------------
// Probe 6 — AttachThreadInput: GetFocus is per-input-queue state.
extern (Windows) uint uint app.worker6(void* arg) nothrowworker6(void* (parameter) void* argarg) nothrow
{
WaitForSingleObject(g.g.evReadyevReady, 10000);
HWND (local variable) _error_ beforebefore = GetFocus();
SetLastError(0);
const (local variable) const(_error_) attatt = AttachThreadInput(GetCurrentThreadId(), g.g.mainTidmainTid, TRUE);
HWND (local variable) _error_ afterafter = GetFocus();
AttachThreadInput(GetCurrentThreadId(), g.g.mainTidmainTid, FALSE);
logEvent("thread=worker action=attach_probe before=%p attach_ret=%d err=%lu after=%p",
before, att, GetLastError(), after);
g.g.workerWndworkerWnd = after; // smuggle the result to the verdict
g.g.draineddrained = att;
PostMessageW(g.g.mainWndmainWnd, WM_DONE, 0, 0);
return 0;
}
void void app.probe6() nothrowprobe6() nothrow
{
g.g.stagestage = "p6_attach_thread_input";
g.g.mainWndmainWnd = makeWindow("wsi-f17-main"w.ptr);
SetForegroundWindow(g.g.mainWndmainWnd);
SetFocus(g.g.mainWndmainWnd);
logEvent("main action=SetFocus hwnd=%p get_focus=%p", g.g.mainWndmainWnd, GetFocus());
HANDLE (local variable) _error_ tt = CreateThread(null, 0, &worker6, null, 0, &g.g.workerTidworkerTid);
SetEvent(g.g.evReadyevReady);
int app.pumpUntilQuit(int timeoutMs) nothrowpumpUntilQuit(5000);
WaitForSingleObject(t, 5000);
CloseHandle(t);
DestroyWindow(g.g.mainWndmainWnd);
const (local variable) const(_error_) seesFocusseesFocus = g.(field) _error_ g.workerWndworkerWnd is g.(field) _error_ g.mainWndmainWnd;
logEvent("probe n=6 result=ok detail=attach_ret=%d focus_visible_after_attach=%d focus_hwnd=%p",
g.g.draineddrained, seesFocus ? 1 : 0, g.g.workerWndworkerWnd);
}
// ---------------------------------------------------------------------------
// Driver: child mode runs one probe; parent mode spawns every probe twice.
int int app.runProbe(int n) nothrowrunProbe(int (parameter) int nn) nothrow
{
g.g.probeprobe = n;
g.g.mainTidmainTid = GetCurrentThreadId();
SetUnhandledExceptionFilter(cast(LPTOP_LEVEL_EXCEPTION_FILTER)&sehFilter);
if ((parameter) int nn != 4) // probe 4 arms its own short watchdog at the right moment
void app.armWatchdog(int ms) nothrowarmWatchdog(g.(field) _error_ g.watchdogMswatchdogMs);
g.g.evReadyevReady = CreateEventW(null, FALSE, FALSE, null);
g.g.evGoevGo = CreateEventW(null, FALSE, FALSE, null);
g.g.evDoneevDone = CreateEventW(null, FALSE, FALSE, null);
g.g.evNeverevNever = CreateEventW(null, TRUE, FALSE, null);
WNDCLASSEXW (local variable) _error_ wcwc;
wc.cbSize = WNDCLASSEXW.sizeof;
wc.lpfnWndProc = &wndProc;
wc.hInstance = g.g.instinst;
wc.lpszClassName = "wsi-f17-class"w.ptr;
wc.hCursor = LoadCursorW(null, IDC_ARROW);
wc.hbrBackground = cast(HBRUSH)(COLOR_WINDOW + 1);
RegisterClassExW(&wc); // process-wide: usable from every thread
logEvent("probe_start n=%d main_tid=%lu", n, g.g.mainTidmainTid);
switch ((parameter) int nn)
{
case 1:
void app.probe1() nothrowprobe1();
break;
case 2:
void app.probe2() nothrowprobe2();
break;
case 3:
void app.probe3() nothrowprobe3();
break;
case 4:
void app.probe4() nothrowprobe4();
break;
case 5:
void app.probe5() nothrowprobe5();
break;
case 6:
void app.probe6() nothrowprobe6();
break;
default:
logEvent("probe n=%d result=error detail=unknown_probe", n);
break;
}
ExitProcess(0); // crash probes exit 0 too — crashing is their job
return 0;
}
int int D main(string[] args)main((alias) object.string = stringstring[] (parameter) string[] argsargs)
{
instrumentInit("f17_win32");
g.g.instinst = GetModuleHandleW(null);
foreach ((parameter) string aa; (parameter) string[] argsargs[1 .. $])
if ((local variable) string aa.(field) ulong string.lengthlength == 9 && (local variable) string aa[0 .. 8] == "--probe=")
return int app.runProbe(int n) nothrowrunProbe((local variable) string aa[8] - '0');
// Parent: every probe twice (spec rule 4), each in its own process so a
// crashed/deadlocked child cannot poison the next probe.
logEvent("driver_start probes=6 runs_each=2");
WCHAR[MAX_PATH] (local variable) _error_ exeexe;
GetModuleFileNameW(null, exe.ptr, MAX_PATH);
foreach ((local variable) int nn; 1 .. 7)
{
foreach ((local variable) int runrun; 0 .. 2)
{
WCHAR[MAX_PATH + 32] (local variable) _error_ cmdcmd;
int (local variable) int pp;
cmd[p++] = '"';
for (int (local variable) int ii = 0; exe[i]; ++i)
cmd[p++] = exe[i];
cmd[p++] = '"';
foreach ((parameter) immutable(wchar) chch; " --probe=0"w)
cmd[p++] = ch;
cmd[p - 1] = cast(WCHAR)('0' + n);
cmd[p] = 0;
STARTUPINFOW (local variable) _error_ sisi;
si.cb = STARTUPINFOW.sizeof;
PROCESS_INFORMATION (local variable) _error_ pipi;
logEvent("spawn probe=%d run=%d", n, run + 1);
if (!CreateProcessW(null, cmd.ptr, null, null, TRUE, 0, null, null, &si, &pi))
{
logEvent("error what=CreateProcessW code=%lu", GetLastError());
continue;
}
WaitForSingleObject(pi.hProcess, 30000);
DWORD (local variable) _error_ codecode = 0xdead;
GetExitCodeProcess(pi.hProcess, &code);
logEvent("child_exit probe=%d run=%d code=%lu", n, run + 1, code);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
}
logEvent("exit code=0");
return 0;
}