-
Notifications
You must be signed in to change notification settings - Fork 362
Detect scope and continuation leaks in instrumentation tests #12458
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Open
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
30b4701
Introduce systematic continuation leakage diagnostics
amarziali 884d849
code hygiene
amarziali fdab466
use the right named
amarziali a066905
Add deferred cleanup use case
amarziali f587655
Fix diagnostic snapshot synchronization
amarziali c6fa7cf
Remove strictTraceWrite override
amarziali a18e3a7
Incorporate skill takeaways in the main text
amarziali 7079b31
Ensure scope diagnostics transform scope stacks
amarziali f3995b3
Skill update
amarziali e908e7d
review findings
amarziali 7e159dc
codex findings
amarziali 9803d38
Adapt continuation diagnostics to ContextScope
amarziali cbfc67c
avoid release race
amarziali a816d0c
Duplicate scope cleanup now reports double finish
amarziali 5ff233d
spotbugs
amarziali 3068262
harden javadoc
amarziali f307011
harden tests
amarziali c57b769
Report explicit continuation resolution as released
amarziali 2234d7b
Use earliest resume timestamp in continuation diagnostics
amarziali 6fbc832
Avoid dropping similarly named classes
amarziali 8411dfd
Avoid false activation-after-resolution findings for overlapping resumes
amarziali File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| --- | ||
| name: fix-continuation-leakage | ||
| description: > | ||
| Diagnose and fix scope or continuation lifecycle failures in dd-trace-java instrumentation | ||
| tests. Use when a test reports a continuation leak, double resolution, activation after resolve, | ||
| or an unclosed scope, or when strictTraceWrites(false) appears to hide one. Reads the automatic | ||
| diagnostic timeline, finds the broken lifecycle edge, fixes it, and explains it with a compact | ||
| Mermaid diagram. | ||
| user-invocable: true | ||
| context: fork | ||
| allowed-tools: | ||
| - Bash | ||
| - Read | ||
| - Edit | ||
| - Glob | ||
| - Grep | ||
| - AskUserQuestion | ||
| --- | ||
|
|
||
| # Fix continuation leakage | ||
|
|
||
| Instrumentation tests run the diagnostic automatically. A failure includes the capture, resume, | ||
| resolution, scope, thread, timing, and callsite data needed to find the missing lifecycle edge. | ||
|
|
||
| ## Work the failure | ||
|
|
||
| 1. Resolve the actual Gradle module path and failing suite from CI or the module's tasks. Nesting | ||
| varies (for example, `netty:netty-4.1` versus `kotlin-coroutines-1.3`). Use `forkedTest` for | ||
| `*ForkedTest*` classes, or the suite-specific forked task such as `latestDepForkedTest`. | ||
| Run the smallest failing test with full output: | ||
|
|
||
| ```bash | ||
| set -o pipefail | ||
| ./gradlew :dd-java-agent:instrumentation:<module-path>:<test-task> --tests '<FQCN-or-pattern>' --info 2>&1 | tee /tmp/scopediag-run.txt | ||
| ``` | ||
|
|
||
| 2. Verify the selected test ran in that task's XML results; a successful build with no discovered | ||
| tests is not validation. Find `Scope/continuation timeline` in the output. If Gradle hides it, inspect the test XML's | ||
| `<system-out>` under the module's `build/test-results` directory. | ||
| 3. Follow the failing record from its first event: | ||
| - `LEAKED` / `NEVER_CLOSED`: find the success, error, cancellation, and rejection exits that | ||
| skipped `release()` or `close()`. | ||
| - `DOUBLE_FINISH`: find two owners of the same cleanup. | ||
| - `ACTIVATE_AFTER_RESOLVE`: find work scheduled after ownership ended. | ||
| - `LATE_FINISH` / `CLOSE_WRONG_THREAD`: advisory evidence; verify whether ordering is valid. | ||
| - `[deferred-cleanup]`: a root iteration scope transferred cleanup to the bounded iteration | ||
| cleaner. It may remain open at the test boundary and is not a leak. Do not generalize this to | ||
| other `ITERATION` scopes; an unregistered iteration scope must still close normally. | ||
| 4. Classify the captured work before changing code: | ||
| - For a real asynchronous operation, repair success, failure, cancellation, and rejection | ||
| cleanup. | ||
| - If the test started the work, wait for its terminal event and dispose or close it before the | ||
| test ends. | ||
| - If a framework initializer creates a permanent sentinel with no context consumer, disable | ||
| propagation only around that creation boundary. Match the exact type and method, and update | ||
| `knownMatchingTypes()` when shortcut matching is used. | ||
| Check static initialization (`<clinit>`): first use under an active request can capture its | ||
| context in singleton tasks, including shaded Netty `GlobalEventExecutor` sentinels. Reproduce | ||
| first use in a fresh JVM; prewarming can hide the bug. Do not suppress all class initializers. | ||
| - If an executor replaces a task before delegating, avoid capturing the discarded task while | ||
| preserving capture for the task actually submitted. | ||
| - For intentionally delayed work, wait for its documented terminal event rather than | ||
| suppressing propagation. | ||
| - For a context swap, verify both restoration and resource cleanup. Restore or close the | ||
| returned ownership object in `finally`; do not ignore every swap. | ||
| 5. Prefer a test-lifecycle fix when production behavior is correct. Otherwise fix ownership where | ||
| it breaks, with one owner and `try/finally` cleanup across every exit. | ||
| 6. Rerun the failing test, then its module. Validate the leaked record and root-trace publication | ||
| separately from trace-count or arrival-order assertions; fixing a leak may expose an unrelated | ||
| flaky assertion. | ||
|
|
||
| ## Fixture setup failures | ||
|
|
||
| Automatic recording may start after `setupSpec()` or equivalent fixture initialization. For an | ||
| initialization error or a trace wait inside setup, temporarily record around that setup block and | ||
| remove the diagnostic scaffolding after finding the owner. | ||
|
|
||
| Apply process-wide configuration before starting servers, actor systems, executors, or other | ||
| long-lived fixtures. Use a forked test or recreate the fixture when its static state cannot be | ||
| reset safely. | ||
|
|
||
| ## Do not hide evidence | ||
|
|
||
| Do not make the test green with `strictTraceWrites(false)` or | ||
| `@TrackScopeContinuations(enabled=false, reason="...")`. Those hide evidence. The opt-out requires | ||
| a reason and is only for a proven diagnostic incompatibility. If the failure is genuinely | ||
| intermittent, treat that as a flaky-test finding, keep diagnostics enabled, and link the `@Flaky` | ||
| annotation to a tracked issue. | ||
|
|
||
| ## Assess production impact | ||
|
|
||
| An unresolved continuation blocks normal reference-count completion, not necessarily publication. | ||
| The production `PendingTrace` buffer can still write finished spans; strict tests remove that | ||
| delayed-write fallback, but partial flush remains possible. Do not infer lost traces or a fixed | ||
| UI delay from a diagnostic failure. Check the collector and configuration; see | ||
| [continuation effects](../../../docs/how_instrumentations_work.md#continuation-effects). | ||
|
|
||
| Memory retention requires a reachable owner of the continuation/context; it does not prove the | ||
| whole trace remains retained or memory grows without bound. Wrong parentage requires activation | ||
| of unrelated context or a leaked active scope. Separate the observed lifecycle defect from its | ||
| possible production effects and from test-only cleanup failures. | ||
|
|
||
| ## Explain it to a human | ||
|
|
||
| Lead with one sentence: what was captured, which cleanup edge was missing, and where. Cite the | ||
| timeline callsites. Then include a small Mermaid `flowchart LR`; use green for healthy edges, red | ||
| for the broken edge, and label thread handoffs. Use a Gantt only when timing itself caused the bug. | ||
|
|
||
| End with the code fix and the exact tests that passed. |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| --- | ||
| name: fix-continuation-leakage | ||
| description: > | ||
| Diagnose and fix scope or continuation lifecycle failures in dd-trace-java instrumentation | ||
| tests. Use when a test reports a continuation leak, double resolution, activation after resolve, | ||
| or an unclosed scope, or when strictTraceWrites(false) appears to hide one. Reads the automatic | ||
| diagnostic timeline, finds the broken lifecycle edge, fixes it, and explains it with a compact | ||
| Mermaid diagram. | ||
| user-invocable: true | ||
| context: fork | ||
| allowed-tools: | ||
| - Bash | ||
| - Read | ||
| - Edit | ||
| - Glob | ||
| - Grep | ||
| - AskUserQuestion | ||
| --- | ||
|
|
||
| # Fix continuation leakage | ||
|
|
||
| Read `.agents/skills/fix-continuation-leakage/SKILL.md` in full and follow it. That file is the | ||
| shared playbook for repository agents. |
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
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
22 changes: 22 additions & 0 deletions
22
...tion-testing/src/main/java/datadog/trace/agent/test/scopediag/ContinuableScopeAdvice.java
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package datadog.trace.agent.test.scopediag; | ||
|
|
||
| import net.bytebuddy.asm.Advice; | ||
|
|
||
| /** Test-only advice for {@code ContinuableScope}. */ | ||
| public final class ContinuableScopeAdvice { | ||
| private ContinuableScopeAdvice() {} | ||
|
|
||
| public static final class OnProperClose { | ||
| @Advice.OnMethodExit(suppress = Throwable.class) | ||
| public static void exit(@Advice.This Object scope) { | ||
| ScopeContinuationProbe.onScopeClose(scope); | ||
| } | ||
| } | ||
|
|
||
| public static final class Close { | ||
| @Advice.OnMethodEnter(suppress = Throwable.class) | ||
| public static void enter(@Advice.This Object scope) { | ||
| ScopeContinuationProbe.onScopeClosing(scope); | ||
| } | ||
| } | ||
| } |
15 changes: 15 additions & 0 deletions
15
...sting/src/main/java/datadog/trace/agent/test/scopediag/ContinuableScopeManagerAdvice.java
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package datadog.trace.agent.test.scopediag; | ||
|
|
||
| import net.bytebuddy.asm.Advice; | ||
|
|
||
| /** Test-only advice for scopes owned by the iteration cleaner. */ | ||
| public final class ContinuableScopeManagerAdvice { | ||
| private ContinuableScopeManagerAdvice() {} | ||
|
|
||
| public static final class ScheduleRootIterationCleanup { | ||
| @Advice.OnMethodExit(suppress = Throwable.class) | ||
| public static void exit(@Advice.Argument(1) Object scope) { | ||
| ScopeContinuationProbe.onDeferredScopeCleanup(scope); | ||
| } | ||
| } | ||
| } |
47 changes: 47 additions & 0 deletions
47
...entation-testing/src/main/java/datadog/trace/agent/test/scopediag/ContinuationAdvice.java
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| package datadog.trace.agent.test.scopediag; | ||
|
|
||
| import net.bytebuddy.asm.Advice; | ||
|
|
||
| /** Test-only advice for {@code ScopeContinuation}. */ | ||
| public final class ContinuationAdvice { | ||
| private ContinuationAdvice() {} | ||
|
|
||
| public static final class Register { | ||
| @Advice.OnMethodExit(suppress = Throwable.class) | ||
| public static void exit(@Advice.This Object self) { | ||
| ScopeContinuationProbe.onCapture(self); | ||
| } | ||
| } | ||
|
|
||
| /** Timestamps entry because {@code resume()} may resolve the continuation before returning. */ | ||
| public static final class Activate { | ||
| @Advice.OnMethodEnter | ||
| public static long enter() { | ||
| return System.nanoTime(); | ||
| } | ||
|
|
||
| @Advice.OnMethodExit(suppress = Throwable.class) | ||
| public static void exit( | ||
| @Advice.This Object self, @Advice.Enter long ddActivateNanos, @Advice.Return Object scope) { | ||
| ScopeContinuationProbe.onActivate(self, scope, ddActivateNanos); | ||
| } | ||
| } | ||
|
|
||
| /** Timestamps entry because resolution may write the trace before the method returns. */ | ||
| public static final class Cancel { | ||
| @Advice.OnMethodEnter(suppress = Throwable.class) | ||
| public static ScopeContinuationProbe.ResolveAttempt enter( | ||
| @Advice.This Object self, | ||
| @Advice.Origin("#m") String method, | ||
| @Advice.FieldValue("count") int count) { | ||
| return ScopeContinuationProbe.onResolveEnter(self, method, count); | ||
| } | ||
|
|
||
| @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) | ||
| public static void exit( | ||
| @Advice.Enter ScopeContinuationProbe.ResolveAttempt attempt, | ||
| @Advice.FieldValue("count") int countAfter) { | ||
| ScopeContinuationProbe.onResolveExit(attempt, countAfter); | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.