The gap
DebugEngine::read_memory reaches the target with no deadline of any kind. Its neighbours have one —
execute_command_bounded and wait_for_event_bounded each arm a watchdog thread holding an
InterruptHandle and Ctrl+Break the engine when the caller's deadline passes — but a typed memory
read goes straight to ReadVirtual and returns when the target gets round to it.
On a dump or a local target that is unobservable. Over KDNET it is not: a read is bytes on a wire, and
windbg-mcp allows up to 1 MiB in one call. A slow-but-healthy link can take tens of seconds to
service that, and an unresponsive target longer still.
Why it matters to a caller
windbg-mcp's transactional batches (glslang/windbg-mcp#96) made this load-bearing. A batch is one
indivisible job on the engine thread, so when a session is torn down mid-transaction the worker tells
the supervisor when the batch will be done and the supervisor waits that out rather than
terminating the worker mid-rollback — on a live kernel, the difference between a patch restored and a
target left patched and halted.
That promise is derived from the batch's budget plus the overrun its executor allows
(batch::OVERRUN_ALLOWANCE), which accounts for watchdog floors — every operation a step can start
is bounded, except this one. A read_memory step is therefore the one place a batch can outrun any
figure the worker advertises.
The exposure today is bounded rather than open: the supervisor allows one more release grace past the
promise before it gives up (release_handoff), so a read has to overrun by more than five seconds
(disconnect) or twenty (end_session) to cost the rollback. A megabyte over a slow link can do that.
What would close it
A bounded variant beside the existing two — read_memory_bounded(addr, size, budget_ms), or a
deadline parameter on the current method — using the same watchdog machinery, so a caller that has
promised a deadline can arm the read to fit inside it.
Worth deciding at the same time whether the watchdog can actually interrupt a ReadVirtual in
progress: SetInterrupt is documented as safe from another thread, but src/dbgeng.rs:726 already
records one case it cannot reach (a live-kernel wait that has not yet connected). If a blocked read
is another, the honest answer may be that the caller's bound is unkeepable and the method should say
so rather than appear to take one.
Dependent change
Once it exists, windbg-mcp's BatchEngine::read_memory should pass the step's remaining budget the
way its command step already passes one to execute_command_bounded, and
batch::OVERRUN_ALLOWANCE's documentation should drop the caveat this issue is about.
Found by automated review on glslang/windbg-mcp#96, after merge.
The gap
DebugEngine::read_memoryreaches the target with no deadline of any kind. Its neighbours have one —execute_command_boundedandwait_for_event_boundedeach arm a watchdog thread holding anInterruptHandleand Ctrl+Break the engine when the caller's deadline passes — but a typed memoryread goes straight to
ReadVirtualand returns when the target gets round to it.On a dump or a local target that is unobservable. Over KDNET it is not: a read is bytes on a wire, and
windbg-mcpallows up to 1 MiB in one call. A slow-but-healthy link can take tens of seconds toservice that, and an unresponsive target longer still.
Why it matters to a caller
windbg-mcp's transactional batches (glslang/windbg-mcp#96) made this load-bearing. A batch is oneindivisible job on the engine thread, so when a session is torn down mid-transaction the worker tells
the supervisor when the batch will be done and the supervisor waits that out rather than
terminating the worker mid-rollback — on a live kernel, the difference between a patch restored and a
target left patched and halted.
That promise is derived from the batch's budget plus the overrun its executor allows
(
batch::OVERRUN_ALLOWANCE), which accounts for watchdog floors — every operation a step can startis bounded, except this one. A
read_memorystep is therefore the one place a batch can outrun anyfigure the worker advertises.
The exposure today is bounded rather than open: the supervisor allows one more release grace past the
promise before it gives up (
release_handoff), so a read has to overrun by more than five seconds(disconnect) or twenty (
end_session) to cost the rollback. A megabyte over a slow link can do that.What would close it
A bounded variant beside the existing two —
read_memory_bounded(addr, size, budget_ms), or adeadline parameter on the current method — using the same watchdog machinery, so a caller that has
promised a deadline can arm the read to fit inside it.
Worth deciding at the same time whether the watchdog can actually interrupt a
ReadVirtualinprogress:
SetInterruptis documented as safe from another thread, butsrc/dbgeng.rs:726alreadyrecords one case it cannot reach (a live-kernel wait that has not yet connected). If a blocked read
is another, the honest answer may be that the caller's bound is unkeepable and the method should say
so rather than appear to take one.
Dependent change
Once it exists,
windbg-mcp'sBatchEngine::read_memoryshould pass the step's remaining budget theway its
commandstep already passes one toexecute_command_bounded, andbatch::OVERRUN_ALLOWANCE's documentation should drop the caveat this issue is about.Found by automated review on glslang/windbg-mcp#96, after merge.