Skip to content

Commit 27b870a

Browse files
committed
RUM-10040: Perform lazy capture of DatadogContext at the span creation
1 parent d188b59 commit 27b870a

File tree

31 files changed

+776
-387
lines changed

31 files changed

+776
-387
lines changed

dd-sdk-android-core/api/apiSurface

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ interface com.datadog.android.api.feature.FeatureEventReceiver
109109
interface com.datadog.android.api.feature.FeatureScope
110110
val dataStore: com.datadog.android.api.storage.datastore.DataStoreHandler
111111
fun withWriteContext((com.datadog.android.api.context.DatadogContext) -> Unit)
112+
fun withContext((com.datadog.android.api.context.DatadogContext) -> Unit)
112113
fun getWriteContextSync(): Pair<com.datadog.android.api.context.DatadogContext, EventWriteScope>?
113114
fun sendEvent(Any)
114115
fun <T: Feature> unwrap(): T

dd-sdk-android-core/api/dd-sdk-android-core.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,7 @@ public abstract interface class com/datadog/android/api/feature/FeatureScope {
345345
public abstract fun getWriteContextSync ()Lkotlin/Pair;
346346
public abstract fun sendEvent (Ljava/lang/Object;)V
347347
public abstract fun unwrap ()Lcom/datadog/android/api/feature/Feature;
348+
public abstract fun withContext (Lkotlin/jvm/functions/Function1;)V
348349
public abstract fun withWriteContext (Lkotlin/jvm/functions/Function2;)V
349350
}
350351

