Skip to content

Commit cbc616c

Browse files
authored
Fix kotlin coroutine context propagation (#7879)
Resolves #7837 `org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1` adds a second `newCoroutineContext` that we shouldn't instrument. When we instrument it the order of [`KotlinContextElement`](https://github.com/open-telemetry/opentelemetry-java/blob/main/extensions/kotlin/src/main/java/io/opentelemetry/extension/kotlin/KotlinContextElement.java) and user added `ThreadContextElement` gets reversed. If user added `ThreadContextElement` changes opentelemetry context then these changes will get overwritten by `KotlinContextElement`.
1 parent fe263ed commit cbc616c

File tree

2 files changed

+40
-0
lines changed

2 files changed

+40
-0
lines changed

instrumentation/kotlinx-coroutines/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/KotlinCoroutinesInstrumentation.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public ElementMatcher<TypeDescription> typeMatcher() {
2525
public void transform(TypeTransformer transformer) {
2626
transformer.applyAdviceToMethod(
2727
named("newCoroutineContext")
28+
.and(takesArgument(0, named("kotlinx.coroutines.CoroutineScope")))
2829
.and(takesArgument(1, named("kotlin.coroutines.CoroutineContext"))),
2930
this.getClass().getName() + "$ContextAdvice");
3031
}

instrumentation/kotlinx-coroutines/javaagent/src/test/kotlin/io/opentelemetry/javaagent/instrumentation/kotlinxcoroutines/KotlinCoroutinesInstrumentationTest.kt

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package io.opentelemetry.javaagent.instrumentation.kotlinxcoroutines
77

88
import io.opentelemetry.context.Context
99
import io.opentelemetry.context.ContextKey
10+
import io.opentelemetry.context.Scope
1011
import io.opentelemetry.extension.kotlin.asContextElement
1112
import io.opentelemetry.extension.kotlin.getOpenTelemetryContext
1213
import io.opentelemetry.instrumentation.reactor.v3_1.ContextPropagationOperator
@@ -20,6 +21,7 @@ import kotlinx.coroutines.CoroutineStart
2021
import kotlinx.coroutines.Dispatchers
2122
import kotlinx.coroutines.ExperimentalCoroutinesApi
2223
import kotlinx.coroutines.GlobalScope
24+
import kotlinx.coroutines.ThreadContextElement
2325
import kotlinx.coroutines.asCoroutineDispatcher
2426
import kotlinx.coroutines.async
2527
import kotlinx.coroutines.awaitAll
@@ -54,6 +56,7 @@ import java.util.concurrent.Executors
5456
import java.util.concurrent.TimeUnit
5557
import java.util.function.Consumer
5658
import java.util.stream.Stream
59+
import kotlin.coroutines.CoroutineContext
5760

5861
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
5962
@ExperimentalCoroutinesApi
@@ -563,4 +566,40 @@ class KotlinCoroutinesInstrumentationTest {
563566
class DispatcherWrapper(val dispatcher: CoroutineDispatcher) {
564567
override fun toString(): String = dispatcher.toString()
565568
}
569+
570+
// regression test for https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/7837
571+
// tests that a custom ThreadContextElement runs after KotlinContextElement that is used for
572+
// context propagation in coroutines
573+
@Test
574+
fun `test custom context element`() {
575+
val testValue = "test-value"
576+
val contextKey = ContextKey.named<String>("test-key")
577+
val scope = Context.current().with(contextKey, "wrong value").makeCurrent()
578+
scope.use {
579+
runBlocking {
580+
val context = Context.current().with(contextKey, testValue)
581+
withContext(TestContextElement(context)) {
582+
delay(10)
583+
val result = Context.current().get(contextKey)
584+
assertThat(result).isEqualTo(testValue)
585+
}
586+
}
587+
}
588+
}
589+
590+
class TestContextElement(private val otelContext: Context) : ThreadContextElement<Scope> {
591+
companion object Key : CoroutineContext.Key<TestContextElement> {
592+
}
593+
594+
override val key: CoroutineContext.Key<TestContextElement>
595+
get() = Key
596+
597+
override fun restoreThreadContext(context: CoroutineContext, oldState: Scope) {
598+
oldState.close()
599+
}
600+
601+
override fun updateThreadContext(context: CoroutineContext): Scope {
602+
return otelContext.makeCurrent()
603+
}
604+
}
566605
}

0 commit comments

Comments
 (0)