Skip to content

Commit f689e83

Browse files
committed
Preserve getQualifier from spring scheduling runnables
1 parent e379305 commit f689e83

17 files changed

+583
-18
lines changed

dd-java-agent/instrumentation/spring-scheduling-3.1/build.gradle

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
ext {
2+
latestDepForkedTestMinJavaVersionForTests = JavaVersion.VERSION_17
3+
latestDepTestMinJavaVersionForTests = JavaVersion.VERSION_17
24
spring6TestMinJavaVersionForTests = JavaVersion.VERSION_17
3-
spring6LatestDepTestMinJavaVersionForTests = JavaVersion.VERSION_17
45
}
6+
57
muzzle {
68
pass {
79
group = 'org.springframework'
@@ -20,22 +22,30 @@ muzzle {
2022

2123
apply from: "$rootDir/gradle/java.gradle"
2224

23-
addTestSuiteForDir('latestDepTest', 'test')
25+
addTestSuite('latestDepTest')
26+
addTestSuiteExtendingForDir('latestDepForkedTest','latestDepTest', 'latestDepTest')
2427
addTestSuiteForDir('spring6Test', 'test')
25-
addTestSuiteExtendingForDir('spring6LatestDepTest', 'latestDepTest', 'test')
28+
addTestSuiteExtendingForDir('spring5LatestDepTest', 'latestDepTest', 'test')
2629

27-
[compileSpring6TestJava, compileSpring6LatestDepTestJava].each {
30+
[compileSpring6TestJava, compileLatestDepTestJava].each {
2831
setJavaVersion(it, 17)
2932
sourceCompatibility = JavaVersion.VERSION_1_8
3033
targetCompatibility = JavaVersion.VERSION_1_8
3134
}
3235

3336

34-
[compileSpring6TestGroovy, compileSpring6LatestDepTestGroovy, spring6Test, spring6LatestDepTest].each {
37+
[
38+
compileSpring6TestGroovy,
39+
compileLatestDepTestGroovy,
40+
compileLatestDepForkedTestGroovy,
41+
spring6Test,
42+
latestDepTest,
43+
latestDepForkedTest
44+
].each {
3545
it.javaLauncher = getJavaLauncherFor(17)
3646
}
3747

38-
[spring6Test, spring6LatestDepTest].each {
48+
[spring6Test, latestDepTest, latestDepForkedTest].each {
3949
it.jvmArgs '--add-opens', 'java.base/java.util=ALL-UNNAMED'
4050
}
4151

@@ -55,19 +65,17 @@ dependencies {
5565
testImplementation group: 'org.springframework.boot', name: 'spring-boot-starter-actuator', version: '2.4.0'
5666

5767

58-
latestDepTestImplementation group: 'org.springframework', name: 'spring-context', version: '5.+'
68+
spring5LatestDepTestImplementation group: 'org.springframework', name: 'spring-context', version: '5.+'
5969

60-
latestDepTestImplementation group: 'net.javacrumbs.shedlock', name: 'shedlock-spring', version: '4.+'
61-
latestDepTestImplementation group: 'net.javacrumbs.shedlock', name: 'shedlock-provider-jdbc-template', version: '4.+'
62-
latestDepTestImplementation group: 'com.h2database', name: 'h2', version: '2.2.+' // 2.3+ requires Java 11
63-
latestDepTestImplementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa', version: '2.+'
70+
spring5LatestDepTestImplementation group: 'net.javacrumbs.shedlock', name: 'shedlock-spring', version: '4.+'
71+
spring5LatestDepTestImplementation group: 'net.javacrumbs.shedlock', name: 'shedlock-provider-jdbc-template', version: '4.+'
72+
spring5LatestDepTestImplementation group: 'com.h2database', name: 'h2', version: '2.2.+' // 2.3+ requires Java 11
73+
spring5LatestDepTestImplementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa', version: '2.+'
6474

6575
spring6TestImplementation group: 'org.springframework', name: 'spring-context', version: '6.0.0.RELEASE'
6676
spring6TestImplementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa', version: '3.0.0'
6777

68-
spring6LatestDepTestImplementation group: 'com.h2database', name: 'h2', version: '+'
69-
spring6LatestDepTestImplementation group: 'org.springframework', name: 'spring-context', version: '6.+'
70-
spring6LatestDepTestImplementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa', version: '3.+'
71-
72-
78+
latestDepTestImplementation group: 'com.h2database', name: 'h2', version: '+'
79+
latestDepTestImplementation group: 'org.springframework', name: 'spring-context', version: '6.+'
80+
latestDepTestImplementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa', version: '3.+'
7381
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import datadog.trace.agent.test.AgentTestRunner
2+
import datadog.trace.test.util.Flaky
3+
import org.springframework.context.annotation.AnnotationConfigApplicationContext
4+
import org.springframework.jdbc.core.JdbcTemplate
5+
6+
import javax.sql.DataSource
7+
import java.util.concurrent.TimeUnit
8+
9+
class ShedlockTest extends AgentTestRunner {
10+
11+
@Flaky("task.invocationCount() == 0")
12+
def "should not disable shedlock"() {
13+
setup:
14+
def context = new AnnotationConfigApplicationContext(ShedlockConfig)
15+
JdbcTemplate jdbcTemplate = new JdbcTemplate(context.getBean(DataSource))
16+
jdbcTemplate.execute("CREATE TABLE shedlock(name VARCHAR(64) NOT NULL PRIMARY KEY, lock_until TIMESTAMP NOT NULL,\n" +
17+
" locked_at TIMESTAMP NOT NULL, locked_by VARCHAR(255) NOT NULL);")
18+
def task = context.getBean(ShedLockedTask)
19+
20+
expect: "lock is held for more than one second"
21+
!task.awaitInvocation(1000, TimeUnit.MILLISECONDS)
22+
task.invocationCount() == 1
23+
24+
cleanup:
25+
context.close()
26+
}
27+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import datadog.trace.agent.test.AgentTestRunner
2+
import org.springframework.context.annotation.AnnotationConfigApplicationContext
3+
4+
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
5+
6+
class SpringAsyncTest extends AgentTestRunner {
7+
8+
def "context propagated through @async annotation"() {
9+
setup:
10+
def context = new AnnotationConfigApplicationContext(AsyncTaskConfig)
11+
AsyncTask asyncTask = context.getBean(AsyncTask)
12+
when:
13+
runUnderTrace("root") {
14+
asyncTask.async().join()
15+
}
16+
then:
17+
assertTraces(1) {
18+
trace(3) {
19+
span {
20+
resourceName "root"
21+
}
22+
span {
23+
resourceName "AsyncTask.async"
24+
threadNameStartsWith "SimpleAsyncTaskExecutor"
25+
childOf span(0)
26+
}
27+
span {
28+
resourceName "AsyncTask.getInt"
29+
threadNameStartsWith "SimpleAsyncTaskExecutor"
30+
childOf span(1)
31+
}
32+
}
33+
}
34+
}
35+
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import static datadog.trace.agent.test.utils.TraceUtils.basicSpan
2+
3+
import datadog.trace.agent.test.AgentTestRunner
4+
import datadog.trace.bootstrap.instrumentation.api.Tags
5+
import org.springframework.boot.actuate.scheduling.ScheduledTasksEndpoint
6+
import org.springframework.context.annotation.AnnotationConfigApplicationContext
7+
8+
import java.util.concurrent.TimeUnit
9+
10+
class SpringSchedulingTest extends AgentTestRunner {
11+
12+
def legacyTracing() {
13+
false
14+
}
15+
16+
@Override
17+
protected void configurePreAgent() {
18+
super.configurePreAgent()
19+
if (legacyTracing()) {
20+
injectSysConfig("spring-scheduling.legacy.tracing.enabled", "true")
21+
}
22+
}
23+
24+
def "schedule trigger test according to cron expression"() {
25+
setup:
26+
def context = new AnnotationConfigApplicationContext(TriggerTaskConfig, SchedulingConfig)
27+
def task = context.getBean(TriggerTask)
28+
29+
task.blockUntilExecute()
30+
31+
expect:
32+
assert task != null
33+
def hasParent = legacyTracing()
34+
assertTraces(hasParent ? 1 : 2) {
35+
if (!hasParent) {
36+
trace(1) {
37+
basicSpan(it, "parent")
38+
}
39+
}
40+
trace(hasParent ? 2 : 1) {
41+
if (hasParent) {
42+
basicSpan(it, "parent")
43+
}
44+
span {
45+
resourceName "TriggerTask.run"
46+
operationName "scheduled.call"
47+
hasParent ? childOfPrevious() : parent()
48+
errored false
49+
tags {
50+
"$Tags.COMPONENT" "spring-scheduling"
51+
defaultTags()
52+
}
53+
}
54+
}
55+
}
56+
and:
57+
def scheduledTaskEndpoint = context.getBean(ScheduledTasksEndpoint)
58+
assert scheduledTaskEndpoint != null
59+
scheduledTaskEndpoint.scheduledTasks().getCron().each {
60+
it.getRunnable().getTarget() == TriggerTask.getName()
61+
}
62+
cleanup:
63+
context.close()
64+
}
65+
66+
def "schedule interval test"() {
67+
setup:
68+
def context = new AnnotationConfigApplicationContext(IntervalTaskConfig, SchedulingConfig)
69+
def task = context.getBean(IntervalTask)
70+
71+
task.blockUntilExecute()
72+
73+
expect:
74+
assert task != null
75+
def hasParent = legacyTracing()
76+
77+
assertTraces(hasParent ? 1 : 2) {
78+
if (!hasParent) {
79+
trace(1) {
80+
basicSpan(it, "parent")
81+
}
82+
}
83+
trace(hasParent ? 2 : 1) {
84+
if (hasParent) {
85+
basicSpan(it, "parent")
86+
}
87+
span {
88+
resourceName "IntervalTask.run"
89+
operationName "scheduled.call"
90+
hasParent ? childOfPrevious() : parent()
91+
errored false
92+
tags {
93+
"$Tags.COMPONENT" "spring-scheduling"
94+
defaultTags()
95+
}
96+
}
97+
}
98+
}
99+
cleanup:
100+
context.close()
101+
}
102+
103+
def "schedule lambda test"() {
104+
setup:
105+
def context = new AnnotationConfigApplicationContext(LambdaTaskConfig, SchedulingConfig)
106+
def configurer = context.getBean(LambdaTaskConfigurer)
107+
108+
configurer.singleUseLatch.await(2000, TimeUnit.MILLISECONDS)
109+
110+
expect:
111+
def hasParent = legacyTracing()
112+
assertTraces(hasParent ? 1 : 2) {
113+
if (!hasParent) {
114+
trace(1) {
115+
basicSpan(it, "parent")
116+
}
117+
}
118+
trace(hasParent ? 2 : 1) {
119+
if (hasParent) {
120+
basicSpan(it, "parent")
121+
}
122+
span {
123+
resourceNameContains("LambdaTaskConfigurer\$\$Lambda")
124+
operationName "scheduled.call"
125+
hasParent ? childOfPrevious() : parent()
126+
errored false
127+
tags {
128+
"$Tags.COMPONENT" "spring-scheduling"
129+
defaultTags()
130+
}
131+
}
132+
}
133+
}
134+
135+
cleanup:
136+
context.close()
137+
}
138+
}
139+
140+
class SpringSchedulingLegacyTracingForkedTest extends SpringSchedulingTest {
141+
@Override
142+
def legacyTracing() {
143+
true
144+
}
145+
}
146+
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import datadog.trace.api.Trace;
2+
import java.util.concurrent.CompletableFuture;
3+
import java.util.concurrent.ThreadLocalRandom;
4+
import org.springframework.scheduling.annotation.Async;
5+
6+
public class AsyncTask {
7+
8+
@Async
9+
public CompletableFuture<Integer> async() {
10+
return CompletableFuture.completedFuture(getInt());
11+
}
12+
13+
@Trace
14+
public int getInt() {
15+
return ThreadLocalRandom.current().nextInt();
16+
}
17+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import org.springframework.context.annotation.Bean;
2+
import org.springframework.context.annotation.Configuration;
3+
import org.springframework.scheduling.annotation.EnableAsync;
4+
import org.springframework.scheduling.annotation.EnableScheduling;
5+
6+
@Configuration
7+
@EnableScheduling
8+
@EnableAsync
9+
public class AsyncTaskConfig {
10+
11+
@Bean
12+
AsyncTask asyncTask() {
13+
return new AsyncTask();
14+
}
15+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import java.util.concurrent.CountDownLatch;
2+
import java.util.concurrent.TimeUnit;
3+
import org.springframework.scheduling.annotation.Scheduled;
4+
import org.springframework.stereotype.Component;
5+
6+
@Component
7+
public class IntervalTask implements Runnable {
8+
9+
private final CountDownLatch latch = new CountDownLatch(1);
10+
11+
@Scheduled(fixedRate = 5000, scheduler = "tracingTaskScheduler")
12+
@Override
13+
public void run() {
14+
latch.countDown();
15+
}
16+
17+
public void blockUntilExecute() throws InterruptedException {
18+
latch.await(5, TimeUnit.SECONDS);
19+
}
20+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import org.springframework.context.annotation.Bean;
2+
import org.springframework.context.annotation.Configuration;
3+
import org.springframework.scheduling.annotation.EnableScheduling;
4+
5+
@Configuration
6+
@EnableScheduling
7+
public class IntervalTaskConfig {
8+
@Bean
9+
public IntervalTask scheduledTasks() {
10+
return new IntervalTask();
11+
}
12+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import org.springframework.context.annotation.Bean;
2+
import org.springframework.context.annotation.Configuration;
3+
import org.springframework.scheduling.annotation.EnableScheduling;
4+
5+
@Configuration
6+
@EnableScheduling
7+
public class LambdaTaskConfig {
8+
9+
@Bean
10+
LambdaTaskConfigurer lambdaTaskConfigurer() {
11+
return new LambdaTaskConfigurer();
12+
}
13+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import java.util.concurrent.CountDownLatch;
2+
import org.springframework.scheduling.annotation.SchedulingConfigurer;
3+
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
4+
import org.springframework.stereotype.Service;
5+
6+
@Service
7+
public class LambdaTaskConfigurer implements SchedulingConfigurer {
8+
9+
public final CountDownLatch singleUseLatch = new CountDownLatch(1);
10+
11+
@Override
12+
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
13+
// need to manually set in this case since it won't use the scheduler in the annotation
14+
taskRegistrar.setTaskScheduler(new SchedulingConfig.TracingTaskScheduler());
15+
taskRegistrar.addFixedDelayTask(singleUseLatch::countDown, 500);
16+
}
17+
}

0 commit comments

Comments
 (0)