Skip to content

Add @StaticLifetime and @Singleton marker annotations (AI perf review) - #12646

Open
dougqh wants to merge 10 commits into
masterfrom
dougqh/static-lifetime-singleton
Open

dougqh wants to merge 10 commits into
masterfrom
dougqh/static-lifetime-singleton

Conversation

@dougqh

@dougqh dougqh commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

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 @StaticLifetime needs 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 @NoEscape field-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 DDCache constructed per-request as an instance field on a per-request object, instead of once as a static field — the cache was allocated and thrown away on every request and never actually amortized anything.

Additional Notes

@StaticLifetime and @Singleton are 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.

  • Checked and confirmed no collision risk for @Singleton vs javax.inject.Singleton/jakarta.inject.Singleton — the only occurrences of those are in isolated Play-framework smoke-test fixtures, nowhere near internal-api/dd-trace-api/dd-trace-core/components.
  • A third sibling referenced by these tickets, @Borrowed, does not actually exist in the repo yet — filed as a separate follow-on: APMLP-1873.
  • ./gradlew :internal-api:spotlessApply :internal-api:compileJava run — compiles clean, formatted.

Contributor Checklist

  • Format the title according to the contribution guidelines
  • Assign the type: and (comp: or inst:) labels in addition to any other useful labels
  • Avoid using close, fix, or any linking keywords when referencing an issue
  • Update the CODEOWNERS file on source file addition, migration, or deletion (no change needed — new files land under existing internal-api ownership)
  • Update public documentation with any new configuration flags or behaviors (n/a — no config/behavior change)
  • Once approved, use merge queue to merge the PR

🤖 Generated with Claude Code

@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>
@dougqh dougqh added comp: core Tracer core type: refactoring tag: no release notes Changes to exclude from release notes tag: ai generated Largely based on code generated by an AI or LLM labels Sep 25, 2026
dougqh and others added 3 commits September 25, 2026 12:21
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>
@datadog-prod-us1-6

This comment has been minimized.

@dd-octo-sts

dd-octo-sts Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

🟢 Java Benchmark SLOs — All performance SLOs passed

Suite Status
Startup 🟢 pass

SLO thresholds are defined here based on automatically generated metrics. A warning is raised when results are within 5% of the threshold.

PR vs. master results
Scenario Candidate master Δ (95% CI of mean)
startup:insecure-bank:iast:Agent 13.99 s 13.94 s [-0.5%; +1.2%] (no difference)
startup:insecure-bank:tracing:Agent 13.02 s 13.03 s [-0.8%; +0.6%] (no difference)
startup:petclinic:appsec:Agent 17.08 s 17.02 s [-0.5%; +1.2%] (no difference)
startup:petclinic:iast:Agent 16.62 s 17.00 s [-6.5%; +2.0%] (no difference)
startup:petclinic:profiling:Agent 16.54 s 16.86 s [-3.3%; -0.6%] (maybe better)
startup:petclinic:sca:Agent 16.97 s 16.78 s [+0.2%; +2.1%] (maybe worse)
startup:petclinic:tracing:Agent 16.05 s 16.23 s [-1.9%; -0.3%] (maybe better)

Commit: 4ef7e11d · CI Pipeline · Benchmarking Platform UI


Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion.

dougqh and others added 2 commits September 25, 2026 14:01
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>
dougqh added a commit that referenced this pull request Sep 25, 2026
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>
@dougqh
dougqh marked this pull request as ready for review September 25, 2026 19:47
@dougqh
dougqh requested a review from a team as a code owner September 25, 2026 19:47
@dougqh
dougqh requested review from ValentinZakharov and removed request for a team September 25, 2026 19:47

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread internal-api/src/main/java/datadog/trace/api/function/StaticLifetime.java Outdated

@datadog-prod-us1-6 datadog-prod-us1-6 Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bits Code Review: FAIL

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.

Open Bits AI session

🤖 Bits Code Review · Commit 0848e09 · @DataDog review to ask questions

Comment thread internal-api/src/main/java/datadog/trace/api/function/StaticLifetime.java Outdated
Comment thread internal-api/src/main/java/datadog/trace/api/function/StaticLifetime.java Outdated
@dougqh dougqh changed the title Add @StaticLifetime and @Singleton marker annotations Add @StaticLifetime and @Singleton marker annotations (AI perf review) Sep 28, 2026
dougqh and others added 2 commits September 28, 2026 16:41
…-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>
@dougqh
dougqh requested a review from amarziali September 28, 2026 22:07
gh-worker-dd-mergequeue-cf854d Bot pushed a commit that referenced this pull request Sep 29, 2026
…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>
dougqh and others added 2 commits September 29, 2026 17:03
…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>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp: core Tracer core tag: ai generated Largely based on code generated by an AI or LLM tag: no release notes Changes to exclude from release notes type: refactoring

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant