Conversation
@Staticlifetime marks a field that must live for the whole program rather than for its enclosing instance's lifetime (e.g. a cache allocated per-request instead of once). @singleton is its escape valve, declaring a class has exactly one process-wide instance, so an instance field on it can satisfy @Staticlifetime without being static. Wires a matching check into the perf-review checks doc, mirroring the existing @NoEscape field-storage check. APMLP-1846, APMLP-1847 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Splits narrative vs. machine-checkable rule, matching the convention established by @NoEscape and @Staticlifetime. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Spot-checks the annotation against the real defect it was modeled on: PR #12561 (commit e6b8756) made this exact DDCache field static after it lived per-instance, unshared, for ~4 years. Serves as the first real call site for @Staticlifetime. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
TAG_CACHE/VALUE_CACHE in TraceMapperV0_4 and KEY_CACHE/VALUE_CACHE in OtlpCommonProto are already static final -- marking them adds compliant-example coverage alongside RouteOnSuccessOrError's violation-turned-compliant case. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
🟢 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. |
Holding off on applying @Staticlifetime to real cache fields until @SuppressPerfContract (dougqh/perf-contract-suppress, PR #12647) lands -- want the exemption mechanism in place before widening the annotated surface. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same reason as the WebFlux route-cache revert: hold off until @SuppressPerfContract lands. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
These are cross-cutting, package-independent of any one marker, so they don't belong under datadog.trace.api.function alongside the functional interfaces (TriConsumer, TriFunction) and the markers themselves. datadog.perfcontract follows the existing precedent for top-level datadog.* packages (datadog.appsec, datadog.opentracing, datadog.communication, datadog.telemetry). The existing markers (NoEscape, Strategy, StrategyConsumer, BackgroundOnly, ForegroundSafe, and StaticLifetime/Singleton once #12646 lands) stay put for now -- moving those is a separate PR. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0848e09635
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
The @StaticLifetime annotation contract has two enforcement gaps: the checker trigger does not cover mutable static fields (allowing replaceable static caches to pass), and the singleton-scoped instance-field form does not require final, meaning a reassignable field can incorrectly satisfy the annotation.
🤖 Bits Code Review · Commit 0848e09 · @DataDog review to ask questions
…-final Per Bits AI review: the contract accepted "static" fields and singleton-scoped instance fields without requiring final, so a mutable field of either shape could pass while still being reassignable to a fresh, cold instance at runtime -- defeating the amortization guarantee the annotation exists to protect, just via a write instead of via scope. Add an explicit mutability trigger and require final in the Accepted description for both shapes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ntract Per review: the intro implied a ClassValue-backed holder need not be static itself, contradicting the Accepted list below it, which requires "static final ClassValue<T>". Clarify that it's the per-Class values ClassValue computes that get a free process-wide lifetime, not the holder field referencing the ClassValue instance -- that still has to be static final. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…review) (#12647) Add @PerfContract meta-annotation and @SuppressPerfContract Generic exemption mechanism for perf-contract marker annotations (NoEscape, Strategy, StaticLifetime, ...), replacing per-marker exemption channels (bespoke nested annotations, comment-only conventions) with one shared mechanism. @SuppressPerfContract can also be used as a meta-annotation to define named, canned exceptions (e.g. a future @Borrowed for @NoEscape). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Teach perf-review's @NoEscape check about @SuppressPerfContract Check #8 (@NoEscape field-storage violation) only recognized a retention-justifying comment as compliant. It now also accepts @SuppressPerfContract(value = NoEscape.class, reason = "...") and a canned-exception annotation meta-annotated with it (e.g. a future @Borrowed), matching the mechanism's own Checker contract. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Move @PerfContract and @SuppressPerfContract to datadog.perfcontract These are cross-cutting, package-independent of any one marker, so they don't belong under datadog.trace.api.function alongside the functional interfaces (TriConsumer, TriFunction) and the markers themselves. datadog.perfcontract follows the existing precedent for top-level datadog.* packages (datadog.appsec, datadog.opentracing, datadog.communication, datadog.telemetry). The existing markers (NoEscape, Strategy, StrategyConsumer, BackgroundOnly, ForegroundSafe, and StaticLifetime/Singleton once #12646 lands) stay put for now -- moving those is a separate PR. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Add CODEOWNERS entry for datadog.perfcontract The new package (PerfContract, SuppressPerfContract) had no owner, blocking the PR merge. Same owner as the sibling datadog.trace.api.function package these moved out of. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Fix stale marker refs, simplify javadocs, mark existing markers @PerfContract Removes references to Singleton/StaticLifetime, which don't exist in this PR's tree (a cross-reference left over from generating two related PRs in one session). Adopts sarahchen6's simplified class javadocs for PerfContract and SuppressPerfContract. Annotates NoEscape, Strategy, and StrategyConsumer with @PerfContract so SuppressPerfContract's own worked example (suppressing a NoEscape finding) is valid, per the Codex/Bits review finding that NoEscape carried no @PerfContract. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Add @SuppressPerfContract as an accepted NoEscape exception path checks.md already documented @SuppressPerfContract(NoEscape.class, ...) and canned-exception annotations as compliant, but NoEscape's own Checker contract only listed the plain comment convention, so an AI reviewer reading NoEscape.java alone would flag a compliant suppression. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Co-authored-by: devflow.devflow-routing-intake <devflow.devflow-routing-intake@kubernetes.us1.ddbuild.io>
…ime-singleton # Conflicts: # .agents/skills/perf-review/references/checks.md
…tate with @PerfContract Relocates both annotations out of the legacy datadog.trace.api.function package into the new datadog.perfcontract package (introduced by #12647), alongside PerfContract/SuppressPerfContract, and adds @PerfContract so tooling can discover them as perf-contract markers the same way it already discovers Strategy/StrategyConsumer/NoEscape. No other code references either annotation, so no import-site updates are needed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
What Does This Do
Adds two marker annotations to
datadog.trace.api.function...@StaticLifetime(internal-api/.../function/StaticLifetime.java): marks a field that must live for the whole program, not merely for its enclosing instance's lifetime — targets the shape where a cache is constructed per-request as an instance field and never amortizes anything.@Singleton(internal-api/.../function/Singleton.java): class-level marker declaring exactly one process-wide instance — the escape valve@StaticLifetimeneeds to accept an instance field on a genuinely singleton-scoped class.Also wires a matching AI-review check into
.agents/skills/perf-review/references/checks.md(item 9), mirroring the existing@NoEscapefield-storage check, and cross-references the pre-existing "static/once" deterministic-lint candidate line.No call sites are annotated in this PR — annotations only, per plan. Selective application to real classes (e.g. the already-singleton
DECORATE/GETTER-style decorators) may follow separately.Motivation
Sparked by a real production defect (found by Andrea's automation): a
DDCacheconstructed per-request as an instance field on a per-request object, instead of once as astaticfield — the cache was allocated and thrown away on every request and never actually amortized anything.Additional Notes
@StaticLifetimeand@Singletonare being delivered together because they're tightly coupled —@StaticLifetime's checker can't express its accepted forms without@Singleton's escape-valve semantics existing first.@Singletonvsjavax.inject.Singleton/jakarta.inject.Singleton— the only occurrences of those are in isolated Play-framework smoke-test fixtures, nowhere nearinternal-api/dd-trace-api/dd-trace-core/components.@Borrowed, does not actually exist in the repo yet — filed as a separate follow-on: APMLP-1873../gradlew :internal-api:spotlessApply :internal-api:compileJavarun — compiles clean, formatted.Contributor Checklist
type:and (comp:orinst:) labels in addition to any other useful labelsclose,fix, or any linking keywords when referencing an issueinternal-apiownership)🤖 Generated with Claude Code