Bound unresolved-artifact retry loop in ScaReachabilityTransformer - #12245
Bound unresolved-artifact retry loop in ScaReachabilityTransformer#12245jandro996 wants to merge 1 commit into
Conversation
- Cap re-queue attempts for classes whose watched artifact never resolves via a new MAX_UNRESOLVED_RETRIES=5 limit, tracked per class name and deduplicated once per heartbeat. - Document known limitation: the retry counter isn't reset per class-load lifecycle, so a class name reloaded after exhausting the cap in a discarded classloader inherits the exhausted count.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a52dac37c1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
🎯 Code Coverage (details) 🔗 Commit SHA: a52dac3 | Docs | View more details | Give us feedback! |
🟢 Java Benchmark SLOs — All performance SLOs passed
PR vs. master results
Commit: Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion. |
There was a problem hiding this comment.
The new retry state uses a plain HashSet. Other agent components can start class retransformation at the same time, so concurrent add and clear operations can damage retry tracking.
🤖 Datadog Autotest · Commit a52dac3 · What is Autotest? · @DataDog review to ask questions · Any feedback? Reach out in #autotest
What Does This Do
MAX_UNRESOLVED_RETRIES = 5and a@VisibleForTesting final ConcurrentHashMap<String, Integer> unresolvedAttemptCountsfield toScaReachabilityTransformer.processClass(), replaces the unconditional re-queue intopendingRetransformNameswith a capped one: a class whose watched artifact never resolves is retried at mostMAX_UNRESOLVED_RETRIEStimes, then dropped with a one-time debug log.countedThisHeartbeat, cleared at the top ofperformPendingRetransforms()), so multiple classloaders loading the same class name in one batch consume a single attempt, not one per classloader.transform()withclassBeingRedefined == null) stays unconditional — a new class-load event (redeploy, new classloader instance) re-arms the retry window by design.unresolvedAttemptCounts) a known, accepted limitation: the counter is keyed only by class name and isn't reset per class-load lifecycle, so a class name that exhausts the cap in one classloader's lifecycle inherits the exhausted count if reloaded later under a new classloader instance. Rare in practice; not fixed.Motivation
ScaReachabilityTransformerre-queued a class for retransformation indefinitely whenever its watched artifact could never be resolved (e.g. a dependency that isn't actually on the classpath), causing an unbounded retry loop on every heartbeat for that class.Additional Notes
Explicit accepted trade-off: the cap is uniform for both own-JAR and classpath-wide artifact resolution, and OSGi/fat-jar apps with genuinely late-loading dependencies may lose detection after 5 heartbeats instead of retrying forever. No new configuration flag was added to disable this behavior — bounded degradation was chosen over an unbounded detection window.
Contributor Checklist
type:and (comp:orinst:) labels in addition to any other useful labelsclose,fix, or any linking keywords when referencing an issueUse
solvesinstead, and assign the PR milestone to the issueJira ticket: APPSEC-69734
Note: Once your PR is ready to merge, add it to the merge queue by commenting
/merge./merge -ccancels the queue request./merge -f --reason "reason"skips all merge queue checks; please use this judiciously, as some checks do not run at the PR-level. For more information, see this doc.