app.dhover×284 error×202all
// 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) 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_PING = __error__
WM_PING
= WM_APP + 1; // SendMessage payload (handler returns 42)
undefined identifier `UINT`
undefined identifier `WM_APP`
enum UINT
(constant) _error_ app.WM_POSTED = __error__
WM_POSTED
= WM_APP + 2; // PostMessage latency probe
undefined identifier `UINT`
undefined identifier `WM_APP`
enum UINT
(constant) _error_ app.WM_MUTUAL = __error__
WM_MUTUAL
= WM_APP + 3; // probe 4a mutual send
undefined identifier `UINT`
undefined identifier `WM_APP`
enum UINT
(constant) _error_ app.WM_DONE = __error__
WM_DONE
= WM_APP + 4; // worker -> main "stop pumping"
undefined identifier `UINT`
undefined identifier `WM_APP`
struct
(struct) app.State
State
{ HINSTANCE
(field) _error_ app.State.inst
inst
;
undefined identifier `HINSTANCE`
int
(field) int app.State.probe
probe
;
HWND
(field) _error_ app.State.mainWnd
mainWnd
,
(field) _error_ app.State.workerWnd
workerWnd
;
undefined identifier `HWND`
undefined identifier `HWND`
DWORD
(field) _error_ app.State.mainTid
mainTid
,
(field) _error_ app.State.workerTid
workerTid
;
undefined identifier `DWORD`
undefined identifier `DWORD`
HANDLE
(field) _error_ app.State.evReady
evReady
,
(field) _error_ app.State.evGo
evGo
,
(field) _error_ app.State.evDone
evDone
,
(field) _error_ app.State.evNever
evNever
;
undefined identifier `HANDLE`
undefined identifier `HANDLE`
undefined identifier `HANDLE`
undefined identifier `HANDLE`
long
(field) long app.State.postT0
postT0
; // PostMessage send timestamp (probe 3)
long
(field) long app.State.sendLatencyUs
sendLatencyUs
; // measured SendMessage block time (probe 3)
int
(field) int app.State.mainPaints
mainPaints
,
(field) int app.State.workerPaints
workerPaints
; // probe 2
int
(field) int app.State.drained
drained
; // probe 1
LONG
(field) _error_ app.State.mutualRecv
mutualRecv
; // probe 4a: WM_MUTUAL deliveries
undefined identifier `LONG`
const(char)*
(field) const(char)* app.State.stage
stage
= "init"; // what the watchdog reports
int
(field) int app.State.watchdogMs
watchdogMs
= 15000;
} __gshared
(struct) app.State
State
_error_ app.g
g
;
// --------------------------------------------------------------------------- // Crash + hang capture: the verdict line must survive anything. extern (Windows) LONG
app.sehFilter
sehFilter
(EXCEPTION_POINTERS* ep) nothrow
undefined identifier `LONG`
undefined identifier `EXCEPTION_POINTERS`
{ const
_error_ code
code
= ep && ep.ExceptionRecord ? ep.ExceptionRecord.ExceptionCode : 0;
logEvent("probe n=%d result=crash detail=seh code=0x%08lx stage=%s", g.
g.probe
probe
, code, g.
g.stage
stage
);
ExitProcess(0); return EXCEPTION_EXECUTE_HANDLER; // not reached } extern (Windows) uint
uint app.watchdogProc(void* arg) nothrow
watchdogProc
(void*
(parameter) void* arg
arg
) nothrow
{ Sleep(cast(DWORD) cast(size_t) arg);
undefined identifier `Sleep`
// Probe 4b *expects* to land here: the deadlock is the finding. logEvent("probe n=%d result=deadlock detail=watchdog_fired stage=%s",
undefined identifier `logEvent`
g.
g.probe
probe
, g.
g.stage
stage
);
ExitProcess(0);
undefined identifier `ExitProcess`
return 0; } void
void app.armWatchdog(int ms) nothrow
armWatchdog
(int
(parameter) int ms
ms
) nothrow
{ CloseHandle(CreateThread(null, 0, &watchdogProc,
undefined identifier `CloseHandle`
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.wndProc
wndProc
(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) nothrow
undefined identifier `LRESULT`
undefined identifier `HWND`
undefined identifier `UINT`
undefined identifier `WPARAM`
undefined identifier `LPARAM`
{ 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.postT0
postT0
, GetCurrentThreadId());
return 0; case WM_MUTUAL: import core.atomic : atomicOp;
(template instance) atomicOp!"+="
atomicOp
!"+="(*cast(shared LONG*)&g.
g.mutualRecv
mutualRecv
, 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_ ps
ps
;
HDC
_error_ dc
dc
= BeginPaint(hwnd, &ps);
const
_error_ tid
tid
= GetCurrentThreadId();
// Visible activity per thread; the count is the probe-2 evidence. RECT
_error_ rc
rc
;
GetClientRect(hwnd, &rc); FillRect(dc, &rc, cast(HBRUSH)(COLOR_WINDOW + (tid & 1))); EndPaint(hwnd, &ps); if (tid == g.
g.mainTid
mainTid
)
++g.
g.mainPaints
mainPaints
;
else ++g.
g.workerPaints
workerPaints
;
return 0; default: return DefWindowProcW(hwnd, msg, wp, lp); } } HWND
app.makeWindow
makeWindow
(const(wchar)* title) nothrow
undefined identifier `HWND`
{ HWND
_error_ h
h
= CreateWindowExW(0, "wsi-f17-class"w.ptr, title,
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 320, 240, null, null, g.
g.inst
inst
, 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) nothrow
pumpUntilQuit
(int
(parameter) int timeoutMs
timeoutMs
) nothrow
{ const
(local variable) const(_error_) deadline
deadline
= nowUs() + cast(long)
(parameter) int timeoutMs
timeoutMs
* 1000;
undefined identifier `nowUs`
MSG
(local variable) _error_ m
m
;
undefined identifier `MSG`
int
(local variable) int dispatched
dispatched
;
while (nowUs() < deadline)
undefined identifier `nowUs`
{ while (PeekMessageW(&m, null, 0, 0, PM_REMOVE))
undefined identifier `PeekMessageW`
{ if (m.message == WM_QUIT)
undefined identifier `WM_QUIT`
return
(local variable) int dispatched
dispatched
;
TranslateMessage(&m);
undefined identifier `TranslateMessage`
DispatchMessageW(&m);
undefined identifier `DispatchMessageW`
++
(local variable) int dispatched
dispatched
;
} MsgWaitForMultipleObjects(0, null, FALSE, 20, QS_ALLINPUT);
undefined identifier `MsgWaitForMultipleObjects`
} return
(local variable) int dispatched
dispatched
;
} // --------------------------------------------------------------------------- // 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) nothrow
worker1
(void*
(parameter) void* arg
arg
) nothrow
{ g.
g.workerWnd
workerWnd
= makeWindow("wsi-f17-worker"w.ptr);
logEvent("thread=worker action=window_created hwnd=%p tid=%lu ok=%d",
undefined identifier `logEvent`
g.
g.workerWnd
workerWnd
, GetCurrentThreadId(), g.
g.workerWnd
workerWnd
!is null ? 1 : 0);
foreach (
(local variable) int i
i
; 0 .. 10)
PostMessageW(g.
g.workerWnd
workerWnd
, WM_PING, i, 0);
undefined identifier `PostMessageW`
logEvent("thread=worker action=posted count=10 to_own_window=1");
undefined identifier `logEvent`
SetEvent(g.
g.evReady
evReady
);
undefined identifier `SetEvent`
WaitForSingleObject(g.
g.evGo
evGo
, 10000);
undefined identifier `WaitForSingleObject`
// 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_ m
m
;
undefined identifier `MSG`
while (PeekMessageW(&m, null, WM_PING, WM_PING, PM_REMOVE))
undefined identifier `PeekMessageW`
++g.
(field) _error_ g.drained
drained
;
logEvent("thread=worker action=drained wm_ping=%d", g.
g.drained
drained
);
undefined identifier `logEvent`
DestroyWindow(g.
g.workerWnd
workerWnd
); // must happen on the creating thread
undefined identifier `DestroyWindow`
SetEvent(g.
g.evDone
evDone
);
undefined identifier `SetEvent`
return 0; } void
void app.probe1() nothrow
probe1
() nothrow
{ g.
g.stage
stage
= "p1_create_on_worker";
HANDLE
(local variable) _error_ t
t
= CreateThread(null, 0, &worker1, null, 0, &g.
g.workerTid
workerTid
);
undefined identifier `HANDLE`
undefined identifier `CreateThread`
WaitForSingleObject(g.
g.evReady
evReady
, 10000);
undefined identifier `WaitForSingleObject`
// 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_ m
m
;
undefined identifier `MSG`
int
(local variable) int sawThreadWide
sawThreadWide
,
(local variable) int sawHwndFiltered
sawHwndFiltered
;
const
(local variable) const(_error_) deadline
deadline
= nowUs() + 500_000;
undefined identifier `nowUs`
while (nowUs() < deadline)
undefined identifier `nowUs`
{ while (PeekMessageW(&m, null, WM_PING, WM_PING, PM_REMOVE))
undefined identifier `PeekMessageW`
++
(local variable) int sawThreadWide
sawThreadWide
;
if (PeekMessageW(&m, g.
g.workerWnd
workerWnd
, 0, 0, PM_NOREMOVE))
undefined identifier `PeekMessageW`
++
(local variable) int sawHwndFiltered
sawHwndFiltered
;
Sleep(10);
undefined identifier `Sleep`
} logEvent("main_hunt thread_wide=%d hwnd_filtered=%d tid=%lu",
undefined identifier `logEvent`
sawThreadWide, sawHwndFiltered, GetCurrentThreadId()); SetEvent(g.
g.evGo
evGo
);
undefined identifier `SetEvent`
WaitForSingleObject(g.
g.evDone
evDone
, 10000);
undefined identifier `WaitForSingleObject`
WaitForSingleObject(t, 5000);
undefined identifier `WaitForSingleObject`
CloseHandle(t);
undefined identifier `CloseHandle`
const
(local variable) const(_error_) ok
ok
=
(local variable) int sawThreadWide
sawThreadWide
== 0 &&
(local variable) int sawHwndFiltered
sawHwndFiltered
== 0 && g.
(field) _error_ g.drained
drained
== 10;
logEvent("probe n=1 result=%s detail=posted=10 main_saw=%d main_saw_hwnd_filtered=%d worker_drained=%d",
undefined identifier `logEvent`
ok ? "ok".ptr : "error".ptr, sawThreadWide, sawHwndFiltered, g.
g.drained
drained
);
} // --------------------------------------------------------------------------- // Probe 2 — worker creates and pumps its own window while main does the same. extern (Windows) uint
uint app.worker2(void* arg) nothrow
worker2
(void*
(parameter) void* arg
arg
) nothrow
{ g.
g.workerWnd
workerWnd
= makeWindow("wsi-f17-worker"w.ptr);
logEvent("thread=worker action=window_created hwnd=%p tid=%lu",
undefined identifier `logEvent`
g.
g.workerWnd
workerWnd
, GetCurrentThreadId());
SetEvent(g.
g.evReady
evReady
);
undefined identifier `SetEvent`
foreach (
(local variable) int i
i
; 0 .. 30)
{ InvalidateRect(g.
g.workerWnd
workerWnd
, null, TRUE);
undefined identifier `InvalidateRect`
MSG
(local variable) _error_ m
m
;
undefined identifier `MSG`
while (PeekMessageW(&m, null, 0, 0, PM_REMOVE))
undefined identifier `PeekMessageW`
DispatchMessageW(&m);
undefined identifier `DispatchMessageW`
Sleep(5);
undefined identifier `Sleep`
} DestroyWindow(g.
g.workerWnd
workerWnd
);
undefined identifier `DestroyWindow`
SetEvent(g.
g.evDone
evDone
);
undefined identifier `SetEvent`
return 0; } void
void app.probe2() nothrow
probe2
() nothrow
{ g.
g.stage
stage
= "p2_two_pumps";
g.
g.mainWnd
mainWnd
= makeWindow("wsi-f17-main"w.ptr);
HANDLE
(local variable) _error_ t
t
= CreateThread(null, 0, &worker2, null, 0, &g.
g.workerTid
workerTid
);
undefined identifier `HANDLE`
undefined identifier `CreateThread`
WaitForSingleObject(g.
g.evReady
evReady
, 10000);
undefined identifier `WaitForSingleObject`
foreach (
(local variable) int i
i
; 0 .. 30)
{ InvalidateRect(g.
g.mainWnd
mainWnd
, null, TRUE);
undefined identifier `InvalidateRect`
MSG
(local variable) _error_ m
m
;
undefined identifier `MSG`
while (PeekMessageW(&m, null, 0, 0, PM_REMOVE))
undefined identifier `PeekMessageW`
DispatchMessageW(&m);
undefined identifier `DispatchMessageW`
Sleep(5);
undefined identifier `Sleep`
} WaitForSingleObject(g.
g.evDone
evDone
, 10000);
undefined identifier `WaitForSingleObject`
WaitForSingleObject(t, 5000);
undefined identifier `WaitForSingleObject`
CloseHandle(t);
undefined identifier `CloseHandle`
DestroyWindow(g.
g.mainWnd
mainWnd
);
undefined identifier `DestroyWindow`
const
(local variable) const(_error_) ok
ok
= g.
(field) _error_ g.mainPaints
mainPaints
> 0 && g.
(field) _error_ g.workerPaints
workerPaints
> 0;
logEvent("probe n=2 result=%s detail=main_paints=%d worker_paints=%d concurrent_pumps=2",
undefined identifier `logEvent`
ok ? "ok".ptr : "error".ptr, g.
g.mainPaints
mainPaints
, g.
g.workerPaints
workerPaints
);
} // --------------------------------------------------------------------------- // Probe 3 — SendMessage blocks until the owner pumps; PostMessage does not. extern (Windows) uint
uint app.worker3(void* arg) nothrow
worker3
(void*
(parameter) void* arg
arg
) nothrow
{ WaitForSingleObject(g.
g.evReady
evReady
, 10000);
undefined identifier `WaitForSingleObject`
logEvent("thread=worker action=send_begin t=%lld owner_sleeping_ms=400", nowUs());
undefined identifier `logEvent`
const
(local variable) const(_error_) t0
t0
= nowUs();
undefined identifier `nowUs`
const
(local variable) const(_error_) r
r
= SendMessageW(g.
g.mainWnd
mainWnd
, WM_PING, 0, 0); // blocks: owner not pumping yet
undefined identifier `SendMessageW`
g.
g.sendLatencyUs
sendLatencyUs
= nowUs() - t0;
logEvent("thread=worker action=send_returned ret=%lld blocked_us=%lld",
undefined identifier `logEvent`
cast(long) r, g.
g.sendLatencyUs
sendLatencyUs
);
g.
g.postT0
postT0
= nowUs();
PostMessageW(g.
g.mainWnd
mainWnd
, WM_POSTED, 0, 0); // returns immediately
undefined identifier `PostMessageW`
logEvent("thread=worker action=post_returned after_us=%lld", nowUs() - g.
g.postT0
postT0
);
undefined identifier `logEvent`
PostMessageW(g.
g.mainWnd
mainWnd
, WM_DONE, 0, 0);
undefined identifier `PostMessageW`
return 0; } void
void app.probe3() nothrow
probe3
() nothrow
{ g.
g.stage
stage
= "p3_send_vs_post";
g.
g.mainWnd
mainWnd
= makeWindow("wsi-f17-main"w.ptr);
HANDLE
(local variable) _error_ t
t
= CreateThread(null, 0, &worker3, null, 0, &g.
g.workerTid
workerTid
);
undefined identifier `HANDLE`
undefined identifier `CreateThread`
SetEvent(g.
g.evReady
evReady
);
undefined identifier `SetEvent`
logEvent("main action=sleep_no_pump ms=400"); // the measured gap
undefined identifier `logEvent`
Sleep(400);
undefined identifier `Sleep`
int app.pumpUntilQuit(int timeoutMs) nothrow
pumpUntilQuit
(5000);
WaitForSingleObject(t, 5000);
undefined identifier `WaitForSingleObject`
CloseHandle(t);
undefined identifier `CloseHandle`
DestroyWindow(g.
g.mainWnd
mainWnd
);
undefined identifier `DestroyWindow`
const
(local variable) const(_error_) ok
ok
= g.
(field) _error_ g.sendLatencyUs
sendLatencyUs
>= 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",
undefined identifier `logEvent`
ok ? "ok".ptr : "error".ptr, g.
g.sendLatencyUs
sendLatencyUs
);
} // --------------------------------------------------------------------------- // 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) nothrow
worker4
(void*
(parameter) void* arg
arg
) nothrow
{ g.
g.workerWnd
workerWnd
= makeWindow("wsi-f17-worker"w.ptr);
SetEvent(g.
g.evReady
evReady
);
undefined identifier `SetEvent`
WaitForSingleObject(g.
g.evGo
evGo
, 10000); // barrier: fire together with main
undefined identifier `WaitForSingleObject`
logEvent("thread=worker action=mutual_send_begin t=%lld", nowUs());
undefined identifier `logEvent`
const
(local variable) const(_error_) t0
t0
= nowUs();
undefined identifier `nowUs`
const
(local variable) const(_error_) r
r
= SendMessageW(g.
g.mainWnd
mainWnd
, WM_MUTUAL, 0, 0);
undefined identifier `SendMessageW`
logEvent("thread=worker action=mutual_send_returned ret=%lld blocked_us=%lld",
undefined identifier `logEvent`
cast(long) r, nowUs() - t0); SetEvent(g.
g.evDone
evDone
);
undefined identifier `SetEvent`
// 4b: park hard — not pumping, not in SendMessage, just a kernel wait. logEvent("thread=worker action=park_in_WaitForSingleObject infinite=1");
undefined identifier `logEvent`
WaitForSingleObject(g.
g.evNever
evNever
, INFINITE);
undefined identifier `WaitForSingleObject`
return 0; } void
void app.probe4() nothrow
probe4
() nothrow
{ g.
g.stage
stage
= "p4a_mutual_send";
g.
g.mainWnd
mainWnd
= makeWindow("wsi-f17-main"w.ptr);
HANDLE
(local variable) _error_ t
t
= CreateThread(null, 0, &worker4, null, 0, &g.
g.workerTid
workerTid
);
undefined identifier `HANDLE`
undefined identifier `CreateThread`
WaitForSingleObject(g.
g.evReady
evReady
, 10000);
undefined identifier `WaitForSingleObject`
SetEvent(g.
g.evGo
evGo
);
undefined identifier `SetEvent`
logEvent("main action=mutual_send_begin t=%lld", nowUs());
undefined identifier `logEvent`
const
(local variable) const(_error_) t0
t0
= nowUs();
undefined identifier `nowUs`
const
(local variable) const(_error_) r
r
= SendMessageW(g.
g.workerWnd
workerWnd
, WM_MUTUAL, 0, 0);
undefined identifier `SendMessageW`
const
(local variable) const(_error_) mainBlocked
mainBlocked
= nowUs() - t0;
undefined identifier `nowUs`
logEvent("main action=mutual_send_returned ret=%lld blocked_us=%lld",
undefined identifier `logEvent`
cast(long) r, mainBlocked); WaitForSingleObject(g.
g.evDone
evDone
, 5000);
undefined identifier `WaitForSingleObject`
logEvent("probe n=4 stage=mutual_send result=%s detail=both_returned wm_mutual_recv=%ld main_blocked_us=%lld",
undefined identifier `logEvent`
g.
g.mutualRecv
mutualRecv
== 2 ? "ok".ptr : "error".ptr, g.
g.mutualRecv
mutualRecv
, mainBlocked);
// 4b — worker is now parked in WaitForSingleObject(INFINITE). Sleep(100); // let it reach the wait
undefined identifier `Sleep`
g.
g.stage
stage
= "p4b_send_to_blocked_thread";
DWORD
(local variable) _error_ res
res
; // druntime declares the out param as PDWORD (PDWORD_PTR upstream)
undefined identifier `DWORD`
SetLastError(0);
undefined identifier `SetLastError`
const
(local variable) const(_error_) ok
ok
= SendMessageTimeoutW(g.
g.workerWnd
workerWnd
, WM_PING, 0, 0,
undefined identifier `SendMessageTimeoutW`
SMTO_NORMAL, 1500, &res); logEvent("main action=SendMessageTimeout ret=%d err=%lu timeout_ms=1500",
undefined identifier `logEvent`
cast(int) ok, GetLastError()); logEvent("main action=plain_send_begin expect=deadlock watchdog_ms=3000");
undefined identifier `logEvent`
void app.armWatchdog(int ms) nothrow
armWatchdog
(3000); // THIS ends the probe: verdict result=deadlock
SendMessageW(g.
g.workerWnd
workerWnd
, WM_PING, 0, 0); // never returns
undefined identifier `SendMessageW`
logEvent("probe n=4 stage=send_to_blocked result=silent detail=send_unexpectedly_returned");
undefined identifier `logEvent`
} // --------------------------------------------------------------------------- // 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) nothrow
worker5
(void*
(parameter) void* arg
arg
) nothrow
{ HDC
(local variable) _error_ wdc
wdc
= GetDC(g.
g.mainWnd
mainWnd
); // window DC acquired on THIS thread
undefined identifier `HDC`
undefined identifier `GetDC`
logEvent("thread=worker action=GetDC hdc=%p err=%lu", wdc, GetLastError());
undefined identifier `logEvent`
HDC
(local variable) _error_ mem
mem
= CreateCompatibleDC(wdc);
undefined identifier `HDC`
undefined identifier `CreateCompatibleDC`
enum
(constant) int app.worker5.W = 200
W
= 200,
(constant) int app.worker5.H = 150
H
= 150;
BITMAPINFO
(local variable) _error_ bmi
bmi
;
undefined identifier `BITMAPINFO`
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* bits
bits
;
HBITMAP
(local variable) _error_ dib
dib
= CreateDIBSection(null, &bmi, DIB_RGB_COLORS, &bits, null, 0);
undefined identifier `HBITMAP`
undefined identifier `CreateDIBSection`
auto
(local variable) _error_ old
old
= SelectObject(mem, dib);
undefined identifier `SelectObject`
int
(local variable) int okCount
okCount
,
(local variable) int failCount
failCount
;
DWORD
(local variable) _error_ firstErr
firstErr
;
undefined identifier `DWORD`
foreach (
(local variable) int i
i
; 0 .. 100)
{ auto
(local variable) uint* px
px
= cast(uint*)
(local variable) void* bits
bits
;
(local variable) uint* px
px
[0 ..
(constant) int app.worker5.W = 200
W
*
(constant) int app.worker5.H = 150
H
] = 0xff0000 | (cast(uint)
(local variable) int i
i
* 2 << 8); // frame-varying fill
SetLastError(0);
undefined identifier `SetLastError`
if (BitBlt(wdc, 20, 20, W, H, mem, 0, 0, SRCCOPY))
undefined identifier `BitBlt`
++
(local variable) int okCount
okCount
;
else { if (
(local variable) int failCount
failCount
== 0)
firstErr = GetLastError(); ++
(local variable) int failCount
failCount
;
} Sleep(3);
undefined identifier `Sleep`
} SelectObject(mem, old);
undefined identifier `SelectObject`
DeleteObject(dib);
undefined identifier `DeleteObject`
DeleteDC(mem);
undefined identifier `DeleteDC`
ReleaseDC(g.
g.mainWnd
mainWnd
, wdc);
undefined identifier `ReleaseDC`
logEvent("thread=worker action=blits done ok=%d fail=%d first_err=%lu",
undefined identifier `logEvent`
okCount, failCount, failCount ? firstErr : 0); g.
g.drained
drained
= okCount; // reuse the slot for the verdict
PostMessageW(g.
g.mainWnd
mainWnd
, WM_DONE, 0, 0);
undefined identifier `PostMessageW`
return 0; } void
void app.probe5() nothrow
probe5
() nothrow
{ g.
g.stage
stage
= "p5_cross_thread_bitblt";
g.
g.mainWnd
mainWnd
= makeWindow("wsi-f17-main"w.ptr);
HANDLE
(local variable) _error_ t
t
= CreateThread(null, 0, &worker5, null, 0, &g.
g.workerTid
workerTid
);
undefined identifier `HANDLE`
undefined identifier `CreateThread`
// Owner keeps pumping AND repainting underneath the foreign BitBlts. SetTimer(g.
g.mainWnd
mainWnd
, 1, 16, null);
undefined identifier `SetTimer`
const
(local variable) const(int) dispatched
dispatched
=
int app.pumpUntilQuit(int timeoutMs) nothrow
pumpUntilQuit
(10000);
KillTimer(g.
g.mainWnd
mainWnd
, 1);
undefined identifier `KillTimer`
WaitForSingleObject(t, 5000);
undefined identifier `WaitForSingleObject`
CloseHandle(t);
undefined identifier `CloseHandle`
DestroyWindow(g.
g.mainWnd
mainWnd
);
undefined identifier `DestroyWindow`
logEvent("probe n=5 result=%s detail=blits_ok=%d/100 owner_dispatched=%d owner_paints=%d",
undefined identifier `logEvent`
g.
g.drained
drained
== 100 ? "ok".ptr : "error".ptr, g.
g.drained
drained
, dispatched, g.
g.mainPaints
mainPaints
);
} // --------------------------------------------------------------------------- // Probe 6 — AttachThreadInput: GetFocus is per-input-queue state. extern (Windows) uint
uint app.worker6(void* arg) nothrow
worker6
(void*
(parameter) void* arg
arg
) nothrow
{ WaitForSingleObject(g.
g.evReady
evReady
, 10000);
undefined identifier `WaitForSingleObject`
HWND
(local variable) _error_ before
before
= GetFocus();
undefined identifier `HWND`
undefined identifier `GetFocus`
SetLastError(0);
undefined identifier `SetLastError`
const
(local variable) const(_error_) att
att
= AttachThreadInput(GetCurrentThreadId(), g.
g.mainTid
mainTid
, TRUE);
undefined identifier `AttachThreadInput`
HWND
(local variable) _error_ after
after
= GetFocus();
undefined identifier `HWND`
undefined identifier `GetFocus`
AttachThreadInput(GetCurrentThreadId(), g.
g.mainTid
mainTid
, FALSE);
undefined identifier `AttachThreadInput`
logEvent("thread=worker action=attach_probe before=%p attach_ret=%d err=%lu after=%p",
undefined identifier `logEvent`
before, att, GetLastError(), after); g.
g.workerWnd
workerWnd
= after; // smuggle the result to the verdict
g.
g.drained
drained
= att;
PostMessageW(g.
g.mainWnd
mainWnd
, WM_DONE, 0, 0);
undefined identifier `PostMessageW`
return 0; } void
void app.probe6() nothrow
probe6
() nothrow
{ g.
g.stage
stage
= "p6_attach_thread_input";
g.
g.mainWnd
mainWnd
= makeWindow("wsi-f17-main"w.ptr);
SetForegroundWindow(g.
g.mainWnd
mainWnd
);
undefined identifier `SetForegroundWindow`
SetFocus(g.
g.mainWnd
mainWnd
);
undefined identifier `SetFocus`
logEvent("main action=SetFocus hwnd=%p get_focus=%p", g.
g.mainWnd
mainWnd
, GetFocus());
undefined identifier `logEvent`
HANDLE
(local variable) _error_ t
t
= CreateThread(null, 0, &worker6, null, 0, &g.
g.workerTid
workerTid
);
undefined identifier `HANDLE`
undefined identifier `CreateThread`
SetEvent(g.
g.evReady
evReady
);
undefined identifier `SetEvent`
int app.pumpUntilQuit(int timeoutMs) nothrow
pumpUntilQuit
(5000);
WaitForSingleObject(t, 5000);
undefined identifier `WaitForSingleObject`
CloseHandle(t);
undefined identifier `CloseHandle`
DestroyWindow(g.
g.mainWnd
mainWnd
);
undefined identifier `DestroyWindow`
const
(local variable) const(_error_) seesFocus
seesFocus
= g.
(field) _error_ g.workerWnd
workerWnd
is g.
(field) _error_ g.mainWnd
mainWnd
;
logEvent("probe n=6 result=ok detail=attach_ret=%d focus_visible_after_attach=%d focus_hwnd=%p",
undefined identifier `logEvent`
g.
g.drained
drained
, seesFocus ? 1 : 0, g.
g.workerWnd
workerWnd
);
} // --------------------------------------------------------------------------- // Driver: child mode runs one probe; parent mode spawns every probe twice. int
int app.runProbe(int n) nothrow
runProbe
(int
(parameter) int n
n
) nothrow
{ g.
g.probe
probe
= n;
g.
g.mainTid
mainTid
= GetCurrentThreadId();
SetUnhandledExceptionFilter(cast(LPTOP_LEVEL_EXCEPTION_FILTER)&sehFilter);
undefined identifier `SetUnhandledExceptionFilter`
if (
(parameter) int n
n
!= 4) // probe 4 arms its own short watchdog at the right moment
void app.armWatchdog(int ms) nothrow
armWatchdog
(g.
(field) _error_ g.watchdogMs
watchdogMs
);
g.
g.evReady
evReady
= CreateEventW(null, FALSE, FALSE, null);
g.
g.evGo
evGo
= CreateEventW(null, FALSE, FALSE, null);
g.
g.evDone
evDone
= CreateEventW(null, FALSE, FALSE, null);
g.
g.evNever
evNever
= CreateEventW(null, TRUE, FALSE, null);
WNDCLASSEXW
(local variable) _error_ wc
wc
;
undefined identifier `WNDCLASSEXW`
wc.cbSize = WNDCLASSEXW.sizeof; wc.lpfnWndProc = &wndProc; wc.hInstance = g.
g.inst
inst
;
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
undefined identifier `RegisterClassExW`
logEvent("probe_start n=%d main_tid=%lu", n, g.
g.mainTid
mainTid
);
undefined identifier `logEvent`
switch (
(parameter) int n
n
)
{ case 1:
void app.probe1() nothrow
probe1
();
break; case 2:
void app.probe2() nothrow
probe2
();
break; case 3:
void app.probe3() nothrow
probe3
();
break; case 4:
void app.probe4() nothrow
probe4
();
break; case 5:
void app.probe5() nothrow
probe5
();
break; case 6:
void app.probe6() nothrow
probe6
();
break; default: logEvent("probe n=%d result=error detail=unknown_probe", n);
undefined identifier `logEvent`
break; } ExitProcess(0); // crash probes exit 0 too — crashing is their job
undefined identifier `ExitProcess`
return 0; } int
int D main(string[] args)
main
(
(alias) object.string = string
string
[]
(parameter) string[] args
args
)
{ instrumentInit("f17_win32");
undefined identifier `instrumentInit`
g.
g.inst
inst
= GetModuleHandleW(null);
foreach (
(parameter) string a
a
;
(parameter) string[] args
args
[1 .. $])
if (
(local variable) string a
a
.
(field) ulong string.length
length
== 9 &&
(local variable) string a
a
[0 .. 8] == "--probe=")
return
int app.runProbe(int n) nothrow
runProbe
(
(local variable) string a
a
[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");
undefined identifier `logEvent`
WCHAR[MAX_PATH]
(local variable) _error_ exe
exe
;
undefined identifier `MAX_PATH`
GetModuleFileNameW(null, exe.ptr, MAX_PATH);
undefined identifier `GetModuleFileNameW`
foreach (
(local variable) int n
n
; 1 .. 7)
{ foreach (
(local variable) int run
run
; 0 .. 2)
{ WCHAR[MAX_PATH + 32]
(local variable) _error_ cmd
cmd
;
undefined identifier `WCHAR`
int
(local variable) int p
p
;
cmd[p++] = '"'; for (int
(local variable) int i
i
= 0; exe[i]; ++i)
cmd[p++] = exe[i]; cmd[p++] = '"'; foreach (
(parameter) immutable(wchar) ch
ch
; " --probe=0"w)
cmd[p++] = ch; cmd[p - 1] = cast(WCHAR)('0' + n); cmd[p] = 0; STARTUPINFOW
(local variable) _error_ si
si
;
undefined identifier `STARTUPINFOW`
si.cb = STARTUPINFOW.sizeof; PROCESS_INFORMATION
(local variable) _error_ pi
pi
;
undefined identifier `PROCESS_INFORMATION`
logEvent("spawn probe=%d run=%d", n, run + 1);
undefined identifier `logEvent`
if (!CreateProcessW(null, cmd.ptr, null, null, TRUE, 0, null, null, &si, &pi))
undefined identifier `CreateProcessW`
{ logEvent("error what=CreateProcessW code=%lu", GetLastError());
undefined identifier `logEvent`
continue; } WaitForSingleObject(pi.hProcess, 30000);
undefined identifier `WaitForSingleObject`
DWORD
(local variable) _error_ code
code
= 0xdead;
undefined identifier `DWORD`
GetExitCodeProcess(pi.hProcess, &code);
undefined identifier `GetExitCodeProcess`
logEvent("child_exit probe=%d run=%d code=%lu", n, run + 1, code);
undefined identifier `logEvent`
CloseHandle(pi.hThread);
undefined identifier `CloseHandle`
CloseHandle(pi.hProcess);
undefined identifier `CloseHandle`
} } logEvent("exit code=0");
undefined identifier `logEvent`
return 0; }