Repository navigation
fix(agent): keep detached probes reconnectable; add Client.connectAndExitProbe - #967
Merged
Merged
Conversation
Reconnecting to a detached probe (btrace -r <probe-id>) connected and reported success, but every command sent afterwards was ignored: a probe's single inbound reader thread ended when its first client's stream closed, and RemoteClient.reconnect only swapped in the new transport. A reconnecting client sending EXIT or EVENT waited forever. The reader now treats the end of a client's stream as a detach: it drops only that transport (under a lock pairing sock and protocol, so a racing reconnect is never closed) and parks until reconnect installs a new one, exiting once the probe shuts down. The v2 protocol reported end of stream as a plain IOException, which took the error path in both the old and new reader; it now throws EOFException (still an IOException). A side effect: a detached probe no longer has its -o output file closed when its client goes away. Verified: new RemoteClientReconnectTest fails before and passes after; ./gradlew test -> 1512 passed, 0 failed (119 skipped); spotlessCheck green; -Pintegration :integration-tests:test -> 74 run, 0 failed (2 skipped), including the client-side stop of a detached probe added in the following commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Library hosts such as the BTrace MCP server can list detached probes (connectAndListProbes) but had no public way to stop one: the reconnect path behind the CLI's -r <probe-id> exit is package-private. connectAndExitProbe(host, probeId) reconnects, sends EXIT, returns once the agent confirms it, and always closes the connection; an unknown probe id surfaces as an IOException. The functional tests' submit-and-detach flow moves into a helper shared by testOnMethodUnattended and the new testStopDetachedProbe, and both now stop their target so its agent releases the BTrace port for later tests. Verified: ClientTest covers the stop and unknown-probe paths; -Pintegration :integration-tests:test -> 74 run, 0 failed (2 skipped), including testStopDetachedProbe against a real agent; ./gradlew test -> 1512 passed, 0 failed (119 skipped). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
getExtensionApiClasspath's classpath fallback took any entry whose path contained "btrace-client" for the client JAR. Run from a checkout or worktree whose directory name contains that string, every entry matched, and the client picked up an unrelated extensions/ directory as its extension API classpath. That made the lean-engine case of EmbeddedExtensionIntegrationTest fail on unmodified develop when built in a directory named btrace-client-*. Match only the entry's file name, btrace-client.jar or btrace-client-<suffix>.jar. Verified: new ClientTest.btraceHomeIsDerivedOnlyFromABtraceClientJar; EmbeddedExtensionIntegrationTest passes from a btrace-client-* worktree; full integration suite 74 run, 0 failed (2 skipped). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Collaborator
Author
|
Consumer: btraceio/agent-plugins#12 ( |
12 of 16 tasks
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.
What does this change do?
Reconnecting to a detached probe has never actually worked:
btrace -r <probe-id> exit|event <PID>connected and reported success, then every command it sent was ignored. A probe's single inbound reader thread ended when its first client's stream closed, andRemoteClient.reconnectonly swapped in the new transport. A client sendingEXITafter reconnecting waited forever. This came up while adding a "stop a detached probe" tool to the BTrace MCP server (btraceio/agent-plugins).Three commits:
fix(agent): keep detached probes reconnectable. The reader treats the end of a client's stream as a detach. It drops only that transport and parks untilreconnect()installs a new one, exiting once the probe shuts down.sockandprotocolare now swapped as a pair under a dedicated lock, so a racing reconnect can never have its new socket closed. The v2 protocol reported end-of-stream as a plainIOException, which sent both the old and the new reader down the error path; it now throwsEOFException(still anIOException).feat(client):Client.connectAndExitProbe(host, probeId). This is the public counterpart of the package-private-r <id> exitpath, for library hosts like the MCP server. It returns once the agent confirms the exit and always closes the connection; an unknown probe id is anIOException.fix(client): derive BTrace home only from abtrace-clientJAR.getExtensionApiClasspath()'s classpath fallback matched any entry whose path containedbtrace-client. From a checkout or worktree namedbtrace-client-*, every entry matched, and an unrelatedextensions/directory leaked onto the compile classpath. That madeEmbeddedExtensionIntegrationTest.leanEngineCannotLinkTheSameProbefail on unmodified develop built in such a directory (reproduced). It now matches the file name only:btrace-client.jarorbtrace-client-<suffix>.jar.Related issue
N/A. Needed by the
stop_detached_probeMCP tool in btraceio/agent-plugins (PR linked from there).Scope and compatibility
Compatibility or migration notes:
io.btrace.client.Client#connectAndExitProbe(String, String).BinaryProtocolend of stream is nowEOFException. It's a subtype of the previousIOExceptionwith the same message, and the wire format is unchanged.-ooutput file open and keeps writing. Before, the reader's exit path closed it, so a detached probe silently lost file output. The file is still closed when the probe exits.Testing
spotlessCheckCommands and results:
New tests:
RemoteClientReconnectTest: fails before the agent fix (the EXIT from the reconnected client never reaches the probe) and passes after.ClientTest.connectAndExitProbe*: covers the stop path and the unknown-probe path, and checks the socket is closed.ClientTest.btraceHomeIsDerivedOnlyFromABtraceClientJar.BTraceFunctionalTests.testStopDetachedProbe: deploys and detaches a real probe, stops it withconnectAndExitProbe, and asserts it's gone from-lp.Also verified end to end through the MCP server's e2e suite against a JAR built from this branch: 11 passed. Before the agent fix, the new stop case hung.
Notes:
launchTestAppfunctional tests now stop their target infinally. Before this,testOnMethodUnattendedleaked a target that held port 2020, which was harmless only because no later test in the class needed that port.:btrace-dist:builddoes not run the module unit tests (CLAUDE.md suggests it does), so./gradlew testwas run separately.Documentation and release impact
-r <id> exit|eventfix probably deserves an entry.)Release notes / follow-up work: The agent-plugins
stop_detached_probetool depends on this landing ondevelop, since its e2e CI builds BTrace's default branch.Final checklist
🤖 Generated with Claude Code
This change is