From df2d368786eb0b4d11bc2a59c38b246203316fa4 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Tue, 1 Sep 2026 16:09:21 -0400 Subject: [PATCH 1/2] Add @ForegroundSafe / @BackgroundOnly marker annotations Documentation-and-tooling markers declaring whether code is cheap enough for application (foreground) threads or must be confined to a background thread the tracer paces itself. No application to real code yet and no checker -- just the annotation types, following the Strategy/StrategyConsumer marker convention (APMLP-1543). --- .../trace/api/function/BackgroundOnly.java | 33 +++++++++++++++++ .../trace/api/function/ForegroundSafe.java | 36 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 internal-api/src/main/java/datadog/trace/api/function/BackgroundOnly.java create mode 100644 internal-api/src/main/java/datadog/trace/api/function/ForegroundSafe.java diff --git a/internal-api/src/main/java/datadog/trace/api/function/BackgroundOnly.java b/internal-api/src/main/java/datadog/trace/api/function/BackgroundOnly.java new file mode 100644 index 00000000000..242652be53d --- /dev/null +++ b/internal-api/src/main/java/datadog/trace/api/function/BackgroundOnly.java @@ -0,0 +1,33 @@ +package datadog.trace.api.function; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks code that must be confined to a background thread the tracer owns and paces itself -- e.g. + * serialization, stats aggregation, or eviction -- and must never be reached from an application + * thread (the foreground; see {@link ForegroundSafe}), where its cost would become customer-visible + * latency instead. + * + *

This is a documentation-and-tooling marker; it changes no behavior. It exists to telegraph the + * constraint to readers and to give a future checker (see {@code APMLP-1645}) something to verify + * -- that no {@code @BackgroundOnly} code is reachable from a foreground call site. The discipline + * it names is not yet enforced; hold to it by hand until the checker lands. + * + *

The two markers are not symmetric -- see {@link ForegroundSafe} for why it, not this + * one, is the strictly stronger guarantee. + * + *

