diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java index 249453c58fb..8a2de041314 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java @@ -337,6 +337,8 @@ public static void start( StaticEventLogger.end("crashtracking"); } + AgentTracer.maybeInstallLegacyContextManager(); + startDatadogAgent(initTelemetry, inst); final EnumSet libraries = detectLibraries(log); diff --git a/dd-java-agent/agent-installer/src/main/java/datadog/trace/agent/tooling/nativeimage/TracerActivation.java b/dd-java-agent/agent-installer/src/main/java/datadog/trace/agent/tooling/nativeimage/TracerActivation.java index e6ff1031faa..682ceb9115e 100644 --- a/dd-java-agent/agent-installer/src/main/java/datadog/trace/agent/tooling/nativeimage/TracerActivation.java +++ b/dd-java-agent/agent-installer/src/main/java/datadog/trace/agent/tooling/nativeimage/TracerActivation.java @@ -8,6 +8,7 @@ import datadog.trace.agent.tooling.MeterInstaller; import datadog.trace.agent.tooling.ProfilerInstaller; import datadog.trace.agent.tooling.TracerInstaller; +import datadog.trace.bootstrap.instrumentation.api.AgentTracer; import datadog.trace.bootstrap.instrumentation.api.ProfilingContextIntegration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,6 +19,7 @@ public final class TracerActivation { public static void activate() { try { + AgentTracer.maybeInstallLegacyContextManager(); // Initialize meter MeterInstaller.installMeter(); // Initialize tracer diff --git a/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/InstrumentationSpecification.groovy b/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/InstrumentationSpecification.groovy index 8f1a6cd6df4..8970af5be26 100644 --- a/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/InstrumentationSpecification.groovy +++ b/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/InstrumentationSpecification.groovy @@ -10,7 +10,6 @@ import static datadog.trace.api.config.TraceInstrumentationConfig.CODE_ORIGIN_FO import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.closePrevious import static datadog.trace.util.AgentThreadFactory.AgentThread.TASK_SCHEDULER - import ch.qos.logback.classic.Level import ch.qos.logback.classic.util.ContextInitializer import com.datadog.debugger.agent.ClassesToRetransformFinder @@ -351,18 +350,20 @@ abstract class InstrumentationSpecification extends DDSpecification implements A void setupSpec() { InstrumentationErrors.resetErrors() - AgentMeter.registerIfAbsent( - STATS_D_CLIENT, - new MonitoringImpl(STATS_D_CLIENT, 10, TimeUnit.SECONDS), - DDSketchHistograms.FACTORY - ) - // If this fails, it's likely the result of another test loading Config before it can be // injected into the bootstrap classpath. If one test extends AgentTestRunner in a module, all tests must extend assert Config.getClassLoader() == null: "Config must load on the bootstrap classpath." configurePreAgent() + AgentTracer.maybeInstallLegacyContextManager() + + AgentMeter.registerIfAbsent( + STATS_D_CLIENT, + new MonitoringImpl(STATS_D_CLIENT, 10, TimeUnit.SECONDS), + DDSketchHistograms.FACTORY + ) + TEST_DATA_STREAMS_WRITER = new RecordingDatastreamsPayloadWriter() DDAgentFeaturesDiscovery features = new MockFeaturesDiscovery(true) diff --git a/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/AbstractInstrumentationTest.java b/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/AbstractInstrumentationTest.java index 5f724853972..af77734f415 100644 --- a/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/AbstractInstrumentationTest.java +++ b/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/AbstractInstrumentationTest.java @@ -23,6 +23,7 @@ import datadog.trace.core.TraceCollector; import datadog.trace.junit.utils.config.WithConfig; import datadog.trace.junit.utils.context.AllowContextTestingExtension; +import datadog.trace.junit.utils.context.LegacyContextTestingExtension; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; import java.util.List; @@ -54,7 +55,11 @@ * */ @WithConfig(key = "detailed.instrumentation.errors", value = "true") -@ExtendWith({TestClassShadowingExtension.class, AllowContextTestingExtension.class}) +@ExtendWith({ + TestClassShadowingExtension.class, + AllowContextTestingExtension.class, + LegacyContextTestingExtension.class +}) public abstract class AbstractInstrumentationTest { static final Instrumentation INSTRUMENTATION = ByteBuddyAgent.getInstrumentation(); diff --git a/dd-trace-core/src/jmh/java/datadog/context/ContextManagerBenchmark.java b/dd-trace-core/src/jmh/java/datadog/context/ContextManagerBenchmark.java index 039f0bad160..0128535a9d0 100644 --- a/dd-trace-core/src/jmh/java/datadog/context/ContextManagerBenchmark.java +++ b/dd-trace-core/src/jmh/java/datadog/context/ContextManagerBenchmark.java @@ -107,9 +107,34 @@ public void setup() { } static ContextManager createManager(String type) { - return "Continuable".equals(type) - ? new ContinuableScopeManager(0, false) - : ThreadLocalContextManager.INSTANCE; + if ("Continuable".equals(type)) { + ContinuableScopeManager csm = new ContinuableScopeManager(0, false); + return new ContextManager() { + @Override + public Context current() { + return csm.currentContext(); + } + + @Override + public ContextScope attach(Context ctx) { + return csm.attach(ctx); + } + + @Override + public Context swap(Context ctx) { + return csm.swap(ctx); + } + + @Override + public ContextContinuation capture(Context ctx) { + return csm.capture(ctx); + } + + @Override + public void addListener(ContextListener l) {} + }; + } + return ThreadLocalContextManager.INSTANCE; } static Context[] createContexts() { diff --git a/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java b/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java index 7ad4497beca..d8b3fef89ce 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java @@ -22,6 +22,9 @@ import datadog.communication.ddagent.DDAgentFeaturesDiscovery; import datadog.communication.ddagent.ExternalAgentLauncher; import datadog.communication.ddagent.SharedCommunicationObjects; +import datadog.context.Context; +import datadog.context.ContextContinuation; +import datadog.context.ContextScope; import datadog.context.propagation.Propagators; import datadog.environment.ThreadSupport; import datadog.logging.RatelimitedLogger; @@ -2465,4 +2468,24 @@ static TagMap withTracerTags( } return result.freeze(); } + + @Override + public Context currentContext() { + return scopeManager.currentContext(); + } + + @Override + public ContextScope attach(Context context) { + return scopeManager.attach(context); + } + + @Override + public Context swap(Context context) { + return scopeManager.swap(context); + } + + @Override + public ContextContinuation capture(Context context) { + return scopeManager.capture(context); + } } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/scopemanager/ContinuableScopeManager.java b/dd-trace-core/src/main/java/datadog/trace/core/scopemanager/ContinuableScopeManager.java index 04fbd637bb3..4cc16cd8a95 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/scopemanager/ContinuableScopeManager.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/scopemanager/ContinuableScopeManager.java @@ -14,8 +14,6 @@ import datadog.context.Context; import datadog.context.ContextContinuation; -import datadog.context.ContextListener; -import datadog.context.ContextManager; import datadog.context.ContextScope; import datadog.logging.RatelimitedLogger; import datadog.trace.api.Config; @@ -47,7 +45,7 @@ * from being reported even if all related spans are finished. It also delegates to other * ScopeInterceptors to provide additional functionality. */ -public final class ContinuableScopeManager implements ContextManager { +public final class ContinuableScopeManager { static final Logger log = LoggerFactory.getLogger(ContinuableScopeManager.class); static final RatelimitedLogger ratelimitedLog = new RatelimitedLogger(log, 1, MINUTES); @@ -95,8 +93,6 @@ public ContinuableScopeManager( this.profilingContextIntegration = profilingContextIntegration; this.profilingEnabled = !(profilingContextIntegration instanceof ProfilingContextIntegration.NoOp); - - ContextManager.register(this); } public AgentScope activateSpan(final AgentSpan span) { @@ -370,18 +366,15 @@ ScopeStack scopeStack() { return this.tlsScopeStack.get(); } - @Override - public Context current() { + public Context currentContext() { final ContinuableScope active = scopeStack().active(); return active == null ? Context.root() : active.context; } - @Override public ContextScope attach(@NonNull Context context) { return activate(context); } - @Override public Context swap(@NonNull Context context) { ScopeStack oldStack = tlsScopeStack.get(); ContinuableScope oldScope = oldStack.top; @@ -412,7 +405,6 @@ public Context swap(@NonNull Context context) { return new ScopeContext(oldStack); } - @Override public ContextContinuation capture(@NonNull Context context) { // respect async propagation flag for Context.current().capture() ContinuableScope activeScope = scopeStack().active(); @@ -431,12 +423,6 @@ public ContextContinuation capture(@NonNull Context context) { return new ScopeContinuation(this, context, CONTEXT, traceCollector).register(); } - @Override - public void addListener(@NonNull ContextListener unused) { - // this new API is not expected to be used in legacy mode... - log.warn("Unexpected call to ContextManager.addListener(...)"); - } - static final class ScopeStackThreadLocal extends ThreadLocal { private final ProfilingContextIntegration profilingContextIntegration; diff --git a/dd-trace-core/src/test/java/datadog/trace/core/scopemanager/ScopeManagerTest.java b/dd-trace-core/src/test/java/datadog/trace/core/scopemanager/ScopeManagerForkedTest.java similarity index 94% rename from dd-trace-core/src/test/java/datadog/trace/core/scopemanager/ScopeManagerTest.java rename to dd-trace-core/src/test/java/datadog/trace/core/scopemanager/ScopeManagerForkedTest.java index cf1748cece0..32701171b4a 100644 --- a/dd-trace-core/src/test/java/datadog/trace/core/scopemanager/ScopeManagerTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/core/scopemanager/ScopeManagerForkedTest.java @@ -2,8 +2,8 @@ import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.noopContinuation; import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.noopSpan; -import static datadog.trace.core.scopemanager.ScopeManagerTest.EVENT.ACTIVATE; -import static datadog.trace.core.scopemanager.ScopeManagerTest.EVENT.CLOSE; +import static datadog.trace.core.scopemanager.ScopeManagerForkedTest.EVENT.ACTIVATE; +import static datadog.trace.core.scopemanager.ScopeManagerForkedTest.EVENT.CLOSE; import static datadog.trace.test.util.GCUtils.awaitGC; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -36,6 +36,7 @@ import datadog.trace.api.scopemanager.ScopeListener; import datadog.trace.bootstrap.instrumentation.api.AgentScope; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.AgentTracer; import datadog.trace.bootstrap.instrumentation.api.ProfilingContextIntegration; import datadog.trace.common.writer.ListWriter; import datadog.trace.context.TraceScope; @@ -56,13 +57,14 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.tabletest.junit.TableTest; -class ScopeManagerTest extends DDCoreJavaSpecification { +class ScopeManagerForkedTest extends DDCoreJavaSpecification { enum EVENT { ACTIVATE, @@ -83,6 +85,12 @@ protected boolean useStrictTraceWrites() { ProfilingContextIntegration profilingContext; Stateful state; + @BeforeAll + static void installLegacyContextManager() { + // this test requires use of legacy context manager + AgentTracer.installLegacyContextManager(); + } + @BeforeEach void setup() { state = mock(Stateful.class); @@ -91,6 +99,7 @@ void setup() { when(profilingContext.name()).thenReturn("mock"); writer = new ListWriter(); tracer = tracerBuilder().writer(writer).profilingContextIntegration(profilingContext).build(); + AgentTracer.forceRegister(tracer); scopeManager = ScopeManagerTestBridge.getScopeManager(tracer); eventCountingListener = new EventCountingListener(); scopeManager.addScopeListener(eventCountingListener); @@ -889,28 +898,28 @@ void activatingASpanMergesItWithExistingContext() { ContextScope contextScope = scopeManager.attach(context); assertSame(contextScope, scopeManager.active()); - assertEquals(context, scopeManager.current()); + assertEquals(context, scopeManager.currentContext()); assertNull(scopeManager.activeSpan()); - assertEquals("test-value", scopeManager.current().get(testKey)); + assertEquals("test-value", scopeManager.currentContext().get(testKey)); AgentScope scope = tracer.activateSpan(span); assertSame(scope, scopeManager.active()); - assertNotEquals(context, scopeManager.current()); + assertNotEquals(context, scopeManager.currentContext()); assertSame(span, scopeManager.activeSpan()); - assertEquals("test-value", scopeManager.current().get(testKey)); + assertEquals("test-value", scopeManager.currentContext().get(testKey)); scope.close(); assertSame(contextScope, scopeManager.active()); - assertEquals(context, scopeManager.current()); + assertEquals(context, scopeManager.currentContext()); assertNull(scopeManager.activeSpan()); - assertEquals("test-value", scopeManager.current().get(testKey)); + assertEquals("test-value", scopeManager.currentContext().get(testKey)); contextScope.close(); assertNull(scopeManager.active()); - assertEquals(Context.root(), scopeManager.current()); + assertEquals(Context.root(), scopeManager.currentContext()); assertNull(scopeManager.activeSpan()); } @@ -922,28 +931,28 @@ void capturingAndContinuingASpanMergesItWithExistingContext() { ContextScope contextScope = scopeManager.attach(context); assertSame(contextScope, scopeManager.active()); - assertEquals(context, scopeManager.current()); + assertEquals(context, scopeManager.currentContext()); assertNull(scopeManager.activeSpan()); - assertEquals("test-value", scopeManager.current().get(testKey)); + assertEquals("test-value", scopeManager.currentContext().get(testKey)); AgentScope scope = tracer.captureSpan(span).activate(); assertSame(scope, scopeManager.active()); - assertNotEquals(context, scopeManager.current()); + assertNotEquals(context, scopeManager.currentContext()); assertSame(span, scopeManager.activeSpan()); - assertEquals("test-value", scopeManager.current().get(testKey)); + assertEquals("test-value", scopeManager.currentContext().get(testKey)); scope.close(); assertSame(contextScope, scopeManager.active()); - assertEquals(context, scopeManager.current()); + assertEquals(context, scopeManager.currentContext()); assertNull(scopeManager.activeSpan()); - assertEquals("test-value", scopeManager.current().get(testKey)); + assertEquals("test-value", scopeManager.currentContext().get(testKey)); contextScope.close(); assertNull(scopeManager.active()); - assertEquals(Context.root(), scopeManager.current()); + assertEquals(Context.root(), scopeManager.currentContext()); assertNull(scopeManager.activeSpan()); } @@ -955,30 +964,30 @@ void capturingAndContinuingTheActiveSpanMergesItWithExistingContext() { ContextScope contextScope = scopeManager.attach(context); assertSame(contextScope, scopeManager.active()); - assertEquals(context, scopeManager.current()); + assertEquals(context, scopeManager.currentContext()); assertNull(scopeManager.activeSpan()); - assertEquals("test-value", scopeManager.current().get(testKey)); + assertEquals("test-value", scopeManager.currentContext().get(testKey)); AgentScope innerScope = tracer.activateSpan(span); AgentScope scope = tracer.captureActiveSpan().activate(); innerScope.close(); assertSame(scope, scopeManager.active()); - assertNotEquals(context, scopeManager.current()); + assertNotEquals(context, scopeManager.currentContext()); assertSame(span, scopeManager.activeSpan()); - assertEquals("test-value", scopeManager.current().get(testKey)); + assertEquals("test-value", scopeManager.currentContext().get(testKey)); scope.close(); assertSame(contextScope, scopeManager.active()); - assertEquals(context, scopeManager.current()); + assertEquals(context, scopeManager.currentContext()); assertNull(scopeManager.activeSpan()); - assertEquals("test-value", scopeManager.current().get(testKey)); + assertEquals("test-value", scopeManager.currentContext().get(testKey)); contextScope.close(); assertNull(scopeManager.active()); - assertEquals(Context.root(), scopeManager.current()); + assertEquals(Context.root(), scopeManager.currentContext()); assertNull(scopeManager.activeSpan()); } @@ -1035,28 +1044,28 @@ void contextsCanBeSwappedOutAndBack() { Context swappedOut = scopeManager.swap(Context.root()); assertNull(scopeManager.active()); - assertEquals(Context.root(), scopeManager.current()); + assertEquals(Context.root(), scopeManager.currentContext()); scopeManager.swap(context1); assertNotNull(scopeManager.active()); - assertEquals(context1, scopeManager.current()); + assertEquals(context1, scopeManager.currentContext()); scopeManager.swap(swappedOut); assertNull(scopeManager.active()); - assertEquals(Context.root(), scopeManager.current()); + assertEquals(Context.root(), scopeManager.currentContext()); ContextScope contextScope = scopeManager.attach(context1); assertSame(contextScope, scopeManager.active()); - assertEquals(context1, scopeManager.current()); + assertEquals(context1, scopeManager.currentContext()); swappedOut = scopeManager.swap(context2); assertNotNull(scopeManager.active()); assertNotSame(contextScope, scopeManager.active()); - assertEquals(context2, scopeManager.current()); + assertEquals(context2, scopeManager.currentContext()); assertEquals("first-value", swappedOut.get(testKey)); Context context3 = swappedOut.with(testKey, "third-value"); @@ -1064,17 +1073,17 @@ void contextsCanBeSwappedOutAndBack() { assertNotNull(scopeManager.active()); assertNotSame(contextScope, scopeManager.active()); - assertEquals(context3, scopeManager.current()); + assertEquals(context3, scopeManager.currentContext()); scopeManager.swap(swappedOut); assertSame(contextScope, scopeManager.active()); - assertEquals(context1, scopeManager.current()); + assertEquals(context1, scopeManager.currentContext()); contextScope.close(); assertNull(scopeManager.active()); - assertEquals(Context.root(), scopeManager.current()); + assertEquals(Context.root(), scopeManager.currentContext()); } @Test diff --git a/internal-api/build.gradle.kts b/internal-api/build.gradle.kts index 6e99abe89d2..65be1563eda 100644 --- a/internal-api/build.gradle.kts +++ b/internal-api/build.gradle.kts @@ -91,6 +91,7 @@ extra["excludedClassesCoverage"] = listOf( "datadog.trace.bootstrap.instrumentation.api.AgentSpan", "datadog.trace.bootstrap.instrumentation.api.AgentSpanContext", "datadog.trace.bootstrap.instrumentation.api.AgentTracer", + "datadog.trace.bootstrap.instrumentation.api.AgentTracer.LegacyContextManager", "datadog.trace.bootstrap.instrumentation.api.AgentTracer.NoopAgentHistogram", "datadog.trace.bootstrap.instrumentation.api.AgentTracer.NoopAgentTraceCollector", "datadog.trace.bootstrap.instrumentation.api.AgentTracer.NoopTraceConfig", diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentTracer.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentTracer.java index 8d882b882bc..a945dddbda6 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentTracer.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentTracer.java @@ -1,5 +1,10 @@ package datadog.trace.bootstrap.instrumentation.api; +import datadog.context.Context; +import datadog.context.ContextContinuation; +import datadog.context.ContextListener; +import datadog.context.ContextManager; +import datadog.context.ContextScope; import datadog.trace.api.ConfigDefaults; import datadog.trace.api.DDTraceId; import datadog.trace.api.EndpointCheckpointer; @@ -402,6 +407,20 @@ default AgentSpan blackholeSpan() { void updatePreferredServiceName(String serviceName, CharSequence source); void addShutdownListener(Runnable listener); + + // these methods are only used for legacy context manager migration + + @Deprecated + Context currentContext(); + + @Deprecated + ContextScope attach(Context context); + + @Deprecated + Context swap(Context context); + + @Deprecated + ContextContinuation capture(Context context); } public interface SpanBuilder { @@ -653,6 +672,26 @@ public TraceConfig captureTraceConfig() { public void updatePreferredServiceName(String serviceName, CharSequence preferredServiceName) { // no ops } + + @Override + public Context currentContext() { + return Context.root(); + } + + @Override + public ContextScope attach(Context context) { + return noopScope(); + } + + @Override + public Context swap(Context context) { + return Context.root(); + } + + @Override + public ContextContinuation capture(Context context) { + return noopContinuation(); + } } public static class NoopAgentTraceCollector implements AgentTraceCollector { @@ -739,4 +778,52 @@ public List getDataStreamsTransactionExtractors return null; } } + + /** + * Decides whether to install the legacy approach to managing contexts, which requires a tracer. + * + *

Must be called ahead of instrumentation, before any use of the Context API. + */ + public static void maybeInstallLegacyContextManager() { + installLegacyContextManager(); // install everywhere to begin with + } + + /** + * Always install the legacy approach to managing contexts, which requires a tracer. + * + *

Must be called ahead of instrumentation, before any use of the Context API. + */ + public static void installLegacyContextManager() { + ContextManager.register(LegacyContextManager.INSTANCE); + } + + /** Shim mapping new Context API to legacy scope manager. */ + static final class LegacyContextManager implements ContextManager { + static final LegacyContextManager INSTANCE = new LegacyContextManager(); + + @Override + public Context current() { + return AgentTracer.get().currentContext(); + } + + @Override + public ContextScope attach(@Nonnull Context context) { + return AgentTracer.get().attach(context); + } + + @Override + public Context swap(@Nonnull Context context) { + return AgentTracer.get().swap(context); + } + + @Override + public ContextContinuation capture(@Nonnull Context context) { + return AgentTracer.get().capture(context); + } + + @Override + public void addListener(@Nonnull ContextListener listener) { + // this method is never used in legacy mode... + } + } } diff --git a/utils/junit-utils/src/main/java/datadog/trace/junit/utils/context/LegacyContextTestingExtension.java b/utils/junit-utils/src/main/java/datadog/trace/junit/utils/context/LegacyContextTestingExtension.java new file mode 100644 index 00000000000..de0c4ecea5b --- /dev/null +++ b/utils/junit-utils/src/main/java/datadog/trace/junit/utils/context/LegacyContextTestingExtension.java @@ -0,0 +1,20 @@ +package datadog.trace.junit.utils.context; + +import de.thetaphi.forbiddenapis.SuppressForbidden; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +/** JUnit 5 extension that decides whether to register the legacy approach to managing contexts. */ +@SuppressForbidden +public class LegacyContextTestingExtension implements BeforeAllCallback { + @Override + public void beforeAll(ExtensionContext context) { + try { + Class.forName("datadog.trace.bootstrap.instrumentation.api.AgentTracer") + .getMethod("maybeInstallLegacyContextManager") + .invoke(null); + } catch (Throwable ignore) { + // don't block testing if the legacy approach to managing contexts isn't available + } + } +}