Repository navigation
feat: jdwp_set_fast_breakpoint and suspension ownership (#42, #45) - #46
Merged
Merged
Conversation
A concurrent check-then-act race for in-target probes (issue #42). Eight box-office windows sell 300 performances of 200,000 seats. About six seats per performance are sold twice, so the test fails on every run. It takes about 3-4 s and prints the elapsed time and ns per attempt, so it doubles as the measurement workload for fast logpoints and fast breakpoints; -Dboxoffice.seats and -Dboxoffice.performances scale it down for slow tools. The READMEs list the flight (now 10 flights, 30 stars), and the sandbox house rules add the consent rule for the fast probe tools.
FastBreakpointProbeSource generates the probe class of jdwp_set_fast_breakpoint. hit() evaluates the condition inside the target JVM and calls the empty stop(kind, epoch, error) only when the condition holds and its gate lets it through. The server's JDI breakpoint sits on stop(), so the instrumented method and hit() stay eligible for JIT compilation and inlining. - The true-hit gate (used in once mode) and the error gate (the first failure of each generation stops) have separate generations, EPOCH and ERROR_EPOCH, which only the server advances. A claim never moves a gate backwards, so an evaluation that started before a re-arm cannot claim the new generation. - Condition failures are recorded in the ring and passed to stop() as text. The error path never throws into the application. - TRUE_HITS, ERRORS and STOPS count the outcomes, and MCP_KIND marks the probe kind. FastLogpointProbeSource now shares its class start, common fields and ring recorder. The generated fast-logpoint source is unchanged apart from the extracted __mcpDescribe helper. JdiExpressionEvaluator gains compileFastBreakpointProbe on the shared probe-compilation path.
FastLogpointService now owns fast breakpoints alongside fast logpoints. They share the arming, the per-class instrumentation and the restore. Each entry carries a kind, its own arming suspend policy, the stop-once mode, server-side gate generations and a service-owned stop request on the probe's stop() method. - The arming hit evaluates the condition through the debugger. A hit whose condition holds or fails while its gate is open is published as a stop on the spot and installs nothing: redefining the method would leave the stopped frame in an obsolete method without locals. Any other arming hit installs the probe. The gates and the stop request are set up before the redefinition, and the probe is enabled last. - A probe stop is published (tracker snapshot, FAST_BREAKPOINT history entry, next-event latch) unless it is stale: the breakpoint was cleared, or its gate was re-armed since. A stop raised inside an MCP evaluation is suppressed and re-opens only the gate it claimed. - Clearing deletes the stop request and retires it, so an in-flight stop is resumed. Leftover discovery recognizes both probe kinds. JdiEventListener routes stop events to the service before the regular breakpoint handler, keeps an arming hit published as a stop suspended, and hands suppressed stops to the service. The service monitor is never held across a method invocation in the target. Covered by routing unit tests only. The next commit adds the tool and a live test that exercises these paths.
The tool sets a conditional breakpoint whose condition runs inside the target. Without userApproved=true it returns FastBreakpointRiskBriefing and arms nothing. Invalid arguments (a blank condition, a policy other than all/thread) are refused before the briefing. Calling it again with the same arguments re-arms an owned fast breakpoint: both gates move to their next generation. A fast breakpoint already set at the line with other arguments is refused with "clear it first". The reply warns when tracked breakpoints in the same method keep it interpreted. Fast breakpoints are listed in their own jdwp_overview / jdwp_clear section (type fast_breakpoint), with mode, policy and the probe's counters. A leftover shows neither its policy nor whether it is spent. A fast breakpoint's stop is reported as "fast_breakpoint=N, condition true" (or "condition failed: ...") by jdwp_resume_until_event, jdwp_get_current_thread and jdwp_get_breakpoint_context. jdwp_resume_until_event gives the line in the user's code, not the probe's stop(). Host and leftover refusals now name the right kind and fallback tool. Arming against a resume, found by the first live test: - An arming hit holds its thread through the event while the dispatcher invokes on it. "Set it, then jdwp_resume_until_event" called vm.resume() in that window and failed the arm, either with IncompatibleThreadStateException or with a misleading "no local JDK" from classpath discovery. - jdwp_resume and jdwp_resume_until_event now wait up to 30 s for an arming in flight. jdwp_resume_until_event waits before its pending-fire check, so a stop published by the arming hit is reported, not resumed. - A hit lost to a resume before anything was installed is re-armed. It records no condition error and no stop. - handleArmingHit returns an ArmingOutcome. For SPENT the dispatcher does not resume the set again: under suspendPolicy=all on a hot line, the fresh arming hit has already parked every thread, and a second vm.resume() would release it too. Tests: - FastBreakpointLiveTest covers the consent gate, once mode parking every thread with re-arm, every-hit with suspendPolicy=thread, a failing condition that stops once and then installs, and a true first hit that leaves the class untouched. - LiveSession is extracted from FastLogpointLiveTest so both live tests share it. - Unit tests cover the tool surface, the lost-hit path and the resume wait.
Where a fast breakpoint stops, the physical stack starts with the probe's stop() and hit() (and __mcpError() for a failed condition). FrameView numbers a suspended thread's stack without the leading frames of probe classes, of this session or an earlier one. Logical frame 0 is the user's frame that called hit(). Probe frames deeper in the stack, for example under a breakpoint inside a method the condition calls, stay visible. The view keeps only the hidden count, never a StackFrame, so it stays valid across invocations, and it classifies the prefix in one JDWP fetch. Every frameIndex and printed #N is now logical: jdwp_get_stack (with a "frames hidden" note), jdwp_get_locals, jdwp_evaluate_expression, jdwp_assert_expression, jdwp_set_local, jdwp_get_breakpoint_context, jdwp_get_current_thread, jdwp_resume_until_event and both watcher paths. Steps exclude every probe class, the listed ones and the one the thread stands in, before the count filter. A completed step inside an excluded class is restarted by the agent and does not use up the count. From a fast stop every kind of step therefore lands on the stop line, before its code, and the step reply says so. jdwp_set_local explains the JDK 21 limit on virtual threads (only the top frame's locals can change) instead of a bare OpaqueFrameException. jdwp_reset now reports "In-target probes cleared" and "Earlier sessions' probes", since both counts include fast breakpoints. Tests: - FrameViewTest covers the hidden prefix, deeper probe frames, a thread suspended inside hit() and an all-probe stack. - A tool test checks that a step from a fast stop adds the exclusions before the count filter. - A live case covers stack, locals, evaluation and set_local at the user's frame, and a step that lands on the stop line. The test waits for the step to land before jdwp_resume_until_event: until #45, that tool's blind vm.resume() can use up the suspension of a step event still in flight.
An object created through JDI is referenced by nothing in the target until it is pinned. RemoteCodeExecutor pinned the bytecode array and the class-name string only a few JDWP round trips after creating them, so a GC in between collected one and defineClass failed with ObjectCollectedException — seen as a spurious "condition failed" on a fast breakpoint's arming hit. Each mirror is now pinned right after its creation, and created again when a GC collected it first (up to three attempts). The Class.forName argument is pinned too. Every pin is released in a finally block.
A SuspensionSession, one per connection, owns what the debugger suspended. Each routed event set becomes a context (QUEUED -> PROCESSING -> HELD or RELEASED). The handlers' stops are candidates, published once the whole set is processed. Only the owning context releases its suspension, so a tool can no longer spend a suspension that is still being processed or was never shown. - jdwp_resume releases only the stops already reported and the manual holds. A stop nobody has seen stays held. - jdwp_resume_until_event presents the oldest unseen stop first, and resumes nothing. Otherwise it releases the seen stops and waits for the next one. It reports a reset or a closed session, and names what it released. - jdwp_suspend_thread / jdwp_resume_thread go through the session. resume_thread refuses a thread whose suspension is still queued, processing or unreported. - A step is installed atomically under the session, with the EVENT_THREAD policy, and releases the stop it steps from. Other threads stay parked until jdwp_resume. A new stop on the thread interrupts a pending step. - jdwp_reset deletes step requests, then releases every hold under a new generation. Events of stale requests are released without a stop. - jdwp_connect / jdwp_wait_for_attach wait up to 500 ms for VM_START and present it as a stop (via=vm_start). - jdwp_set_breakpoint refuses suspendPolicy=none with a condition. - An untracked breakpoint is a stop with no ID (via=untracked_breakpoint). - A set with the ALL policy re-suspends the threads released in between before it resumes, so it never releases another stop's hold. The arming stopgaps of the fast breakpoint commit are removed (awaitArmingIdle, ArmingOutcome.SPENT). A resumed arming thread now fails the arm. Arming requests are enabled while the VM is suspended. JDI learns a request's ID only from the enable reply, and an event raised before that reply is read is dropped as unknown. On a hot line that was the count-filter request's only event, and the fast logpoint or breakpoint never armed. Not yet in this commit: - leases, admission, exemptions, the invocation watchdog and the 20 s worker-invocation timeout; - a suspend-all event raised while the dispatcher invokes on another thread still freezes that invocation; - only step requests carry the generation stamp; - clearing a held breakpoint does not mention its held stop.
An object created through JDI is referenced by nothing in the target until it is pinned, so a GC in between collects it and the call it was made for fails with ObjectCollectedException. The previous commit closed this window in RemoteCodeExecutor only. Four more sites created a string and used it unpinned, the riskiest being the field-watchpoint $fieldName/$mode bindings, which stay unpinned across a whole expression compile. - PinnedMirrors (AutoCloseable) pins each object right after its creation, creates it again when a GC collected it first (three attempts), and releases every pin on close. RemoteCodeExecutor now uses it instead of its private copy. - Pinned now: the field-watchpoint bindings, the Class.forName argument of the force-load in JDIConnectionService, and the System.getProperty argument in ClasspathDiscoverer and JdkDiscoveryService. - The field-watchpoint bindings are built only when a condition or a logpoint expression can use them. A plain field breakpoint no longer creates two strings in the target on every hit.
An invocation borrows one suspension of its thread. When another stop also suspends that thread, the borrowed count leaves it suspended and the invocation never returns. The common case is a suspend-all of another thread arriving while the dispatcher evaluates a condition, a logpoint or an arming hit. The dispatcher then hangs, and every later stop with it. - EvaluationGuard.lease(thread) replaces enter/exit. A thread's outermost lease is taken in the SuspensionSession. The session admits the invocation and takes the thread out of every other context that suspends it (one resume each); a stop-all routed during a lease does the same. When the lease ends, the thread goes back into each context that still holds. A context released meanwhile re-suspends it before its own resume, so every count nets out. - Admission. The dispatcher may invoke on the thread of the set it processes. Any other invocation needs the thread stopped at an event of its own that a tool has shown. Otherwise it is refused with the reason (parked by another thread's stop, held by jdwp_suspend_thread, a stop not yet seen, VM start) instead of failing with IncompatibleThreadStateException or hanging. - An exemption at lease entry requires the thread's suspend count to show the other context's suspension. A hold undone behind the session's back cannot cost the lease its own suspension. - A lease spans invocations only, never frame reads. JDI invalidates every StackFrame of a thread the debugger resumes, also when the resume leaves it suspended. The evaluator reads the frame before leasing, and the arming leases each invocation group separately. Fixes found by two-core live loops: - The evaluator reads its frame again when a resume elsewhere invalidated it before the invocation (another set's release, the arming's VM-wide suspend). The define-time package fallback now uses a fresh frame too. - Arming retries a step whose frame was invalidated, as it already did for one whose object was collected. - A cleared probe's ring is nulled after its class is restored, not before. A hit that passed the kill switch just before it was set still wrote its entry. Tests: - FakeSuspendModel: the proposal's three exemption orderings, the admission refusals, and the lease facade. - FastBreakpointLiveTest reproduces the frozen dispatcher; it fails without the route-time exemption. - FastLogpointLiveTest releases its parked thread through the session instead of a raw resume. Not yet: the worker invocation thread with its timeout, inspection reservations, and the dispatcher watchdog.
… every hold (#45) Fixes found by an adversarial review of the leases (56d9493): - The guard counted leases per target thread, shared by every Java thread. A worker evaluating on a thread the dispatcher already held re-entered at depth 2 and skipped admission. If the first caller ended first, the session lease vanished while the other invocation still ran. The counter also outlived a reconnect. Now a hold belongs to one invocation chain (the Java thread running it) of one session. Only that chain re-enters, and any other caller is refused ("an evaluation on T is already running"). A hold is released by token identity, and binding a new session drops the earlier connection's holds. - Admission now compares the thread's suspend count with the holds the session knows on it: its stop, every other covering stop, and a manual hold. A lower count means a hold was undone outside the session. The lease is refused and nothing is resumed, where it used to skip exemptions on a count > 1 guess. A higher count is a set not routed yet, which routing exempts. - jdwp_suspend_thread can cross a breakpoint and leave a manual hold on top of the stop; the invocation then hung. That manual hold is now exempted for the lease and put back at its end. A manual release during the lease does not resume the thread twice. Tests: - FakeSuspendModel: one invocation per thread, and an invocation completes only while its thread runs. - New cases: competing chains, a reconnect with an equal thread ID, a stale hold, the manual-hold race, and a manual release during a lease. - The live case now asserts that the stop-all landed during the evaluation (routeExemptions in the session diagnostics).
… one's share (#45) Invoked code can block on a lock that a parked thread holds. MCP tool calls are serialized, so a tool blocked that way left no tool to release the thread with. - InvocationRunner runs a tool's invoking work on a cached daemon pool and waits at most 20 s. The job covers the whole work: classpath discovery, the frame read and the evaluation. It is used by jdwp_evaluate_expression, jdwp_assert_expression, jdwp_evaluate_watchers, jdwp_to_string and the six forceLoad paths; the dispatcher never uses it. - Work that outlives the wait keeps running. The tool answers [STILL_RUNNING], naming what blocks the thread: its status, and for a monitor wait the owner of that monitor (a brief, balanced suspend). - Its result is recorded later as EVALUATION_COMPLETED_LATE, only in the session it started in. - One compare-and-set settles the timeout against the completion, so a job yields either its result or exactly one late record. - A thread with a job still running refuses another job before anything is read from it. A job abandoned before it started never runs. - Releasing the stop an invocation borrows from (jdwp_resume, jdwp_reset) used to resume the thread at count 0 mid-invocation, a no-op. The invocation's restore then left the thread suspended with no owner. Now the borrower's share moves to its lease (a stop-all compensates it before its resume, a stop of that thread alone skips its resume), and the lease resumes it when it ends. Every other thread is released at once. - jdwp_resume_thread and a step on a thread an evaluation is running on are refused. Tests: - InvocationRunnerTest: result, failure, late record, busy thread, dropped connection, abandoned job, blocker diagnosis. - SuspensionSession: the transfer rows (thread-only and stop-all stops, released mid-invocation and between invocations, reset, another stop still holding), and the refusals. - FastBreakpointLiveTest: a 25 s evaluation answers [STILL_RUNNING] after 20 s. jdwp_resume releases its stop meanwhile, the late result arrives, and the thread then runs with no suspension left.
…invalidated frames (#45) jdwp_get_stack, jdwp_get_locals, jdwp_set_local and jdwp_get_breakpoint_context refuse a thread an evaluation is running on, before checking that it is suspended: during an invocation it runs, and its frames are the invoked code's. JDI invalidates every frame of a thread on any resume of it (another stop-all's release, the arming's VM-wide suspend) though the thread stays suspended. The tools now read fresh frames and rebuild the reply, at most three times, but only while a hold the session knows lasted across the attempt: SuspensionSession.holdsOn (the contexts holding the thread, a lease's exemption aside, plus the manual hold) taken before the attempt and after its failure must intersect. Otherwise the thread may have run on and stopped elsewhere; the tool says so, and jdwp_set_local that nothing was written. A write is retried only on InvalidStackFrameException, which JDI raises before sending it or when the target rejects the frame.
…ueued stop holds (#45) A condition, logpoint or arming evaluation on the dispatcher can block on a monitor whose owner a set still queued behind it keeps parked; the dispatcher never reaches that set, and no tool releases a set before it is processed. SuspensionWatchdog samples the dispatcher's invocations every 500 ms. One older than 3 s, blocked on a monitor in two consecutive samples of the same lease, has its monitor's owner asked for (a balanced suspend of the invoking thread), and SuspensionSession.unblock releases that owner - re-checked under the session monitor - only when it is held solely by queued or processing sets and its count is exactly theirs: never a thread under a published stop, a manual hold, a leased or reserved thread, or a hold the session does not know. A bystander of a stop-all joins its released set and the stop, if published, is reported as partial. The event thread of a queued set cancels the set: a thread-only set is settled at once (its resume is the thread's), so its later processing cannot spend the count of a stop the thread reaches meanwhile; a stop-all keeps its other threads until processed and is released with compensation. A cancelled set never publishes; the dispatcher only re-arms an arming hit, deletes a step request and handles a class prepare. A set with a fast breakpoint's stop is never cancelled (KEEPS_THREAD_PROPERTY). Frame tools reserve their thread for the watchdog. EVALUATION_UNBLOCKED and EVENT_CANCELLED record what was done.
…the release rules (#45) event-pipeline.md drops the "later set" resume rule and gains section 14, suspension ownership: contexts and their states, presentation, releases with compensation, steps, reset, leases and admission, the watchdog, the attach handshake. Sections 1-5, 8 and 9 now describe candidates and finish(), buffered history, suppression at routing, and nextStop(). threading-and-safety, architecture, lifecycle, expression-evaluation, diagnostics, testing, index, CLAUDE.md and the README follow; the EventHistory and JdiEventListener Javadoc no longer describe the removed rule. SuspensionOwnershipLiveTest gains the release rule against a real JVM: with a stop held and a stop-all queued behind a dispatcher evaluation, jdwp_resume releases only the held stop, the queued set keeps its threads, and its stop is shown later with its thread stopped.
…val wait for its release (#42) Redefining a method turns every frame in it obsolete, and an obsolete frame loses its locals. Fast logpoints and fast breakpoints now honour the redefinition rule both when they arm and when they are cleared. - SuspensionSession.heldThreads names the threads the user may see stopped: a context that is HELD, or QUEUED/PROCESSING in the current generation with a set that can stop; a jdwp_suspend_thread hold; a step in flight; and a suspension no set accounts for (a set raised but not routed yet). Under the VM pause, the rule waits up to 200 ms for the listener to route such sets, nearly always logpoint hits. - The listener judges at routing whether a set can stop (JdiEventListener. mayStop). Logpoints of every kind, fast-logpoint arming hits and class prepares never stop, so a hot jdwp_set_logpoint no longer starves arming. - Arming runs a check without a pause before compiling, then the authoritative check under vm.suspend() spanning check -> redefine. A deferred hit re-arms and records _ARMING_DEFERRED once per hold. An arming hit on an already-obsolete frame is a lost hit (re-armed), not a failure. - A clear blocked by the rule disables the entry at once (kill switch, stop request gone, ring drained) and keeps it as REMOVAL_PENDING. The class is restored at jdwp_resume, jdwp_resume_until_event, jdwp_resume_thread, jdwp_overview, or the service's next register, clear or drain. - jdwp_reset restores unconditionally, like disconnect and shutdown: it releases every stop right after, so no frame anyone sees turns obsolete. - Cost: about 50-100 ms of VM pause per install with roughly 200 threads. Found by the two-core gate loops: - BreakpointTracker forgot a cleared request before deleting it, and the listener could read a cleared logpoint's metadata after it was dropped. Either way a hit already queued stopped its thread as an untracked or plain breakpoint, and that stop now blocks every redefinition of its method. The tracker deletes first, and the listener drops a hit whose breakpoint is gone and whose request is deleted. - The live tests enable their one-shot park requests under vm.suspend(), as the arming requests are, and assert an immediate restore by its own reply. FastLogpointLiveTest Order 9 now proves that arming waits for a thread parked in the method.
…oint at a line, and dependencies that refuse fast IDs (#42) - jdwp_attach_watcher accepts a fast breakpoint this session set that can still stop (pending or armed); a fast logpoint, a leftover and a failed or disabled fast breakpoint are refused with the reason. - The full-stack watcher scan evaluates the watchers of every breakpoint at a frame's line, ordinary and fast: getBreakpointLocationMap maps a line to all its active IDs (it kept one, so a breakpoint and a logpoint on one line lost a watcher), merged with the service's fast breakpoints. - Clearing a fast breakpoint removes its watchers, in jdwp_clear_breakpoint and jdwp_clear, even while its restore waits; a reconnect drops the watchers of the fast breakpoints it disarms. - A breakpoint dependency refuses a fast breakpoint or fast logpoint as trigger or dependent. - FastLogpointService deletes an arming request before forgetting it, as the tracker does: a hit still queued can no longer stop as an untracked breakpoint. - Live: watchers at a fast stop in both scopes, two pending removals in one class restored together, and a restore refused after a foreign redefinition (the entry stays DISABLED, clear retries).
…ar while stopped restores (#42) - The risk briefing's point 6 said that clearing the breakpoint while a thread is stopped in the method restores the class at once, costing the stopped frame its locals. Since the redefinition rule the clear switches the breakpoint off at once and restores the class after that thread is released; jdwp_reset restores at once and releases every stop. - jdwp_clear_breakpoint, jdwp_reset and jdwp_restore_fast_logpoints describe fast breakpoints too; the clear also says it removes the breakpoint's watchers. - EventHistory lists the _ARMING_DEFERRED and _REMOVAL_DEFERRED types of both kinds.
…and java-debug skill (#42) - breakpoints.md: § 5c (fast breakpoints: probe, gates, arming, the stop, logical frames, watchers and dependencies, leftovers) and § 5d (never redefine under a stopped frame: held threads, the check, deferred arming and removal, cost); the clear routing, § 5a's disarm and § 5b's leftovers follow. - event-pipeline.md and testing.md: the § 5c/§ 5d references, the deferred event types and FastBreakpointLiveTest. - CLAUDE.md: 50 tools; fast breakpoints, FrameView and the redefinition rule. - README: 50 tools, the jdwp_set_fast_breakpoint row and feature section, watchers on fast breakpoints, what a reconnect drops; flight #10's debug path says what the tools show, and its par is 4 (the consent round trip counts). - java-debug skill: the fast-breakpoint recipe.
- The probe reads both gate generations before the condition runs: a failure of an evaluation that started before a re-arm can no longer claim the error generation the re-arm opened. - stop() is empty; the probe counts the call just before it makes it. A step into stop() from a fast stop entered AtomicLong.incrementAndGet. - Re-arming a fast breakpoint that still waits for its first hit reopens both gates: an arming hit whose condition failed had spent the error gate, and the next failure never stopped. - A stop request is retired before it is unmapped, so a queued stop never meets a request in neither registry (an untracked stop nobody releases). - stop() arguments are read again after a resume elsewhere invalidated the frame. Unreadable ones publish "reason unknown", never "condition true"; a suppressed stop with unreadable arguments reopens every gate it may have claimed. - A fast breakpoint's arming hit is handled before a fast logpoint's in the same event set: the logpoint's arming redefined the class and lost the breakpoint's hit, possibly its only true one. - An approved restore of a leftover follows the redefinition rule: refused, with nothing changed, while a thread is stopped in a restored method. - A leftover probe of an unknown kind is STRANDED, never offered for restore. - A field logpoint whose bindings cannot be built records FIELD_LOGPOINT_ERROR instead of stopping (its set was routed as never stopping). Docs: breakpoints.md 5c and 5d.
- A tool's job runs only at the stop it was started at: InvocationRunner records the thread's holds, and every lease inside the job is refused once none of them is left (SuspensionSession.admittedUnder). An evaluation that outlived its 20 s wait could run at the thread's next stop, even at the same line, which is another activation. - Frame tools refuse a thread while a job runs on it, also before the job leases the thread (still compiling). - The watchdog decides with the invoking thread suspended and re-checks, once the owner is proven parked, that it still owns the monitor: a stale owner sample could cancel an unrelated new stop. - The [STILL_RUNNING] report waits at most 2 s for JDI to say what blocks the thread. - A job of an ended connection no longer refuses a thread of the next one with the same ID. - A set routed as never stopping never publishes, not even a handler's fail-safe stop: the redefinition rule did not count its thread. - A leased thread whose stop was released mid-evaluation counts as an unknown hold for the redefinition rule until the evaluation ends. Docs: event-pipeline.md 14 and the fail-safe rule, threading-and-safety.md 5.
novoj
marked this pull request as ready for review
October 30, 2026 09:28
novoj
added a commit
that referenced
this pull request
Oct 30, 2026
…on ownership Minor release for PR #41 and PR #46. jdwp_set_fast_logpoint (#40) and jdwp_set_fast_breakpoint (#42) evaluate a logpoint or a breakpoint condition inside the target JVM through an injected probe class, so hits that don't matter never involve the debugger. Both rewrite application bytecode and arm nothing without userApproved=true. The original bytecode is restored on clear, reset, disconnect and shutdown; a method a stopped thread is in is never redefined; leftovers of a dead server are listed on the next attach and restored only with approval. Suspension ownership (#45, folding in #43 and #44): the listener only drains and routes, event sets are processed in order on a dispatcher thread, every stop is held in its own context and released only by its owner, and evaluations are leased, bounded to 20 s ([STILL_RUNNING], EVALUATION_COMPLETED_LATE) and watched for monitor deadlocks. Objects the debugger creates in the target are pinned against GC. Both features went through adversarial Codex design reviews and a code review before merge; the MAJOR findings are fixed, the rest is tracked in #47. Test flight #10 "The Oversold House" adds a highly concurrent sandbox target.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #42. Closes #45, and with it #43 and #44.
Fast breakpoints (#42)
jdwp_set_fast_breakpointevaluates a breakpoint's condition inside the target JVM, so hits that don't meet it never involve the debugger.stop()method, which carries an ordinary JDI breakpoint with the user's suspend policy. The user's method holds no breakpoint and stays JIT-compiled.userApproved=true. Without it, the tool returns a risk briefing.FrameView): every frame index the tools take or print counts from the user's frame. Steps never enter probe classes.jdwp_resetrestores at once. This applies to fast logpoints too.Test flight #10 ("The Oversold House") is a highly concurrent target for fast logpoints and fast breakpoints.
Suspension ownership (#45; folds in #43 and #44)
jdwp_resumereleases only the stops a tool has shown, plusjdwp_suspend_threadholds.jdwp_resume_until_eventshows an unseen stop first, without resuming anything.jdwp_resetstarts a new generation.[STILL_RUNNING], and a late result is recorded asEVALUATION_COMPLETED_LATE. Releasing a stop keeps the share a running evaluation still needs.PinnedMirrors).The developer docs cover both in
docs/breakpoints.md§ 5c–§ 5d anddocs/event-pipeline.md§ 14. The README,CLAUDE.mdand the java-debug skill describe the new tool (50 tools in all).Commits
212fce1FrameViewc1fc642,7cb2c37,d004360,dfa02d8862fffd,3e9f15ade4a72a,56d9493,ce65567,479ea94,a5c531e,03fa71c,a4439b92d2c686,ceb35621ea5bd6,901c1aace89b73(#42),47dc5e5(#45)Review
ce89b73and47dc5e5(for the two that held up in part, that part), as is the unknown-probe-kind MINOR. The remaining MINORs and test gaps are in Follow-ups from the PR #46 code review: per-stop reasons, per-invocation watchdog timing, late ring writes, race tests #47.Tests
-Djdwp.live=true, with the JaCoCo agent):FastBreakpointLiveTest14/14,FastLogpointLiveTest17/17 andSuspensionOwnershipLiveTest2/2.