On a type ({@link ElementType#TYPE}): every method of this type is background-only + * unless a method-level {@link ForegroundSafe} widens it. + * + *

On a method ({@link ElementType#METHOD}): this method specifically is background-only, + * regardless of what the enclosing type declares -- a method-level marker always wins over the + * type-level one. + */ +@Documented +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface BackgroundOnly {} diff --git a/internal-api/src/main/java/datadog/trace/api/function/ForegroundSafe.java b/internal-api/src/main/java/datadog/trace/api/function/ForegroundSafe.java new file mode 100644 index 00000000000..b1c0985dc3c --- /dev/null +++ b/internal-api/src/main/java/datadog/trace/api/function/ForegroundSafe.java @@ -0,0 +1,36 @@ +package datadog.trace.api.function; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks code cheap enough to call from an application thread (the foreground) -- the request or + * transaction thread the instrumented application itself is running, where any added cost is + * customer-visible latency, as opposed to a background thread the tracer owns and paces itself (see + * {@link BackgroundOnly}). + * + *

This is a documentation-and-tooling marker; it changes no behavior. It exists to telegraph the + * guarantee to readers and to give a future checker (see {@code APMLP-1645}) something to verify -- + * that no {@link BackgroundOnly} code is reachable from a foreground call site. The discipline it + * names is not yet enforced; hold to it by hand until the checker lands. + * + *

The two markers are not symmetric. {@code @ForegroundSafe} is the strictly stronger + * guarantee: code cheap enough for the foreground is automatically fine to call from a background + * thread too, so a {@code @ForegroundSafe} type or method may be called from either. {@link + * BackgroundOnly} code carries no such guarantee and must never be reached from a foreground call + * site. + * + *

On a type ({@link ElementType#TYPE}): every method of this type is foreground-safe + * unless a method-level {@link BackgroundOnly} narrows it. + * + *

On a method ({@link ElementType#METHOD}): this method specifically is foreground-safe, + * regardless of what the enclosing type declares -- a method-level marker always wins over the + * type-level one. + */ +@Documented +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface ForegroundSafe {} From 7792b1de81b9244b2024db892a6485c13a597f0a Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Fri, 11 Sep 2026 12:32:47 -0400 Subject: [PATCH 2/2] Annotate DDCache/SimpleUtf8Cache/GenerationalUtf8Cache with @ForegroundSafe / @BackgroundOnly DDCache is cheap (small, bounded probe count) -- safe to call from application threads. SimpleUtf8Cache and GenerationalUtf8Cache trade CPU for allocation savings that only pay off when run on a background thread, so they're confined to the background serializer thread. APMLP-1544 Co-Authored-By: Claude Sonnet 5 --- .../communication/serialization/GenerationalUtf8Cache.java | 6 ++++++ .../communication/serialization/SimpleUtf8Cache.java | 6 ++++++ .../src/main/java/datadog/trace/api/cache/DDCache.java | 7 +++++++ 3 files changed, 19 insertions(+) diff --git a/communication/src/main/java/datadog/communication/serialization/GenerationalUtf8Cache.java b/communication/src/main/java/datadog/communication/serialization/GenerationalUtf8Cache.java index 099ff56e0e0..9dd1c3b2818 100644 --- a/communication/src/main/java/datadog/communication/serialization/GenerationalUtf8Cache.java +++ b/communication/src/main/java/datadog/communication/serialization/GenerationalUtf8Cache.java @@ -1,5 +1,6 @@ package datadog.communication.serialization; +import datadog.trace.api.function.BackgroundOnly; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.nio.charset.StandardCharsets; import javax.annotation.concurrent.ThreadSafe; @@ -15,6 +16,11 @@ * String#getBytes(java.nio.charset.Charset)}. * *

The cache is thread safe. + * + *

{@link BackgroundOnly}: the eden/tenured promotion and recalibration bookkeeping costs more + * than the {@code getBytes} call it replaces, so the saving only shows up as reduced allocation/GC + * pressure on the thread that pays it -- confine it to the background serializer thread, not an + * application thread. */ /* * Cache works by using a 2-level promotion based scheme. diff --git a/communication/src/main/java/datadog/communication/serialization/SimpleUtf8Cache.java b/communication/src/main/java/datadog/communication/serialization/SimpleUtf8Cache.java index 8eb12d48465..4af30539053 100644 --- a/communication/src/main/java/datadog/communication/serialization/SimpleUtf8Cache.java +++ b/communication/src/main/java/datadog/communication/serialization/SimpleUtf8Cache.java @@ -1,5 +1,6 @@ package datadog.communication.serialization; +import datadog.trace.api.function.BackgroundOnly; import java.nio.charset.StandardCharsets; import javax.annotation.concurrent.ThreadSafe; @@ -13,6 +14,11 @@ * String#getBytes(java.nio.charset.Charset)}. * *

The cache is thread safe. + * + *

{@link BackgroundOnly}: the bookkeeping (hit counting, LFU eviction scan) costs more than the + * {@code getBytes} call it replaces, so the saving only shows up as reduced allocation/GC pressure + * on the thread that pays it -- confine it to the background serializer thread, not an application + * thread. */ /* * Thread safety is achieved through using CacheEntry objects where the key data diff --git a/internal-api/src/main/java/datadog/trace/api/cache/DDCache.java b/internal-api/src/main/java/datadog/trace/api/cache/DDCache.java index d1cd8024847..17888187a83 100644 --- a/internal-api/src/main/java/datadog/trace/api/cache/DDCache.java +++ b/internal-api/src/main/java/datadog/trace/api/cache/DDCache.java @@ -1,8 +1,15 @@ package datadog.trace.api.cache; +import datadog.trace.api.function.ForegroundSafe; import java.util.function.BiConsumer; import java.util.function.Function; +/** + * {@link ForegroundSafe}: every implementation looks up a key via a small, bounded number of probes + * (no growth, no eviction bookkeeping beyond overwriting a slot) -- cheap enough to call from an + * application thread. + */ +@ForegroundSafe public interface DDCache { /** * Look up or create and store a value in the cache.