Skip to content

Commit

Permalink
poc: async feature init
Browse files Browse the repository at this point in the history
  • Loading branch information
fractalwrench committed Jul 9, 2024
1 parent b30685f commit f74be1c
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import io.embrace.android.embracesdk.arch.datasource.DataSourceState
import io.embrace.android.embracesdk.capture.internal.errors.InternalErrorType
import io.embrace.android.embracesdk.config.ConfigService
import io.embrace.android.embracesdk.logging.EmbLogger
import io.embrace.android.embracesdk.worker.BackgroundWorker
import io.embrace.android.embracesdk.worker.TaskPriority
import java.util.concurrent.CopyOnWriteArrayList

/**
Expand All @@ -12,6 +14,7 @@ import java.util.concurrent.CopyOnWriteArrayList
*/
internal class DataCaptureOrchestrator(
configService: ConfigService,
private val worker: BackgroundWorker,
private val logger: EmbLogger
) : EmbraceFeatureRegistry {

Expand All @@ -31,13 +34,17 @@ internal class DataCaptureOrchestrator(

override fun add(state: DataSourceState<*>) {
dataSourceStates.add(state)
state.currentSessionType = currentSessionType
state.dispatchStateChange {
state.currentSessionType = currentSessionType
}
}

private fun onConfigChange() {
dataSourceStates.forEach { state ->
try {
state.onConfigChange()
state.dispatchStateChange {
state.onConfigChange()
}
} catch (exc: Throwable) {
logger.logError("Exception thrown starting data capture", exc)
logger.trackInternalError(InternalErrorType.CFG_CHANGE_DATA_CAPTURE_FAIL, exc)
Expand All @@ -52,11 +59,21 @@ internal class DataCaptureOrchestrator(
dataSourceStates.forEach { state ->
try {
// alter the session type - some data sources don't capture for background activities.
state.currentSessionType = currentSessionType
state.dispatchStateChange {
state.currentSessionType = currentSessionType
}
} catch (exc: Throwable) {
logger.logError("Exception thrown starting data capture", exc)
logger.trackInternalError(InternalErrorType.SESSION_CHANGE_DATA_CAPTURE_FAIL, exc)
}
}
}

private fun DataSourceState<*>.dispatchStateChange(action: () -> Unit) {
if (asyncInit) {
worker.submit(TaskPriority.HIGH, action)
} else {
action()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,16 @@ internal class DataSourceState<T : DataSource<*>>(
* A session type where data capture should be disabled. For example,
* background activities capture a subset of sessions.
*/
private val disabledSessionType: SessionType? = null
private val disabledSessionType: SessionType? = null,

/**
* Whether this feature supports being initialized asynchronously. Defaults to false. If
* the feature is set to true the feature will be initialized on a background thread.
*
* If you enable this behavior please ensure your implementation is thread safe (e.g.
* it can handle unbalanced calls to [enableDataCapture] and others).
*/
val asyncInit: Boolean = false
) {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,11 @@ internal class DataSourceModuleImpl(
private val configService = essentialServiceModule.configService

override val dataCaptureOrchestrator: DataCaptureOrchestrator by singleton {
DataCaptureOrchestrator(configService, initModule.logger)
DataCaptureOrchestrator(
configService,
workerThreadModule.backgroundWorker(WorkerName.BACKGROUND_REGISTRATION),
initModule.logger
)
}

override val embraceFeatureRegistry: EmbraceFeatureRegistry = dataCaptureOrchestrator
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package io.embrace.android.embracesdk.arch

import io.embrace.android.embracesdk.arch.datasource.DataSourceState
import io.embrace.android.embracesdk.concurrency.BlockableExecutorService
import io.embrace.android.embracesdk.fakes.FakeConfigService
import io.embrace.android.embracesdk.fakes.FakeDataSource
import io.embrace.android.embracesdk.fakes.system.mockContext
import io.embrace.android.embracesdk.logging.EmbLoggerImpl
import io.embrace.android.embracesdk.worker.BackgroundWorker
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
Expand All @@ -14,27 +16,35 @@ internal class DataCaptureOrchestratorTest {
private lateinit var orchestrator: DataCaptureOrchestrator
private lateinit var dataSource: FakeDataSource
private lateinit var configService: FakeConfigService
private lateinit var executorService: BlockableExecutorService
private var enabled: Boolean = true

private val syncDataSource = DataSourceState(
factory = { dataSource },
configGate = { enabled }
)

private val asyncDataSource = DataSourceState(
factory = { dataSource },
configGate = { enabled },
asyncInit = true
)

@Before
fun setUp() {
dataSource = FakeDataSource(mockContext())
configService = FakeConfigService()
executorService = BlockableExecutorService()
orchestrator = DataCaptureOrchestrator(
configService,
BackgroundWorker(executorService),
EmbLoggerImpl(),
).apply {
add(
DataSourceState(
factory = { dataSource },
configGate = { enabled }
)
)
}
)
}

@Test
fun `config changes are propagated`() {
orchestrator.add(syncDataSource)
assertEquals(0, dataSource.enableDataCaptureCount)
orchestrator.currentSessionType = SessionType.FOREGROUND
assertEquals(1, dataSource.enableDataCaptureCount)
Expand All @@ -46,8 +56,38 @@ internal class DataCaptureOrchestratorTest {

@Test
fun `session type change is propagated`() {
orchestrator.add(syncDataSource)
assertEquals(0, dataSource.enableDataCaptureCount)
orchestrator.currentSessionType = SessionType.FOREGROUND
assertEquals(1, dataSource.enableDataCaptureCount)
}

@Test
fun `async config change`() {
orchestrator.add(asyncDataSource)
executorService.blockingMode = true

orchestrator.currentSessionType = SessionType.FOREGROUND
assertEquals(0, dataSource.enableDataCaptureCount)
executorService.runNext()
assertEquals(1, dataSource.enableDataCaptureCount)

enabled = false
configService.updateListeners()
assertEquals(0, dataSource.disableDataCaptureCount)
executorService.runNext()
assertEquals(1, dataSource.disableDataCaptureCount)
}

@Test
fun `async session change`() {
orchestrator.add(asyncDataSource)
executorService.blockingMode = true

assertEquals(0, dataSource.enableDataCaptureCount)
orchestrator.currentSessionType = SessionType.FOREGROUND
assertEquals(0, dataSource.enableDataCaptureCount)
executorService.runNext()
assertEquals(1, dataSource.enableDataCaptureCount)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,17 @@ import io.embrace.android.embracesdk.capture.powersave.LowPowerDataSource
import io.embrace.android.embracesdk.capture.session.SessionPropertiesDataSource
import io.embrace.android.embracesdk.capture.thermalstate.ThermalStateDataSource
import io.embrace.android.embracesdk.capture.webview.WebViewDataSource
import io.embrace.android.embracesdk.concurrency.BlockableExecutorService
import io.embrace.android.embracesdk.injection.DataSourceModule
import io.embrace.android.embracesdk.worker.BackgroundWorker

internal class FakeDataSourceModule : DataSourceModule {
override val dataCaptureOrchestrator: DataCaptureOrchestrator =
DataCaptureOrchestrator(FakeConfigService(), FakeEmbLogger())
DataCaptureOrchestrator(
FakeConfigService(),
BackgroundWorker(BlockableExecutorService()),
FakeEmbLogger()
)
override val embraceFeatureRegistry: EmbraceFeatureRegistry = dataCaptureOrchestrator
override val breadcrumbDataSource: DataSourceState<BreadcrumbDataSource> = DataSourceState({ null })
override val viewDataSource: DataSourceState<ViewDataSource> = DataSourceState({ null })
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.embrace.android.embracesdk.fakes

import io.embrace.android.embracesdk.concurrency.BlockableExecutorService
import io.embrace.android.embracesdk.concurrency.BlockingScheduledExecutorService
import io.embrace.android.embracesdk.worker.BackgroundWorker
import io.embrace.android.embracesdk.worker.ScheduledWorker

internal fun fakeBackgroundWorker() = BackgroundWorker(BlockableExecutorService())
internal fun fakeScheduledWorker() = ScheduledWorker(BlockingScheduledExecutorService(blockingMode = false))
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import io.embrace.android.embracesdk.FakeDeliveryService
import io.embrace.android.embracesdk.FakeNdkService
import io.embrace.android.embracesdk.arch.DataCaptureOrchestrator
import io.embrace.android.embracesdk.arch.datasource.DataSourceState
import io.embrace.android.embracesdk.concurrency.BlockableExecutorService
import io.embrace.android.embracesdk.concurrency.BlockingScheduledExecutorService
import io.embrace.android.embracesdk.config.remote.RemoteConfig
import io.embrace.android.embracesdk.config.remote.SessionRemoteConfig
Expand Down Expand Up @@ -33,6 +34,7 @@ import io.embrace.android.embracesdk.session.caching.PeriodicBackgroundActivityC
import io.embrace.android.embracesdk.session.caching.PeriodicSessionCacher
import io.embrace.android.embracesdk.session.message.PayloadFactoryImpl
import io.embrace.android.embracesdk.session.properties.EmbraceSessionProperties
import io.embrace.android.embracesdk.worker.BackgroundWorker
import io.embrace.android.embracesdk.worker.ScheduledWorker
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
Expand Down Expand Up @@ -378,6 +380,7 @@ internal class SessionOrchestratorTest {
fakeDataSource = FakeDataSource(mockContext())
dataCaptureOrchestrator = DataCaptureOrchestrator(
configService,
BackgroundWorker(BlockableExecutorService()),
logger
).apply {
add(
Expand Down

0 comments on commit f74be1c

Please sign in to comment.