Skip to content

Commit

Permalink
RUM-6307 Make sure ConsentAwareFileOrchestrator is thread safe
Browse files Browse the repository at this point in the history
  • Loading branch information
mariusc83 committed Oct 11, 2024
1 parent 7ebe827 commit 428bee9
Show file tree
Hide file tree
Showing 10 changed files with 712 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,9 @@ internal class SdkFeature(
fileMover = FileMover(internalLogger),
internalLogger = internalLogger,
filePersistenceConfig = filePersistenceConfig,
metricsDispatcher = metricsDispatcher
metricsDispatcher = metricsDispatcher,
coreFeature.trackingConsentProvider,
featureName
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ internal class AbstractStorage(
private val executorService: ExecutorService,
private val internalLogger: InternalLogger,
internal val storageConfiguration: FeatureStorageConfiguration,
consentProvider: ConsentProvider
private val consentProvider: ConsentProvider
) : Storage, TrackingConsentProviderCallback {

private val grantedPersistenceStrategy: PersistenceStrategy by lazy {
Expand Down Expand Up @@ -64,13 +64,8 @@ internal class AbstractStorage(
forceNewBatch: Boolean,
callback: (EventBatchWriter) -> Unit
) {
val strategy = when (datadogContext.trackingConsent) {
TrackingConsent.GRANTED -> grantedPersistenceStrategy
TrackingConsent.PENDING -> pendingPersistenceStrategy
TrackingConsent.NOT_GRANTED -> notGrantedPersistenceStrategy
}

executorService.submitSafe("Data write", internalLogger) {
val strategy = resolvePersistenceStrategy()
val writer = object : EventBatchWriter {
@WorkerThread
override fun currentMetadata(): ByteArray? {
Expand All @@ -86,6 +81,14 @@ internal class AbstractStorage(
}
}

@WorkerThread
private fun resolvePersistenceStrategy() =
when (consentProvider.getConsent()) {
TrackingConsent.GRANTED -> grantedPersistenceStrategy
TrackingConsent.PENDING -> pendingPersistenceStrategy
TrackingConsent.NOT_GRANTED -> notGrantedPersistenceStrategy
}

@WorkerThread
override fun readNextBatch(): BatchData? {
return grantedPersistenceStrategy.lockAndReadNext()?.let {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import com.datadog.android.core.internal.persistence.file.FilePersistenceConfig
import com.datadog.android.core.internal.persistence.file.FileReaderWriter
import com.datadog.android.core.internal.persistence.file.batch.BatchFileReaderWriter
import com.datadog.android.core.internal.persistence.file.existsSafe
import com.datadog.android.core.internal.privacy.ConsentProvider
import com.datadog.android.core.internal.utils.submitSafe
import com.datadog.android.core.metrics.MethodCallSamplingRate
import com.datadog.android.core.metrics.TelemetryMetricType
Expand All @@ -36,7 +37,9 @@ internal class ConsentAwareStorage(
private val fileMover: FileMover,
private val internalLogger: InternalLogger,
internal val filePersistenceConfig: FilePersistenceConfig,
private val metricsDispatcher: MetricsDispatcher
private val metricsDispatcher: MetricsDispatcher,
private val consentProvider: ConsentProvider,
private val featureName: String
) : Storage {

/**
Expand All @@ -53,20 +56,14 @@ internal class ConsentAwareStorage(
forceNewBatch: Boolean,
callback: (EventBatchWriter) -> Unit
) {
val orchestrator = when (datadogContext.trackingConsent) {
TrackingConsent.GRANTED -> grantedOrchestrator
TrackingConsent.PENDING -> pendingOrchestrator
TrackingConsent.NOT_GRANTED -> null
}

val metric = internalLogger.startPerformanceMeasure(
callerClass = ConsentAwareStorage::class.java.name,
metric = TelemetryMetricType.MethodCalled,
samplingRate = MethodCallSamplingRate.RARE.rate,
operationName = "writeCurrentBatch[${orchestrator?.getRootDirName()}]"
operationName = "writeCurrentBatch[$featureName]"
)

executorService.submitSafe("Data write", internalLogger) {
val orchestrator = resolveOrchestrator()
synchronized(writeLock) {
val batchFile = orchestrator?.getWritableFile(forceNewBatch)
val metadataFile = if (batchFile != null) {
Expand Down Expand Up @@ -153,6 +150,16 @@ internal class ConsentAwareStorage(
}
}

@WorkerThread
private fun resolveOrchestrator(): FileOrchestrator? {
val consent = consentProvider.getConsent()
return when (consent) {
TrackingConsent.GRANTED -> grantedOrchestrator
TrackingConsent.PENDING -> pendingOrchestrator
TrackingConsent.NOT_GRANTED -> null
}
}

@WorkerThread
private fun deleteBatch(batch: Batch, reason: RemovalReason) {
deleteBatch(batch.file, batch.metaFile, reason)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ internal open class ConsentAwareFileOrchestrator(
internal val internalLogger: InternalLogger
) : FileOrchestrator, TrackingConsentProviderCallback {

@Volatile
private lateinit var delegateOrchestrator: FileOrchestrator

init {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ internal class AbstractStorageTest {
@Forgery fakeBatchEvent: RawBatchEvent
) {
// Given
val sdkContext = fakeDatadogContext.copy(trackingConsent = TrackingConsent.GRANTED)
whenever(mockConsentProvider.getConsent()) doReturn TrackingConsent.GRANTED
val mockWriteCallback = mock<(EventBatchWriter) -> Unit>()
whenever(mockGrantedPersistenceStrategy.write(any(), anyOrNull(), any())) doReturn fakeResult
var result: Boolean? = null
Expand All @@ -118,7 +118,7 @@ internal class AbstractStorageTest {
}

// When
testedStorage.writeCurrentBatch(sdkContext, forceNewBatch, mockWriteCallback)
testedStorage.writeCurrentBatch(fakeDatadogContext, forceNewBatch, mockWriteCallback)

// Then
assertThat(result).isEqualTo(fakeResult)
Expand All @@ -138,7 +138,7 @@ internal class AbstractStorageTest {
@StringForgery fakeBatchMetadata: String
) {
// Given
val sdkContext = fakeDatadogContext.copy(trackingConsent = TrackingConsent.GRANTED)
whenever(mockConsentProvider.getConsent()) doReturn TrackingConsent.GRANTED
val mockWriteCallback = mock<(EventBatchWriter) -> Unit>()
val batchMetadata = fakeBatchMetadata.toByteArray()
whenever(mockGrantedPersistenceStrategy.write(any(), anyOrNull(), any())) doReturn fakeResult
Expand All @@ -148,7 +148,7 @@ internal class AbstractStorageTest {
}

// When
testedStorage.writeCurrentBatch(sdkContext, forceNewBatch, mockWriteCallback)
testedStorage.writeCurrentBatch(fakeDatadogContext, forceNewBatch, mockWriteCallback)

// Then
assertThat(result).isEqualTo(fakeResult)
Expand All @@ -165,7 +165,8 @@ internal class AbstractStorageTest {
@BoolForgery forceNewBatch: Boolean
) {
// Given
val sdkContext = fakeDatadogContext.copy(trackingConsent = TrackingConsent.GRANTED)
whenever(mockConsentProvider.getConsent()) doReturn TrackingConsent.GRANTED
whenever(mockGrantedPersistenceStrategy.currentMetadata()) doReturn null
val mockWriteCallback = mock<(EventBatchWriter) -> Unit>()
whenever(mockGrantedPersistenceStrategy.currentMetadata()) doReturn null
var resultMetadata: ByteArray? = null
Expand All @@ -174,7 +175,7 @@ internal class AbstractStorageTest {
}

// When
testedStorage.writeCurrentBatch(sdkContext, forceNewBatch, mockWriteCallback)
testedStorage.writeCurrentBatch(fakeDatadogContext, forceNewBatch, mockWriteCallback)

// Then
assertThat(resultMetadata).isNull()
Expand All @@ -192,7 +193,7 @@ internal class AbstractStorageTest {
@StringForgery fakeBatchMetadata: String
) {
// Given
val sdkContext = fakeDatadogContext.copy(trackingConsent = TrackingConsent.GRANTED)
whenever(mockConsentProvider.getConsent()) doReturn TrackingConsent.GRANTED
val mockWriteCallback = mock<(EventBatchWriter) -> Unit>()
val batchMetadata = fakeBatchMetadata.toByteArray()
whenever(mockGrantedPersistenceStrategy.currentMetadata()) doReturn batchMetadata
Expand All @@ -202,7 +203,7 @@ internal class AbstractStorageTest {
}

// When
testedStorage.writeCurrentBatch(sdkContext, forceNewBatch, mockWriteCallback)
testedStorage.writeCurrentBatch(fakeDatadogContext, forceNewBatch, mockWriteCallback)

// Then
assertThat(resultMetadata).isEqualTo(batchMetadata)
Expand All @@ -221,7 +222,7 @@ internal class AbstractStorageTest {
@Forgery fakeBatchEvent: RawBatchEvent
) {
// Given
val sdkContext = fakeDatadogContext.copy(trackingConsent = TrackingConsent.PENDING)
whenever(mockConsentProvider.getConsent()) doReturn TrackingConsent.PENDING
val mockWriteCallback = mock<(EventBatchWriter) -> Unit>()
whenever(mockPendingPersistenceStrategy.write(any(), anyOrNull(), any())) doReturn fakeResult
var result: Boolean? = null
Expand All @@ -230,7 +231,7 @@ internal class AbstractStorageTest {
}

// When
testedStorage.writeCurrentBatch(sdkContext, forceNewBatch, mockWriteCallback)
testedStorage.writeCurrentBatch(fakeDatadogContext, forceNewBatch, mockWriteCallback)

// Then
assertThat(result).isEqualTo(fakeResult)
Expand All @@ -250,7 +251,7 @@ internal class AbstractStorageTest {
@StringForgery fakeBatchMetadata: String
) {
// Given
val sdkContext = fakeDatadogContext.copy(trackingConsent = TrackingConsent.PENDING)
whenever(mockConsentProvider.getConsent()) doReturn TrackingConsent.PENDING
val mockWriteCallback = mock<(EventBatchWriter) -> Unit>()
val batchMetadata = fakeBatchMetadata.toByteArray()
whenever(mockPendingPersistenceStrategy.write(any(), anyOrNull(), any())) doReturn fakeResult
Expand All @@ -260,7 +261,7 @@ internal class AbstractStorageTest {
}

// When
testedStorage.writeCurrentBatch(sdkContext, forceNewBatch, mockWriteCallback)
testedStorage.writeCurrentBatch(fakeDatadogContext, forceNewBatch, mockWriteCallback)

// Then
assertThat(result).isEqualTo(fakeResult)
Expand All @@ -277,7 +278,7 @@ internal class AbstractStorageTest {
@BoolForgery forceNewBatch: Boolean
) {
// Given
val sdkContext = fakeDatadogContext.copy(trackingConsent = TrackingConsent.PENDING)
whenever(mockConsentProvider.getConsent()) doReturn TrackingConsent.PENDING
val mockWriteCallback = mock<(EventBatchWriter) -> Unit>()
var resultMetadata: ByteArray? = null
whenever(mockWriteCallback.invoke(any())) doAnswer {
Expand All @@ -286,7 +287,7 @@ internal class AbstractStorageTest {
whenever(mockPendingPersistenceStrategy.currentMetadata()) doReturn null

// When
testedStorage.writeCurrentBatch(sdkContext, forceNewBatch, mockWriteCallback)
testedStorage.writeCurrentBatch(fakeDatadogContext, forceNewBatch, mockWriteCallback)

// Then
assertThat(resultMetadata).isNull()
Expand All @@ -304,7 +305,7 @@ internal class AbstractStorageTest {
@StringForgery fakeBatchMetadata: String
) {
// Given
val sdkContext = fakeDatadogContext.copy(trackingConsent = TrackingConsent.PENDING)
whenever(mockConsentProvider.getConsent()) doReturn TrackingConsent.PENDING
val mockWriteCallback = mock<(EventBatchWriter) -> Unit>()
val batchMetadata = fakeBatchMetadata.toByteArray()
var resultMetadata: ByteArray? = null
Expand All @@ -314,7 +315,7 @@ internal class AbstractStorageTest {
whenever(mockPendingPersistenceStrategy.currentMetadata()) doReturn batchMetadata

// When
testedStorage.writeCurrentBatch(sdkContext, forceNewBatch, mockWriteCallback)
testedStorage.writeCurrentBatch(fakeDatadogContext, forceNewBatch, mockWriteCallback)

// Then
assertThat(resultMetadata).isEqualTo(batchMetadata)
Expand All @@ -333,7 +334,7 @@ internal class AbstractStorageTest {
@StringForgery fakeBatchMetadata: String
) {
// Given
val sdkContext = fakeDatadogContext.copy(trackingConsent = TrackingConsent.NOT_GRANTED)
whenever(mockConsentProvider.getConsent()) doReturn TrackingConsent.NOT_GRANTED
val mockWriteCallback = mock<(EventBatchWriter) -> Unit>()
val batchMetadata = fakeBatchMetadata.toByteArray()
var result: Boolean? = null
Expand All @@ -342,7 +343,7 @@ internal class AbstractStorageTest {
}

// When
testedStorage.writeCurrentBatch(sdkContext, forceNewBatch, mockWriteCallback)
testedStorage.writeCurrentBatch(fakeDatadogContext, forceNewBatch, mockWriteCallback)

// Then
assertThat(result).isFalse()
Expand All @@ -358,15 +359,15 @@ internal class AbstractStorageTest {
@BoolForgery forceNewBatch: Boolean
) {
// Given
val sdkContext = fakeDatadogContext.copy(trackingConsent = TrackingConsent.NOT_GRANTED)
whenever(mockConsentProvider.getConsent()) doReturn TrackingConsent.NOT_GRANTED
val mockWriteCallback = mock<(EventBatchWriter) -> Unit>()
var resultMetadata: ByteArray? = null
whenever(mockWriteCallback.invoke(any())) doAnswer {
resultMetadata = (it.getArgument(0) as? EventBatchWriter)?.currentMetadata()
}

// When
testedStorage.writeCurrentBatch(sdkContext, forceNewBatch, mockWriteCallback)
testedStorage.writeCurrentBatch(fakeDatadogContext, forceNewBatch, mockWriteCallback)

// Then
assertThat(resultMetadata).isNull()
Expand Down
Loading

0 comments on commit 428bee9

Please sign in to comment.