feat: provided-style extensions and manifest-libs#791
feat: provided-style extensions and manifest-libs#791
Conversation
There was a problem hiding this comment.
It looks like the pull request diff is too large to be displayed directly on GitHub. Here are some general recommendations for handling large pull requests:
-
Break Down the PR: Try to break down the pull request into smaller, more manageable chunks. This makes it easier to review and ensures that each part can be thoroughly checked.
-
Review Locally: Clone the repository and check out the branch locally. This allows you to review the changes using your preferred tools and environment.
-
Focus on Key Areas: Identify and focus on the key areas of the code that are most critical or have the highest impact. This can help prioritize the review process.
-
Automated Tools: Use automated code review tools and linters to catch common issues and enforce coding standards.
-
Collaborate with the Team: If possible, involve multiple reviewers to share the workload and get different perspectives on the changes.
If you can provide a smaller subset of the changes or specific files that need review, I'd be happy to help with a more detailed review.
f5dd242 to
0062717
Compare
There was a problem hiding this comment.
Pull request overview
This PR introduces provided-style extensions and manifest-driven library loading to enable tracing application-specific classes without polluting the application classpath. The core innovation is using reflection and object hand-off patterns to access application classes at runtime, keeping BTrace isolated.
Changes:
- New extension utilities (
ClassLoadingUtil,MethodHandleCache) for runtime class resolution and reflective method access - Manifest-driven library path resolution (
AgentManifestLibs) with security restrictions to BTRACE_HOME - Refactored agent classpath processing with deduplication and better error handling
- Example extensions for Spark and Hadoop demonstrating the provided-style pattern
- Comprehensive architecture documentation and migration guides
- Deprecation of
libs/profiles mechanism with escape hatch for edge cases
Reviewed changes
Copilot reviewed 25 out of 26 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
settings.gradle |
Added example extension modules (btrace-spark, btrace-hadoop) |
integration-tests/src/test/java/tests/RuntimeTest.java |
Fixed debug flag insertion and added timing delays for test stability |
integration-tests/src/test/java/tests/ManifestLibsTests.java |
New test suite for manifest-libs feature |
integration-tests/src/test/java/tests/BTraceFunctionalTests.java |
Improved unattended test with better synchronization and cleanup |
integration-tests/build.gradle |
Added test property for skipping preconfigured libs |
docs/examples/README.md |
Guide for building and configuring provided-style extension examples |
docs/architecture/provided-style-extensions.md |
Comprehensive guide on provided-style extension pattern |
docs/architecture/migrating-from-libs-profiles.md |
Migration guide from deprecated libs/profiles to extensions |
docs/architecture/agent-manifest-libs.md |
Design document for manifest-driven library loading |
compile.log |
Build artifact that should not be committed |
btrace-extensions/examples/btrace-spark/* |
Example Spark extension with API and provided-style implementation |
btrace-extensions/examples/btrace-hadoop/* |
Example Hadoop extension with API and provided-style implementation |
btrace-extension/src/main/java/org/openjdk/btrace/extension/util/MethodHandleCache.java |
Cache for reflective method handles to reduce lookup overhead |
btrace-extension/src/main/java/org/openjdk/btrace/extension/util/ClassLoadingUtil.java |
Helper utilities for TCCL-based class loading and service discovery |
btrace-client/src/main/java/org/openjdk/btrace/client/Main.java |
Removed exit hooks for list-only operations to prevent unintended probe removal |
btrace-client/src/main/java/org/openjdk/btrace/client/Client.java |
Added agent-already-running detection and system property pass-through |
btrace-agent/src/main/java/org/openjdk/btrace/agent/RemoteClient.java |
Improved disconnect handling with disconnecting flag |
btrace-agent/src/main/java/org/openjdk/btrace/agent/Main.java |
Refactored classpath processing with manifest support, deduplication, and escape hatch for single-jar injection |
btrace-agent/src/main/java/org/openjdk/btrace/agent/AgentManifestLibs.java |
New utility for manifest-driven library resolution with security checks |
README.md |
Added deprecation notice for libs/profiles with migration guidance |
|
@jbachorik I've opened a new pull request, #792, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@jbachorik I've opened a new pull request, #793, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@jbachorik I've opened a new pull request, #794, to work on those changes. Once the pull request is ready, I'll request review from you. |
ReDoS in
|
b2aab6c to
75229c8
Compare
| public void runBTrace(String[] args, ProcessOutputProcessor outputProcessor) throws Exception { | ||
| List<String> argVals = | ||
| new ArrayList<>( | ||
| Arrays.asList( | ||
| javaHome + "/bin/java", | ||
| "-cp", | ||
| cp, | ||
| "org.openjdk.btrace.boot.Loader", | ||
| debugBTrace ? "-v" : "", | ||
| "-cp", | ||
| eventsClassPath, | ||
| "-d", | ||
| Paths.get(System.getProperty("java.io.tmpdir"), "btrace-test").toString())); | ||
| if (debugBTrace) { | ||
| argVals.add(4, "-v"); // insert after Main class name |
There was a problem hiding this comment.
argVals is built with debugBTrace ? "-v" : "" and then (when debugBTrace=true) another -v is inserted at index 4. This results in a duplicate -v argument, and when debugBTrace=false it leaves an empty-string argument in the command line. Consider removing the placeholder element and only adding -v conditionally (without inserting empty args).
There was a problem hiding this comment.
Addressed in eaf1686 — removed the debugBTrace ? "-v" : "" ternary placeholder from the initial Arrays.asList(); -v is now added once via the existing conditional add(4, "-v") when debugBTrace=true.
| // Allow time for traced app to produce additional output after "ready:" | ||
| // BTrace INFO logs during agent init can exhaust stdoutLatch before app work begins | ||
| try { | ||
| Thread.sleep(1000L); | ||
| } catch (InterruptedException ie) { |
There was a problem hiding this comment.
This additional fixed Thread.sleep(1000L) adds another full second of delay even though there is already a 1s sleep immediately above for output flushing. Using two unconditional sleeps increases test runtime and can still be flaky; prefer waiting on a concrete condition (e.g., latch for expected app output) or reusing the existing flush wait.
There was a problem hiding this comment.
Intentionally kept — the two sleeps serve distinct purposes: the first allows late BTrace output to flush in on-startup mode, the second allows the traced app to produce output after ready:. Collapsing them into one risks missing app output. A latch-based approach would be preferable but requires broader refactoring of the test harness.
|
|
||
| This guide helps you diagnose and resolve common BTrace issues. | ||
|
|
||
| > **Note:** Examples use `btrace.jar` -- the single masked JAR. If using a legacy multi-JAR distribution, substitute `btrace.jar`. |
There was a problem hiding this comment.
The note says legacy multi-JAR users should “substitute btrace.jar”, which is the same value used in the example and doesn’t explain what to substitute. This looks like a copy/paste error; it should point to the legacy jar(s) (e.g., btrace-agent.jar / btrace-boot.jar) or otherwise clarify the substitution.
There was a problem hiding this comment.
Addressed in eaf1686 — note now reads: "If using a legacy multi-JAR distribution, replace btrace.jar with btrace-agent.jar (and add -Xbootclasspath/a:btrace-boot.jar where needed)."
| @@ -1,5 +1,7 @@ | |||
| # BTrace Tutorial (BTrace 2.3.0) | |||
|
|
|||
| > **Note:** Examples use `btrace.jar` -- the single masked JAR. If using a legacy multi-JAR distribution, substitute `btrace.jar`. | |||
There was a problem hiding this comment.
The note says legacy multi-JAR users should “substitute btrace.jar”, which is the same value used in the example. This looks incorrect/ambiguous; update it to reference the actual legacy jar name(s) or clarify what should be substituted.
There was a problem hiding this comment.
Addressed in eaf1686 — same correction applied to BTraceTutorial.md.
| 1. **JFR Integration** → [Getting Started: JFR Integration](GettingStarted.md#advanced-jfr-integration), [Tutorial Lesson 5](BTraceTutorial.md) | ||
| 2. **Sampling** → [Quick Reference: @Sampled](QuickReference.md#sampled), [FAQ: Performance](FAQ.md#performance-issues) | ||
| 3. **Aggregations** → [Quick Reference: Aggregation Functions](QuickReference.md#aggregation-functions) | ||
| 4. **Cloud Deployments** → [Getting Started: K8s](GettingStarted.md#btrace-in-containers-and-kubernetes), [FAQ: K8s](FAQ.md#can-i-use-btrace-with-microservices) | ||
| 5. **Fat Agent JAR** → [Getting Started: Fat Agent](GettingStarted.md#fat-agent-jar-single-jar-deployment) for single-JAR deployment | ||
| 6. **Level Filtering** → [Quick Reference: @Level](QuickReference.md#level) | ||
| 7. **Extensions Architecture** → [Extension invokedynamic Bridge](architecture/extension-invokedynamic-bridge.md) | ||
|
|
There was a problem hiding this comment.
Link target architecture/extension-invokedynamic-bridge.md does not exist in docs/architecture/ (the file present is ExtensionInvokeDynamicBridge.md). Also, this section uses a different filename casing (GettingStarted.md, QuickReference.md, etc.) than other links earlier in this doc; please make link casing consistent across the whole file to avoid broken links on case-sensitive filesystems.
There was a problem hiding this comment.
Addressed in eaf1686 — link target corrected to architecture/ExtensionInvokeDynamicBridge.md; link casing standardized throughout the "Documentation by Topic" section to match the capitalized filenames used elsewhere.
| * does not emit a misleading "Loaded bundled probe" info line. | ||
| */ | ||
| private static boolean loadEmbeddedProbe(String resourcePath, String probeName, boolean traceToStdOut) { | ||
| try (InputStream is = Main.class.getClassLoader().getResourceAsStream(resourcePath)) { |
There was a problem hiding this comment.
Main.class.getClassLoader() can be null when loaded from the bootstrap classloader; calling getResourceAsStream(...) on it will throw an NPE. Use the same null-safe resource loading approach used elsewhere (fallback to ClassLoader.getSystemResourceAsStream) so embedded probe lookup works reliably.
| try (InputStream is = Main.class.getClassLoader().getResourceAsStream(resourcePath)) { | |
| ClassLoader loader = Main.class.getClassLoader(); | |
| try (InputStream is = | |
| loader != null | |
| ? loader.getResourceAsStream(resourcePath) | |
| : ClassLoader.getSystemResourceAsStream(resourcePath)) { |
| public ClassDataLoader(String extensionId, ClassLoader resourceLoader, ClassLoader parent) { | ||
| super(parent); | ||
| this.extensionId = extensionId; | ||
| this.resourceLoader = resourceLoader; |
There was a problem hiding this comment.
ClassDataLoader stores resourceLoader as-is and later unconditionally calls resourceLoader.getResourceAsStream(...). If the loader is null (bootstrap), this will throw an NPE. Consider normalizing null to ClassLoader.getSystemClassLoader() (as done in EmbeddedExtensionRepository) or guarding at use sites.
| this.resourceLoader = resourceLoader; | |
| this.resourceLoader = | |
| resourceLoader != null ? resourceLoader : ClassLoader.getSystemClassLoader(); |
There was a problem hiding this comment.
Addressed in eaf1686 — constructor now normalizes: this.resourceLoader = resourceLoader != null ? resourceLoader : ClassLoader.getSystemClassLoader();
| List<Path> boot = List.of(tempDir.resolve("boot.jar")); | ||
| List<Path> system = List.of(tempDir.resolve("sys.jar")); | ||
|
|
There was a problem hiding this comment.
This test uses List.of(...) (Java 9+) which will fail if unit tests are ever executed on Java 8 (and contradicts the PR’s stated Java 8 compatibility goal). Prefer Collections.singletonList(...) / Arrays.asList(...) to keep tests Java-8-runtime compatible.
There was a problem hiding this comment.
Addressed in eaf1686 — replaced with Collections.singletonList() and added the required import java.util.Collections;.
| group = 'org.openjdk.btrace' | ||
| version = '2.3.0-SNAPSHOT' | ||
|
|
||
| java { | ||
| sourceCompatibility = JavaVersion.VERSION_11 | ||
| targetCompatibility = JavaVersion.VERSION_11 | ||
| } |
There was a problem hiding this comment.
This module hard-codes version = '2.3.0-SNAPSHOT', which can diverge from the repository’s root version and lead to inconsistent published artifacts. Prefer inheriting rootProject.version (or a shared version property) so the Maven plugin version stays aligned with the rest of the build.
There was a problem hiding this comment.
Addressed in eaf1686 — version = '2.3.0-SNAPSHOT' replaced with version = rootProject.version.
| List<String> argVals = | ||
| new ArrayList<>( | ||
| Arrays.asList( | ||
| javaHome + "/bin/java", | ||
| "-cp", | ||
| cp, | ||
| "org.openjdk.btrace.boot.Loader", | ||
| debugBTrace ? "-v" : "", | ||
| "-cp", | ||
| eventsClassPath, | ||
| "-d", | ||
| Paths.get(System.getProperty("java.io.tmpdir"), "btrace-test").toString())); | ||
| if (debugBTrace) { | ||
| argVals.add(4, "-v"); // insert after Main class name | ||
| } |
There was a problem hiding this comment.
The runBTrace(..., checkLines, ...) helper still builds argVals with debugBTrace ? "-v" : "" and then conditionally inserts another -v at index 4. This duplicates -v when enabled and leaves an empty-string arg when disabled. Remove the placeholder element and only add -v once when needed.
There was a problem hiding this comment.
Addressed in eaf1686 — same fix applied to the second runBTrace helper (lines ~1136–1147).
…ibs deprecation; provided-style helpers; examples; tests\n\n- agent: manifest-driven libs (flagged), centralized classpath append with dedup + diagnostics; deprecate libs (debug log); add single-jar system CL escape hatch; test knob scoped to manifest-libs\n- client: pass-through of btrace.* props to agent using $ system props\n- extension: add ClassLoadingUtil and MethodHandleCache for provided-style extensions\n- examples: add btrace-extensions/examples/{btrace-spark,btrace-hadoop} with README\n- docs: research + provided-style guide + migration + examples index; README deprecation note\n- tests: add ManifestLibsTests; gradle knob for btrace.test.skipLibs\n\nBREAKING CHANGE: libs/profiles deprecated with planned removal after N+2; prefer extensions
- Proactively retransform java.lang.Thread after startup scripts load - Move initExtensions() after transformer is installed - Add delay in RuntimeTest to allow traced app output after ready marker 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ation - Add disconnecting flag to RemoteClient for immediate probe visibility - Skip loadAgent when agent already running (check btrace.port) - Add try-finally cleanup in tests to prevent port conflicts 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…il/ClassLoadingUtil.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…usion Co-authored-by: jbachorik <738413+jbachorik@users.noreply.github.com>
… compatibility Co-authored-by: jbachorik <738413+jbachorik@users.noreply.github.com>
- Add ExtensionConfigurator interface for environment-aware probe selection - Add ProbeConfiguration and RuntimeEnvironment classes - Add ClassDataLoader for loading .classdata resources (dd-trace pattern) - Add EmbeddedExtensionRepository for discovering embedded extensions - Extend ExtensionDescriptorDTO with embedded, resourceBasePath, configuratorClass, bundledProbes fields - Register EmbeddedExtensionRepository in ExtensionLoader (lowest priority) - Handle embedded extensions in ExtensionLoaderImpl via ClassDataLoader 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- gradle: BTraceFatAgentPlugin with auto-discovery, project/maven/file sources - maven: btrace-maven-plugin with fat-agent goal (Gradle-built module) - fix: bootstrap classloader NPE in Main and EmbeddedExtensionRepository - docs: fat-agent-plugin architecture, getting started, migration guide 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add Zip Slip protection in FatAgentMojo (path traversal prevention) - Add extension ID validation to prevent path traversal attacks - Add bytecode validation in ClassDataLoader (magic number check) - Add class name validation for services and configurators - Validate ProbeConfiguration.setOutput() throws on invalid values - Improve ClassFilter documentation for sensitive method filtering - Add AgentManifestLibsTest (27 tests) - Add BTraceFatAgentPluginTest using Gradle TestKit (10 tests) - Add FatAgentMojoTest (26 tests) - Add SecurityValidationTest (17 tests) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The class-name validation pattern in EmbeddedExtensionRepository used ambiguous quantifiers where the inner character class allowed '$' and the outer group matched '$'-prefixed segments. On input like 'A$A$A$A' with a trailing mismatch, the engine explored exponentially many partitions (CodeQL js/redos, alert #15). Switch the three repetitions to possessive quantifiers (`*+`) so each commit step is final. Semantics are preserved for all accepted and rejected class names (verified against SecurityValidationTest). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…and plugins Applies 16 fixes surfaced by a multi-scholic review of the jb/configurations branch. Each item below was verified against current HEAD before editing. agent: - Main.loadEmbeddedProbe: close the probe InputStream via try-with-resources and stop returning true from a stub that installs nothing; log an operator warn so the probes= argument no longer silently succeeds. - Main.appendBootJar / appendSystemJar: synchronize the contains/appendTo/add block on the respective Set so two threads cannot both pass the dedup check and double-append the same jar. - Main libs deprecation: emit at log.warn (was hidden behind isDebugEnabled). - AgentManifestLibs.scanLibTree: close the Files.walk stream via try-with-resources (Files.walk is backed by a DirectoryStream). - AgentManifestLibs.locateAgentPath: broaden the catch to include IllegalArgumentException and FileSystemNotFoundException (both thrown by Paths.get(URI) for non-file code sources), and drop the dead Files.isDirectory/else branch that returned the same value in both arms. - AgentManifestLibs.filterAndNormalize: when toRealPath() fails, compare np against home.toAbsolutePath().normalize() instead of the raw home path so the fallback is at the same normalization level as np, and emit a warn so operators know symlink resolution was bypassed. - RemoteClient.reconnect: publish this.disconnecting=false last so the volatile write acts as a release fence for the prior sock/ois/oos writes. extension: - EmbeddedExtensionRepository.readManifestIndex: trim each id after splitting the BTrace-Embedded-Extensions manifest value so a human-written "ext1, ext2" no longer produces a leading-space id that isValidExtensionId rejects. - EmbeddedExtensionRepository.discoverBundledProbes: read the bundled probe list from the extension's properties file (probes=Probe1,Probe2) instead of always returning an empty list, so ExtensionDescriptorDTO actually carries the probes the extension declares. - MethodHandleCache: add a negative cache so a repeated lookup for a missing method fails fast via the cached LookupRuntimeException instead of re-entering the reflective machinery on every call. core: - ProbeConfiguration.setOutput: use toLowerCase(Locale.ROOT) instead of default-locale toLowerCase() so Turkish dotted/dotless-I does not make "JFR" / "FILE" fall through to IllegalArgumentException. maven plugin: - FatAgentMojo.execute: wrap the staging-directory lifetime in try-finally so deleteDirectory runs on every exit path, not just the happy one. gradle plugin: - BTraceFatAgentExtension Maven/File extractFromZip: switch from project.buildDir (deprecated in Gradle 7, removed in Gradle 9) to project.layout.buildDirectory.dir(...).get().asFile. housekeeping: - Remove the accidentally-committed top-level compile.log and add it to .gitignore so it cannot be reintroduced. Tests: full builds of btrace-core, btrace-extension, btrace-agent, btrace-maven-plugin, and btrace-gradle-plugin are green, including FatAgentMojoTest, AgentManifestLibsTest, SecurityValidationTest, MainTest, and ExtensionBridgeImplPolicyTest. The ClassDataLoader double-define race also surfaced in the review but the chosen fix is still under discussion (see inline PR comment on ClassDataLoader.findClass re: registerAsParallelCapable vs. synchronized block on getClassLoadingLock) and is deliberately not included in this commit. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two threads racing findClass for the same name could both reach defineClass, which the JVM rejects with LinkageError. Guard the cache-check + defineClass block with synchronized(getClassLoadingLock(name)) so only one thread per name defines, while a lock-free fast path still handles the warm-cache case for already-loaded classes. The synchronized block is chosen over registerAsParallelCapable because it protects direct findClass callers in addition to the standard loadClass delegation path, and keeps the invariant locally visible in findClass itself. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ClassDataLoader: fix double-define race in findClass() by adding registerAsParallelCapable() and synchronized(getClassLoadingLock(name)) with double-checked locking to prevent LinkageError from concurrent class definitions - EmbeddedExtensionRepository: fix regex backtracking in isValidClassName() by removing $ from package-segment character class, and extract static Pattern field to avoid recompilation on each call Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…build.gradle A rebase conflict resolution left a commit message fragment appended to a systemProperty line, causing Groovy to fail parsing at line 77. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…itecture) The masked JAR architecture merged agent + boot into a single `btraceJar` task. The fat agent config still referenced the old `agentJar` and `bootJar` tasks which no longer exist, causing build failures. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ient.java - Main.java:898: commit message fragment appended to closing brace - RemoteClient.java:351: reference to non-existent `disconnecting` field from pre-rebase branch (develop uses `disconnected`) Both were leftovers from the rebase conflict resolution. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rebase conflict resolution left a spurious `try {` block wrapping
testOnMethodUnattended() without a matching catch/finally, causing
a compile error.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ManifestLibsTests was failing on CI with "Port 2020 unavailable" because a previous test class (BTraceFunctionalTests) left the default BTrace port briefly occupied. Use port 2022 to avoid contention. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The btrace CLI process launched by RuntimeTest.attach() was not receiving the configured port — it defaulted to 2020 regardless of btrace.port. Pass -Dbtrace.port and -p <port> to the spawned java process so ManifestLibsTests (port 2022) avoids contention with other tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace btrace-agent.jar/btrace-boot.jar references with btrace.jar across all docs (README, GettingStarted, FAQ, Troubleshooting, QuickReference, BTraceTutorial, fat-agent-plugin arch doc) - Fix broken doc links (casing: btrace-extension-development-guide.md → BTraceExtensionDevelopmentGuide.md; remove nonexistent extension-configuration.md reference) - Update fat agent plugin docs: agentJar/bootJar → btraceJar task, document ClassDataLoader thread-safety (registerAsParallelCapable, per-class locking) - Update Gradle plugin README: remove obsolete bootJarTask config - Add JDK 25 Thread method exclusions to Troubleshooting.md - Add test port isolation note to CLAUDE.md Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Applied via /muse:implement (direct mode): - DC-08/DC-19: Remove duplicate -v flag and empty-string arg from RuntimeTest.java runBTrace helpers - DC-10/DC-11: Fix confusing legacy JAR note in docs (substitute → replace with legacy jar names) - DC-12: Fix broken link in docs/README.md (ExtensionInvokeDynamicBridge.md) + standardize link casing - DC-13/DC-14: Fix BOOT_ADDED/SYSTEM_ADDED ordering — add to set AFTER successful append to prevent stale state on failure - DC-15: Null-safe classloader in loadEmbeddedProbe (bootstrap classloader guard) - DC-16: Normalize null resourceLoader in ClassDataLoader constructor to system classloader - DC-17: Replace List.of() with Collections.singletonList() for Java 8 compatibility - DC-18: Use rootProject.version in btrace-maven-plugin/build.gradle DC-01/02/03/04/05/06/07 were already resolved in the branch. DC-09 deferred (removing sleep risks test flakiness without a concrete condition mechanism). <!-- muse-session:impl-20260417-225428 --> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After rebasing onto develop (which added PR #805's btracePort field and conditional -p block), the unconditional -p inserted by PR #791 collides with the conditional block, producing two -p flags when a test sets btracePort. The btrace CLI rejects the duplicate and prints usage, causing ExtensionLifecycleIntegrationTest to fail on every JDK. Drop the unconditional pair from runBTraceDynamic so only the existing conditional block adds -p when btracePort > 0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
eaf1686 to
36cbf1d
Compare
Rebasing onto develop brought in PR #805's conditional if (btracePort > 0) { add -p btracePort } block, which collides with PR #791's unconditional "-p", String.valueOf(getBTracePort()) in runBTraceDynamic — producing two -p flags when a test overrides btracePort. Keep the unconditional form, since getBTracePort() already honours both the develop-style btracePort field (ExtensionLifecycleIntegrationTest) and the PR-style btrace.port system property (ManifestLibsTests), and drop the now-redundant conditional block. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
36cbf1d to
6295796
Compare
Operational logging & error handling: - Main.java: log bootstrap/system jar append failures at warn (was debug), Thread.class retransform failure at warn (was debug), and warn when both deprecated libs= and manifest-based libs are in use - AgentManifestLibs: upgrade skip/validation log levels from DEBUG to INFO (non-existent / non-jar entries) and WARN (path resolution exceptions) - Main.java: add FIXME/TODO marker on loadBundledProbes() placeholder Exception handling & resource lifecycle: - ClassDataLoader.findClass: preserve IOException cause when throwing ClassNotFoundException - MethodHandleCache: drop the negative cache; a failed lookup no longer permanently poisons the cache, so classes that become available later are found on retry - FatAgentMojo.createJar: wrap Files.walk() in try-with-resources to release file handles on all exit paths Classloader & instrumentation: - ClassDataLoader: add canonical-pattern comment above registerAsParallelCapable() with JDK javadoc reference - ClassFilter: expand inline comment on Thread-method exclusions to explain why getUncaughtExceptionHandler stays in the exclusion set (dispatchUncaughtException is the recursion source but probes hooking the getter could still re-enter the dispatcher path) - Main.java: add comment on Thread.class retransform ordering clarifying that the current post-startScripts / pre-initExtensions placement is intentional Extension repository: - EmbeddedExtensionRepository.readManifestIndex: iterate all BTrace-Embedded-Extensions manifests instead of returning on first match; dedupe extension IDs and warn on duplicates with the offending manifest URL - EmbeddedExtensionRepository: introduce a BOOTSTRAP_SENTINEL classloader + isBootstrap flag so null-classloader semantics are explicit rather than coalesced Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- AgentManifestLibs.filterAndNormalize: javadoc describes that the returned list preserves LinkedHashSet insertion order (manifest declaration order), that callers must supply the set in desired precedence order, and that the first entry wins for duplicate class names. - ExtensionLoaderImpl.loadEmbedded: comment at the ClassDataLoader construction site explains that a null parentClassLoader is intentional and well-defined — ClassLoader(ClassLoader) treats null as "use the bootstrap classloader as parent", which is the correct delegation chain when BTrace runs on the bootstrap classpath. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Review-fix commits pushedAddressed 17 of 23 accepted findings from the muse chorus review (session
Highlights
Verification
Deferred for follow-up6 findings were not addressed in these commits because they need architectural or design decisions best handled separately:
The user also dismissed two earlier findings with explicit rationale: ClassDataLoader 🤖 Generated with Claude Code |
Summary
This PR introduces provided-style extensions and fat agent deployment for BTrace, enabling application class tracing without classpath pollution and single-JAR deployment for distributed environments.
Features
Provided-Style Extensions
ClassLoadingUtilloads classes from application context at trace timeMethodHandleCacheprovides efficient, cached reflective accesslibs=parameterEmbedded Extensions & Fat Agent
.classdataresources with thread-safe parallel class loading (registerAsParallelCapable+ per-class locking)org.openjdk.btrace.fat-agent) - Auto-discovers extensions, supports project/maven/file sourcesbtrace-maven-plugin) - Equivalent functionality for Maven usersBug Fixes
findClass()by addingregisterAsParallelCapable()andsynchronized(getClassLoadingLock(name))with double-checked locking to preventLinkageErrorfrom concurrent class definitionsEmbeddedExtensionRepository.isValidClassName()using possessive quantifiers and pre-compiled staticPatterninheritableThreadLocals,getAndClearInterrupt,dispatchUncaughtException, etc.) from instrumentation to prevent infinite recursionbtraceJartask (masked JAR architecture)ManifestLibsTestsuses port 2022 to avoid contention with other tests on default port 2020Documentation
btrace.jar(masked JAR) instead of legacybtrace-agent.jar/btrace-boot.jarDocumentation
Example Extensions
btrace-spark- Trace Spark job lifecyclebtrace-hadoop- Trace Hadoop file operationsUsage
Fat Agent (Gradle)
plugins { id 'org.openjdk.btrace.fat-agent' } btraceFatAgent { embedExtensions { project(':btrace-metrics') maven('io.btrace:btrace-statsd:2.3.0') } }Fat Agent (Maven)
Breaking Changes
libs=andprofiles=deprecated (removal planned N+2)🤖 Generated with Claude Code
This change is