dd-sdk-android-core/src/main/kotlin/com/datadog/android/api/feature/FeatureScope.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,25 @@ interface FeatureScope {
2525
/**
2626
* Utility to write an event, asynchronously.
2727
* @param callback an operation called with an up-to-date [DatadogContext]
28-
* and an [EventWriteScope]. Callback will be executed on a single context processing worker thread.
28+
* and an [EventWriteScope]. Callback will be executed on a single context processing worker thread. Execution of
29+
* [EventWriteScope] will be done on a worker thread from I/O pool.
2930
* [DatadogContext] will have a state created at the moment this method is called.
3031
*/
3132
@AnyThread
3233
fun withWriteContext(
3334
callback: (datadogContext: DatadogContext, write: EventWriteScope) -> Unit
3435
)
3536

37+
/**
38+
* Utility to read current [DatadogContext], asynchronously.
39+
* @param callback an operation called with an up-to-date [DatadogContext].
40+
* [DatadogContext] will have a state created at the moment this method is called.
41+
*/
42+
@AnyThread
43+
fun withContext(
44+
callback: (datadogContext: DatadogContext) -> Unit
45+
)
46+
3647
// TODO RUM-9852 Implement better passthrough mechanism for the JVM crash scenario
3748
/**
3849
* Same as [withWriteContext] but will be executed in the blocking manner.

dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/SdkFeature.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,16 @@ internal class SdkFeature(
185185
}
186186
}
187187

188+
override fun withContext(callback: (datadogContext: DatadogContext) -> Unit) {
189+
coreFeature.contextExecutorService
190+
.executeSafe("withContext-${wrappedFeature.name}", internalLogger) {
191+
val contextProvider = coreFeature.contextProvider
192+
if (contextProvider is NoOpContextProvider) return@executeSafe
193+
val context = contextProvider.context
194+
callback(context)
195+
}
196+
}
197+
188198
override fun getWriteContextSync(): Pair<DatadogContext, EventWriteScope>? {
189199
val operationName = "getWriteContextSync-${wrappedFeature.name}"
190200
return coreFeature.contextExecutorService

dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/SdkFeatureTest.kt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,38 @@ internal class SdkFeatureTest {
521521
verifyNoInteractions(callback)
522522
}
523523

524+
@Test
525+
fun `M provide Datadog context W withContext(callback)`(
526+
@Forgery fakeContext: DatadogContext
527+
) {
528+
// Given
529+
testedFeature.storage = mockStorage
530+
val callback = mock<(DatadogContext) -> Unit>()
531+
whenever(coreFeature.mockInstance.contextProvider.context) doReturn fakeContext
532+
533+
// When
534+
testedFeature.withContext(callback = callback)
535+
536+
// Then
537+
verify(callback).invoke(fakeContext)
538+
}
539+
540+
@Test
541+
fun `M do nothing W withContext(callback) { no Datadog context }`() {
542+
// Given
543+
testedFeature.storage = mockStorage
544+
val callback = mock<(DatadogContext) -> Unit>()
545+
546+
whenever(coreFeature.mockInstance.contextProvider) doReturn NoOpContextProvider()
547+
548+
// When
549+
testedFeature.withContext(callback = callback)
550+
551+
// Then
552+
verifyNoInteractions(mockStorage)
553+
verifyNoInteractions(callback)
554+
}
555+
524556
@Test
525557
fun `M provide write context W getWriteContextSync()`(
526558
@Forgery fakeContext: DatadogContext,

dd-sdk-android-internal/api/apiSurface

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ class com.datadog.android.internal.collections.EvictingQueue<T> : java.util.Queu
44
override fun add(T): Boolean
55
override fun offer(T): Boolean
66
override fun addAll(Collection<T>): Boolean
7+
class com.datadog.android.internal.concurrent.CompletableFuture<T: Any>
8+
var value: T
9+
fun isComplete(): Boolean
10+
fun complete(T)
711
interface com.datadog.android.internal.profiler.BenchmarkCounter
812
fun add(Long, Map<String, String>)
913
interface com.datadog.android.internal.profiler.BenchmarkMeter

dd-sdk-android-internal/api/dd-sdk-android-internal.api

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ public final class com/datadog/android/internal/collections/EvictingQueue : java
2222
public fun toArray ([Ljava/lang/Object;)[Ljava/lang/Object;
2323
}
2424

25+
public final class com/datadog/android/internal/concurrent/CompletableFuture {
26+
public fun <init> ()V
27+
public final fun complete (Ljava/lang/Object;)V
28+
public final fun getValue ()Ljava/lang/Object;
29+
public final fun isComplete ()Z
30+
}
31+
2532
public abstract interface class com/datadog/android/internal/profiler/BenchmarkCounter {
2633
public abstract fun add (JLjava/util/Map;)V
2734
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
* Copyright 2016-Present Datadog, Inc.
5+
*/
6+
7+
package com.datadog.android.internal.concurrent
8+
9+
/**
10+
* java.util.concurrent.CompletableFuture is available only starting from API 24, so here
11+
* is a simple class mimicking very basics of it.
12+
*/
13+
@Suppress("UndocumentedPublicFunction", "UndocumentedPublicProperty")
14+
class CompletableFuture<T : Any> {
15+
@Volatile
16+
lateinit var value: T
17+
private set
18+
19+
fun isComplete(): Boolean = this::value.isInitialized
20+
21+
fun complete(value: T) {
22+
if (isComplete()) return
23+
this.value = value
24+
}
25+
}

dd-sdk-android-internal/src/test/java/com/datadog/internal/collections/EvictingQueueTest.kt renamed to dd-sdk-android-internal/src/test/java/com/datadog/android/internal/collections/EvictingQueueTest.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@
33
* This product includes software developed at Datadog (https://www.datadoghq.com/).
44
* Copyright 2016-Present Datadog, Inc.
55
*/
6-
package com.datadog.internal.collections
6+
package com.datadog.android.internal.collections
77

8-
import com.datadog.android.internal.collections.EvictingQueue
98
import org.assertj.core.api.Assertions.assertThat
109
import org.junit.jupiter.api.Test
1110
import org.junit.jupiter.api.assertDoesNotThrow
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
* Copyright 2016-Present Datadog, Inc.
5+
*/
6+
7+
package com.datadog.android.internal.concurrent
8+
9+
import fr.xgouchet.elmyr.annotation.StringForgery
10+
import fr.xgouchet.elmyr.junit5.ForgeExtension
11+
import org.assertj.core.api.Assertions.assertThat
12+
import org.junit.jupiter.api.RepeatedTest
13+
import org.junit.jupiter.api.Test
14+
import org.junit.jupiter.api.assertThrows
15+
import org.junit.jupiter.api.extension.ExtendWith
16+
import org.junit.jupiter.api.extension.Extensions
17+
18+
@Extensions(
19+
ExtendWith(ForgeExtension::class)
20+
)
21+
class CompletableFutureTest {
22+
23+
@Test
24+
fun `M return value W value { future is completed }`(
25+
@StringForgery fakeValue: String
26+
) {
27+
// Given
28+
val future = CompletableFuture<String>()
29+
future.complete(fakeValue)
30+
31+
// When
32+
val value = future.value
33+
34+
// Then
35+
assertThat(value).isEqualTo(fakeValue)
36+
}
37+
38+
@RepeatedTest(10)
39+
fun `M return value W value { future is completed, different threads }`(
40+
@StringForgery fakeValue: String
41+
) {
42+
// Given
43+
val future = CompletableFuture<String>()
44+
Thread {
45+
future.complete(fakeValue)
46+
}.apply {
47+
start()
48+
join()
49+
}
50+
51+
// When
52+
val value = future.value
53+
54+
// Then
55+
assertThat(value).isEqualTo(fakeValue)
56+
}
57+
58+
@Test
59+
fun `M set value only once W value { multiple complete calls }`(
60+
@StringForgery fakeValue: String,
61+
@StringForgery anotherFakeValue: String
62+
) {
63+
// Given
64+
val future = CompletableFuture<String>()
65+
future.complete(fakeValue)
66+
future.complete(anotherFakeValue)
67+
68+
// When
69+
val value = future.value
70+
71+
// Then
72+
assertThat(value).isEqualTo(fakeValue)
73+
}
74+
75+
@Test
76+
fun `M throw error W value { future is not completed }`() {
77+
// Given
78+
val future = CompletableFuture<String>()
79+
80+
// When + Then
81+
assertThrows<UninitializedPropertyAccessException> {
82+
future.value
83+
}
84+
}
85+
86+
@Test
87+
fun `M return true W isComplete() { future is completed }`(
88+
@StringForgery fakeValue: String
89+
) {
90+
// Given
91+
val future = CompletableFuture<String>()
92+
future.complete(fakeValue)
93+
94+
// When
95+
val result = future.isComplete()
96+
97+
// Then
98+
assertThat(result).isTrue()
99+
}
100+
101+
@Test
102+
fun `M return false W isComplete() { future is not completed }`() {
103+
// Given
104+
val future = CompletableFuture<String>()
105+
106+
// When
107+
val result = future.isComplete()
108+
109+
// Then
110+
assertThat(result).isFalse()
111+
}
112+
}

0 commit comments

Comments
 (0)