Skip to content

Commit 4b15242

Browse files
committed
RUM-10314: Read feature context only when explicitly requested by the caller
1 parent bb9a9a8 commit 4b15242

File tree

56 files changed

+505
-360
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+505
-360
lines changed

dd-sdk-android-core/api/apiSurface

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,9 @@ interface com.datadog.android.api.feature.FeatureEventReceiver
108108
fun onReceive(Any)
109109
interface com.datadog.android.api.feature.FeatureScope
110110
val dataStore: com.datadog.android.api.storage.datastore.DataStoreHandler
111-
fun withWriteContext((com.datadog.android.api.context.DatadogContext) -> Unit)
112-
fun withContext((com.datadog.android.api.context.DatadogContext) -> Unit)
113-
fun getWriteContextSync(): Pair<com.datadog.android.api.context.DatadogContext, EventWriteScope>?
111+
fun withWriteContext(Set<String> = emptySet(), (com.datadog.android.api.context.DatadogContext) -> Unit)
112+
fun withContext(Set<String> = emptySet(), (com.datadog.android.api.context.DatadogContext) -> Unit)
113+
fun getWriteContextSync(Set<String> = emptySet()): Pair<com.datadog.android.api.context.DatadogContext, EventWriteScope>?
114114
fun sendEvent(Any)
115115
fun <T: Feature> unwrap(): T
116116
typealias EventWriteScope = ((com.datadog.android.api.storage.EventBatchWriter) -> Unit) -> Unit
@@ -191,7 +191,7 @@ interface com.datadog.android.core.InternalSdkCore : com.datadog.android.api.fea
191191
fun writeLastFatalAnrSent(Long)
192192
fun getPersistenceExecutorService(): java.util.concurrent.ExecutorService
193193
fun getAllFeatures(): List<com.datadog.android.api.feature.FeatureScope>
194-
fun getDatadogContext(): com.datadog.android.api.context.DatadogContext?
194+
fun getDatadogContext(Set<String> = emptySet()): com.datadog.android.api.context.DatadogContext?
195195
class com.datadog.android.core.SdkReference
196196
constructor(String? = null, (com.datadog.android.api.SdkCore) -> Unit = {})
197197
fun get(): com.datadog.android.api.SdkCore?

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -342,11 +342,17 @@ public abstract interface class com/datadog/android/api/feature/FeatureEventRece
342342

