Skip to content

Do not throw on breakpoint event for a replaced breakpoint - #37

Merged
MattParkerDev merged 1 commit into
MattParkerDev:mainfrom
nevse:fix/breakpoint-hit-and-module-race-v2
Aug 13, 2026
Merged

Do not throw on breakpoint event for a replaced breakpoint#37
MattParkerDev merged 1 commit into
MattParkerDev:mainfrom
nevse:fix/breakpoint-hit-and-module-race-v2

Conversation

@nevse

@nevse nevse commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

SetBreakpoints deactivates the old ICorDebugFunctionBreakpoints and drops the BreakpointInfos that own them. A hit that was already in flight then finds nothing:

var managedBreakpoint = _breakpointManager.FindByCorBreakpoint(functionBreakpoint);
ArgumentNullException.ThrowIfNull(managedBreakpoint);

Activate(false) cannot cancel a hit that has already happened — the process is frozen and the event is queued. The runtime callback does not take DapRequestAndRuntimeEventLock either, so draining the queue at the start of each request narrows the window without closing it: the event can land after the drain and before the replace.

Every other "not our case" branch in that method logs and continues, including the one for an unrecognised breakpoint type a few lines above. This does the same.

Re-sending one file's breakpoints every 20ms for 15s while the debuggee hits them, over SharpDbgInMemory:

throws hits handled as replaced
66db871 3–4
with this 0 1–6

It is not a freeze on current main — the failed-handler recovery catches the throw and continues the process, so the session survives. What is left is an ordinary race being reported as an unexpected failure and relying on a last-resort path: that recovery only continues if TryIsRunning returns S_OK, and it calls Continue() rather than ContinueWithVariableClear() like the rest of the method. On 0.1.9, before the recovery existed, the same hit froze the debuggee for good.

No test: reproducing it needs the 20ms churn above, and a test built on that timing would be flaky. Happy to add one if you can see a deterministic way in.

Rebased onto 534170d, SharpDbg.Cli.Tests 32 of 32.

(This PR originally also made _modules a ConcurrentDictionary. Dropped — that could not be reproduced on 0.1.12, see the comments.)

I agree to the terms of contributing as stated here

SetBreakpoints deactivates a file's ICorDebugFunctionBreakpoint objects and
drops the BreakpointInfo that owned them. A hit already in flight on one of
those reaches HandleBreakpoint, FindByCorBreakpoint returns null, and
ArgumentNullException.ThrowIfNull throws on the runtime event pump's thread.

Continue instead, the way an unrecognised breakpoint type is handled a few
lines above: the hit belongs to a breakpoint the client no longer has.

Measured with a probe that re-sends a file's breakpoints every 20ms while the
debuggee hits them, 15 second runs: 3-4 throws before, 0 after, with the
replaced-breakpoint path taken 1-6 times instead.
@MattParkerDev

Copy link
Copy Markdown
Owner

Regarding the decompiles test - I bumped the decompiler version which changed the output file - clear your SharpIdeSymbolsCache, that should fix it. Added a comment at the failing assertion, because I noticed this too.

@MattParkerDev

Copy link
Copy Markdown
Owner

@nevse The managed breakpoint change looks good.

Regarding the dictionary change - I'm not convinced this is necessary - the dap request and event processing lock is intended to fix the error you are reporting. 0.1.9, the version you reproduced it on, was before this locking was introduced. Please reproduce this on main/0.1.12

Also, is your MCP server no longer open source?

@nevse

nevse commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

That was it — cleared the cache and the suite is 32 of 32. My diagnosis in the description was wrong, sorry: it was stale decompiler output on my side, not anything of yours.

@nevse
nevse force-pushed the fix/breakpoint-hit-and-module-race-v2 branch from a67e64b to 4531dc4 Compare August 12, 2026 17:32
@nevse

nevse commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

You're right — dropped it. I hammered setFunctionBreakpoints while the debuggee loaded ~46 assemblies, 100k+ requests a run: 0.1.9 threw it once in six runs, 0.1.12 never in five. Thin either way, but nothing to show on 0.1.12, and your reasoning about the lock holds.

And yes, it's open source — https://github.com/nevse/SharpDbg.MCP. I just haven't pushed since the start of the month, so none of what I've been quoting is up there yet.

@nevse nevse changed the title Do not throw on a replaced breakpoint, and make the module dictionary thread safe Do not throw when a breakpoint hit arrives for a replaced breakpoint Aug 12, 2026
@MattParkerDev

Copy link
Copy Markdown
Owner

@nevse please restore the footnote in the PR description, from the PR template

@nevse

nevse commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

Restored, and in #34 as well - I had dropped it in both.

@MattParkerDev MattParkerDev changed the title Do not throw when a breakpoint hit arrives for a replaced breakpoint Do not throw on breakpoint event for a replaced breakpoint Aug 13, 2026
@MattParkerDev
MattParkerDev merged commit 460cdcc into MattParkerDev:main Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants