Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ dependencies {
testImplementation project(':dd-java-agent:instrumentation:netty:netty-3.8')
testImplementation project(':dd-java-agent:instrumentation:netty:netty-4.0')
testImplementation project(':dd-java-agent:instrumentation:netty:netty-4.1')
testImplementation project(':dd-java-agent:instrumentation:netty:netty-concurrent-4.0')

latestDepTestImplementation sourceSets.test.output // include the protobuf generated classes
latestDepTestImplementation group: 'com.linecorp.armeria', name: 'armeria-grpc', version: '1.+'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ dependencies {
jetty9LatestDepTestImplementation "org.eclipse.jetty:jetty-servlet:9.+", {
exclude group: 'org.slf4j', module: 'slf4j-api'
}
testImplementation project(':dd-java-agent:instrumentation:netty:netty-concurrent-4.0')
testImplementation testFixtures(project(':dd-java-agent:instrumentation:servlet:javax-servlet:javax-servlet-3.0'))
testImplementation testFixtures(project(':dd-java-agent:instrumentation:jetty:jetty-server:jetty-server-9.0'))
testImplementation testFixtures(project(':dd-java-agent:instrumentation:servlet:jakarta-servlet-5.0'))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package datadog.trace.instrumentation.java.concurrent.runnable;

import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.declaresMethod;
import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.extendsClass;
import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.hasSuperType;
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.nameEndsWith;
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.namedOneOf;
Expand All @@ -16,6 +18,7 @@
import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.not;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;

import com.google.auto.service.AutoService;
Expand Down Expand Up @@ -84,7 +87,16 @@ public void methodAdvice(MethodTransformer transformer) {
.and(takesArgument(1, named(Callable.class.getName()))))),
getClass().getName() + "$Construct");
transformer.applyAdvice(isConstructor(), getClass().getName() + "$Construct");
transformer.applyAdvice(isMethod().and(named("run")), getClass().getName() + "$Run");
// Netty 4.1.44+ separates delayed scheduling in run() from execution in runTask().
transformer.applyAdvice(
isMethod()
.and(named("run"))
.and(
not(
isDeclaredBy(
nameEndsWith(".netty.util.concurrent.ScheduledFutureTask")
.and(hasSuperType(declaresMethod(named("runTask"))))))),
getClass().getName() + "$Run");
transformer.applyAdvice(
isMethod().and(namedOneOf("cancel", "set", "setException")),
getClass().getName() + "$Cancel");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,28 @@ addTestSuiteForDir('latestDep4Test', 'test')
addTestSuiteForDir('latestDepTest', 'test')
addForkedTestTask('latestDep4Test')
addForkedTestTask('latestDepTest')
addTestSuiteForDir('netty4144Test', 'test')
addTestSuiteForDir('legacyNettyTest', 'test')

['netty4144Test', 'legacyNettyTest'].each { name ->
tasks.named(name, Test) {
filter {
includeTestsMatching 'executor.NettyScheduledFutureTaskContextPropagationTest'
}
}
}

dependencies {
compileOnly group: 'io.netty', name: 'netty-common', version: '4.0.0.Final'

testImplementation group: 'io.netty', name: 'netty-common', version: '4.0.0.Final'
testImplementation project(':dd-java-agent:instrumentation:datadog:tracing:trace-annotation')
testImplementation group: 'io.netty', name: 'netty-all', version: '4.0.0.Final'
testImplementation group: 'io.grpc', name: 'grpc-netty-shaded', version: '1.58.0'

latestDep4TestImplementation group: 'io.netty', name: 'netty-common', version: '4.+'
latestDepTestImplementation group: 'io.netty', name: 'netty-common', version: '+'
latestDep4TestImplementation group: 'io.netty', name: 'netty-all', version: '4.+'
latestDepTestImplementation group: 'io.netty', name: 'netty-all', version: '+'
netty4144TestImplementation group: 'io.netty', name: 'netty-all', version: '4.1.44.Final'
legacyNettyTestImplementation group: 'io.netty', name: 'netty-all', version: '4.1.9.Final'
}

tasks.named("latestDepTest", Test) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package datadog.trace.instrumentation.netty40.concurrent;

import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.declaresMethod;
import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.extendsClass;
import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.hasSuperType;
import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.implementsInterface;
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.nameEndsWith;
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
import static datadog.trace.bootstrap.instrumentation.java.concurrent.AdviceUtils.endTaskScope;
import static datadog.trace.bootstrap.instrumentation.java.concurrent.AdviceUtils.startTaskScope;
import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.takesNoArguments;

import com.google.auto.service.AutoService;
import datadog.context.ContextScope;
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.agent.tooling.InstrumenterModule;
import datadog.trace.bootstrap.InstrumentationContext;
import datadog.trace.bootstrap.instrumentation.java.concurrent.State;
import java.util.Map;
import java.util.concurrent.RunnableFuture;
import java.util.concurrent.ScheduledFuture;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

@AutoService(InstrumenterModule.class)
public final class NettyPromiseTaskInstrumentation extends InstrumenterModule.ContextTracking
implements Instrumenter.ForTypeHierarchy, Instrumenter.HasMethodAdvice {

public NettyPromiseTaskInstrumentation() {
// Share both names with RunnableFutureInstrumentation because it captures continuations that
// this instrumentation activates for scheduled tasks; either name must enable both halves.
super("java_concurrent", "runnable-future");
Comment thread
amarziali marked this conversation as resolved.
}

@Override
public String hierarchyMarkerType() {
return RunnableFuture.class.getName();
}

@Override
public ElementMatcher<TypeDescription> hierarchyMatcher() {
return extendsClass(nameEndsWith(".netty.util.concurrent.PromiseTask"))
.and(implementsInterface(named(RunnableFuture.class.getName())))
.and(hasSuperType(declaresMethod(named("runTask"))));
}

@Override
public Map<String, String> contextStore() {
return singletonMap("java.util.concurrent.RunnableFuture", State.class.getName());
}

@Override
public String[] helperClassNames() {
return new String[] {packageName + ".ScheduledTaskScope"};
}

@Override
public void methodAdvice(MethodTransformer transformer) {
transformer.applyAdvice(
isMethod().and(named("runTask")).and(takesNoArguments()),
getClass().getName() + "$RunTask");
transformer.applyAdvice(
isMethod()
.and(named("run"))
.and(takesNoArguments())
.and(isDeclaredBy(nameEndsWith(".netty.util.concurrent.ScheduledFutureTask"))),
getClass().getName() + "$Run");
}

public static final class RunTask {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void activate(@Advice.This RunnableFuture<?> task) {
if (task instanceof ScheduledFuture) {
ContextScope scope =
startTaskScope(InstrumentationContext.get(RunnableFuture.class, State.class), task);
if (scope != null) {
ScheduledTaskScope state = ScheduledTaskScope.CURRENT.get();
if (state == null) {
state = new ScheduledTaskScope();
ScheduledTaskScope.CURRENT.set(state);
}
state.scope = scope;
state.depth++;
}
}
}
}

public static final class Run {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static ContextScope before(@Advice.Local("depth") int depth) {
ScheduledTaskScope state = ScheduledTaskScope.CURRENT.get();
if (state != null) {
depth = state.depth;
return state.scope;
}
return null;
}

@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
Comment thread
amarziali marked this conversation as resolved.
public static void after(
@Advice.Enter ContextScope previous, @Advice.Local("depth") int depth) {
ScheduledTaskScope state = ScheduledTaskScope.CURRENT.get();
if (state != null && state.depth > depth) {
ContextScope scope = state.scope;
state.scope = previous;
state.depth = depth;
endTaskScope(scope);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package datadog.trace.instrumentation.netty40.concurrent;

import datadog.context.ContextScope;

/** Transfers the runTask() scope to run() so completion listeners retain the task context. */
public final class ScheduledTaskScope {
public static final ThreadLocal<ScheduledTaskScope> CURRENT = new ThreadLocal<>();

public ContextScope scope;
// Track activations separately: resuming the same context can reuse the outer scope instance.
public int depth;
}
Loading
Loading