343343
public abstract interface class com/datadog/android/api/feature/FeatureScope {
344344
public abstract fun getDataStore ()Lcom/datadog/android/api/storage/datastore/DataStoreHandler;
345-
public abstract fun getWriteContextSync ()Lkotlin/Pair;
345+
public abstract fun getWriteContextSync (Ljava/util/Set;)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
349-
public abstract fun withWriteContext (Lkotlin/jvm/functions/Function2;)V
348+
public abstract fun withContext (Ljava/util/Set;Lkotlin/jvm/functions/Function1;)V
349+
public abstract fun withWriteContext (Ljava/util/Set;Lkotlin/jvm/functions/Function2;)V
350+
}
351+
352+
public final class com/datadog/android/api/feature/FeatureScope$DefaultImpls {
353+
public static synthetic fun getWriteContextSync$default (Lcom/datadog/android/api/feature/FeatureScope;Ljava/util/Set;ILjava/lang/Object;)Lkotlin/Pair;
354+
public static synthetic fun withContext$default (Lcom/datadog/android/api/feature/FeatureScope;Ljava/util/Set;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
355+
public static synthetic fun withWriteContext$default (Lcom/datadog/android/api/feature/FeatureScope;Ljava/util/Set;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V
350356
}
351357

352358
public final class com/datadog/android/api/feature/FeatureScopeExtKt {
@@ -533,7 +539,7 @@ public abstract interface class com/datadog/android/core/InternalSdkCore : com/d
533539
public abstract fun deleteLastViewEvent ()V
534540
public abstract fun getAllFeatures ()Ljava/util/List;
535541
public abstract fun getAppStartTimeNs ()J
536-
public abstract fun getDatadogContext ()Lcom/datadog/android/api/context/DatadogContext;
542+
public abstract fun getDatadogContext (Ljava/util/Set;)Lcom/datadog/android/api/context/DatadogContext;
537543
public abstract fun getFirstPartyHostResolver ()Lcom/datadog/android/core/internal/net/FirstPartyHostHeaderTypeResolver;
538544
public abstract fun getLastFatalAnrSent ()Ljava/lang/Long;
539545
public abstract fun getLastViewEvent ()Lcom/google/gson/JsonObject;
@@ -546,6 +552,10 @@ public abstract interface class com/datadog/android/core/InternalSdkCore : com/d
546552
public abstract fun writeLastViewEvent ([B)V
547553
}
548554

555+
public final class com/datadog/android/core/InternalSdkCore$DefaultImpls {
556+
public static synthetic fun getDatadogContext$default (Lcom/datadog/android/core/InternalSdkCore;Ljava/util/Set;ILjava/lang/Object;)Lcom/datadog/android/api/context/DatadogContext;
557+
}
558+
549559
public final class com/datadog/android/core/SdkReference {
550560
public fun <init> ()V
551561
public fun <init> (Ljava/lang/String;)V

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,35 +24,47 @@ interface FeatureScope {
2424

2525
/**
2626
* Utility to write an event, asynchronously.
27+
* @param withFeatureContexts Feature contexts ([DatadogContext.featuresContext] property) to include
28+
* in the [DatadogContext] provided. The value should be the feature names as declared by [Feature.name].
29+
* Default is empty, meaning that no feature contexts will be included.
2730
* @param callback an operation called with an up-to-date [DatadogContext]
2831
* and an [EventWriteScope]. Callback will be executed on a single context processing worker thread. Execution of
2932
* [EventWriteScope] will be done on a worker thread from I/O pool.
3033
* [DatadogContext] will have a state created at the moment this method is called.
3134
*/
3235
@AnyThread
3336
fun withWriteContext(
37+
withFeatureContexts: Set<String> = emptySet(),
3438
callback: (datadogContext: DatadogContext, write: EventWriteScope) -> Unit
3539
)
3640

3741
/**
3842
* Utility to read current [DatadogContext], asynchronously.
43+
* @param withFeatureContexts Feature contexts ([DatadogContext.featuresContext] property) to include
44+
* in the [DatadogContext] provided. The value should be the feature names as declared by [Feature.name].
45+
* Default is empty, meaning that no feature contexts will be included.
3946
* @param callback an operation called with an up-to-date [DatadogContext].
4047
* [DatadogContext] will have a state created at the moment this method is called.
4148
*/
4249
@AnyThread
4350
fun withContext(
51+
withFeatureContexts: Set<String> = emptySet(),
4452
callback: (datadogContext: DatadogContext) -> Unit
4553
)
4654

4755
// TODO RUM-9852 Implement better passthrough mechanism for the JVM crash scenario
4856
/**
4957
* Same as [withWriteContext] but will be executed in the blocking manner.
5058
*
59+
* @param withFeatureContexts Feature contexts ([DatadogContext.featuresContext] property) to include
60+
* in the [DatadogContext] provided. The value should be the feature names as declared by [Feature.name].
61+
* Default is empty, meaning that no feature contexts will be included.
62+
*
5163
* **NOTE**: This API is for the internal use only and is not guaranteed to be stable.
5264
*/
5365
@AnyThread
5466
@InternalApi
55-
fun getWriteContextSync(): Pair<DatadogContext, EventWriteScope>?
67+
fun getWriteContextSync(withFeatureContexts: Set<String> = emptySet()): Pair<DatadogContext, EventWriteScope>?
5668

5769
/**
5870
* Send event to a given feature. It will be sent in a synchronous way.

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,11 @@ interface InternalSdkCore : FeatureSdkCore {
116116
fun getAllFeatures(): List<FeatureScope>
117117

118118
/**
119-
* @return the current DatadogContext, or null
119+
* @param withFeatureContexts Feature contexts ([DatadogContext.featuresContext] property) to include
120+
* in the [DatadogContext] provided. The value should be the feature names as declared by [Feature.name].
121+
* Default is empty, meaning that no feature contexts will be included.
122+
* @return the current [DatadogContext], or null
120123
*/
121124
@InternalApi
122-
fun getDatadogContext(): DatadogContext?
125+
fun getDatadogContext(withFeatureContexts: Set<String> = emptySet()): DatadogContext?
123126
}

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,16 @@
77
package com.datadog.android.core.internal
88

99
import com.datadog.android.api.context.DatadogContext
10+
import com.datadog.android.api.feature.Feature
1011

1112
internal interface ContextProvider {
1213
// TODO RUM-3784 lifecycle checks may be needed for the cases when context is requested
1314
// when datadog is not initialized yet/anymore (case of UploadWorker, other calls site
1415
// should be in sync with lifecycle)
15-
val context: DatadogContext
16+
/**
17+
* @param withFeatureContexts Feature contexts ([DatadogContext.featuresContext] property) to include
18+
* in the [DatadogContext] provided. The value should be the feature names as declared by [Feature.name].
19+
* Default is empty, meaning that no feature contexts will be included.
20+
*/
21+
fun getContext(withFeatureContexts: Set<String>): DatadogContext
1622
}

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

Lines changed: 52 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -16,64 +16,58 @@ internal class DatadogContextProvider(
1616
private val coreFeature: CoreFeature,
1717
private val featureContextProvider: FeatureContextProvider
1818
) : ContextProvider {
19-
override val context: DatadogContext
20-
get() {
21-
// IMPORTANT All properties should be immutable and be frozen at the state
22-
// of the context construction moment
23-
return DatadogContext(
24-
site = coreFeature.site,
25-
clientToken = coreFeature.clientToken,
26-
service = coreFeature.serviceName,
27-
env = coreFeature.envName,
28-
version = coreFeature.packageVersionProvider.version,
29-
variant = coreFeature.variant,
30-
sdkVersion = coreFeature.sdkVersion,
31-
source = coreFeature.sourceName,
32-
time = with(coreFeature.timeProvider) {
33-
val deviceTimeMs = getDeviceTimestamp()
34-
val serverTimeMs = getServerTimestamp()
35-
TimeInfo(
36-
deviceTimeNs = TimeUnit.MILLISECONDS.toNanos(deviceTimeMs),
37-
serverTimeNs = TimeUnit.MILLISECONDS.toNanos(serverTimeMs),
38-
serverTimeOffsetNs = TimeUnit.MILLISECONDS
39-
.toNanos(serverTimeMs - deviceTimeMs),
40-
serverTimeOffsetMs = serverTimeMs - deviceTimeMs
41-
)
42-
},
43-
processInfo = ProcessInfo(
44-
isMainProcess = coreFeature.isMainProcess
45-
),
46-
networkInfo = coreFeature.networkInfoProvider.getLatestNetworkInfo(),
47-
deviceInfo = with(coreFeature.androidInfoProvider) {
48-
DeviceInfo(
49-
deviceName = deviceName,
50-
deviceBrand = deviceBrand,
51-
deviceType = deviceType,
52-
deviceModel = deviceModel,
53-
deviceBuildId = deviceBuildId,
54-
osName = osName,
55-
osVersion = osVersion,
56-
osMajorVersion = osMajorVersion,
57-
architecture = architecture,
58-
numberOfDisplays = numberOfDisplays
59-
)
60-
},
61-
userInfo = coreFeature.userInfoProvider.getUserInfo(),
62-
trackingConsent = coreFeature.trackingConsentProvider.getConsent(),
63-
appBuildId = coreFeature.appBuildId,
64-
// toMap call here (and in getFeatureContext) is VERY important - this will make
65-
// independent snapshot of the features context which is not affected by the
66-
// changes which can be made later by another thread.
67-
// Values at the top 2 levels are frozen: feature-name key,
68-
// and feature-specific-name key.
69-
featuresContext = mutableMapOf<String, Map<String, Any?>>().apply {
70-
featureContextProvider.getFeaturesContexts().forEach {
71-
val value = it.second()
72-
if (value.isNotEmpty()) {
73-
this[it.first] = value.toMap()
74-
}
19+
override fun getContext(withFeatureContexts: Set<String>): DatadogContext {
20+
// IMPORTANT All properties should be immutable and be frozen at the state
21+
// of the context construction moment
22+
return DatadogContext(
23+
site = coreFeature.site,
24+
clientToken = coreFeature.clientToken,
25+
service = coreFeature.serviceName,
26+
env = coreFeature.envName,
27+
version = coreFeature.packageVersionProvider.version,
28+
variant = coreFeature.variant,
29+
sdkVersion = coreFeature.sdkVersion,
30+
source = coreFeature.sourceName,
31+
time = with(coreFeature.timeProvider) {
32+
val deviceTimeMs = getDeviceTimestamp()
33+
val serverTimeMs = getServerTimestamp()
34+
TimeInfo(
35+
deviceTimeNs = TimeUnit.MILLISECONDS.toNanos(deviceTimeMs),
36+
serverTimeNs = TimeUnit.MILLISECONDS.toNanos(serverTimeMs),
37+
serverTimeOffsetNs = TimeUnit.MILLISECONDS
38+
.toNanos(serverTimeMs - deviceTimeMs),
39+
serverTimeOffsetMs = serverTimeMs - deviceTimeMs
40+
)
41+
},
42+
processInfo = ProcessInfo(
43+
isMainProcess = coreFeature.isMainProcess
44+
),
45+
networkInfo = coreFeature.networkInfoProvider.getLatestNetworkInfo(),
46+
deviceInfo = with(coreFeature.androidInfoProvider) {
47+
DeviceInfo(
48+
deviceName = deviceName,
49+
deviceBrand = deviceBrand,
50+
deviceType = deviceType,
51+
deviceModel = deviceModel,
52+
deviceBuildId = deviceBuildId,
53+
osName = osName,
54+
osVersion = osVersion,
55+
osMajorVersion = osMajorVersion,
56+
architecture = architecture,
57+
numberOfDisplays = numberOfDisplays
58+
)
59+
},
60+
userInfo = coreFeature.userInfoProvider.getUserInfo(),
61+
trackingConsent = coreFeature.trackingConsentProvider.getConsent(),
62+
appBuildId = coreFeature.appBuildId,
63+
featuresContext = mutableMapOf<String, Map<String, Any?>>().apply {
64+
withFeatureContexts.forEach {
65+
val featureContext = featureContextProvider.getFeatureContext(it)
66+
if (featureContext.isNotEmpty()) {
67+
this[it] = featureContext
7568
}
7669
}
77-
)
78-
}
70+
}
71+
)
72+
}
7973
}

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

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,9 @@ internal class DatadogCore(
230230
val callable = Callable<Map<String, Any?>> {
231231
val feature = features[featureName] ?: return@Callable emptyMap()
232232
return@Callable feature.featureContextLock.readLock().safeWithLock {
233+
// Creating copy here is VERY important - this will make
234+
// independent snapshot of the features context which is not affected by the
235+
// changes which can be made later by another thread.
233236
// Use HashMap instead of .toMutableMap() for faster init
234237
@Suppress("UnsafeThirdPartyFunctionCall") // NPE cannot happen here
235238
HashMap(feature.featureContext)
@@ -387,13 +390,13 @@ internal class DatadogCore(
387390
return features.values.toList()
388391
}
389392

390-
override fun getDatadogContext(): DatadogContext? {
393+
override fun getDatadogContext(withFeatureContexts: Set<String>): DatadogContext? {
391394
return coreFeature.contextExecutorService
392395
.submitSafe(
393396
"getDatadogContext",
394397
internalLogger,
395398
Callable {
396-
with(contextProvider) { if (this is NoOpContextProvider) null else context }
399+
with(contextProvider) { if (this is NoOpContextProvider) null else getContext(withFeatureContexts) }
397400
}
398401
)
399402
.getSafe("getDatadogContext", internalLogger)
@@ -435,12 +438,8 @@ internal class DatadogCore(
435438
)
436439

437440
contextProvider = DatadogContextProvider(coreFeature) {
438-
features.keys.map {
439-
it to {
440-
// already on the context thread
441-
getFeatureContext(it, useContextThread = false)
442-
}
443-
}
441+
// useContextThread = false to infer the caller thread (caller is responsible for the thread selection)
442+
getFeatureContext(it, false)
444443
}
445444

446445
applyAdditionalConfiguration(mutableConfig.additionalConfig)

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,8 @@
77
package com.datadog.android.core.internal
88

99
internal fun interface FeatureContextProvider {
10-
fun getFeaturesContexts(): List<Pair<String, () -> Map<String, Any?>>>
10+
/**
11+
* Returns **frozen** snapshot of the feature context which is not mutated over the time.
12+
*/
13+
fun getFeatureContext(featureName: String): Map<String, Any?>
1114
}

0 commit comments

Comments
 (0)