@@ -20,11 +20,11 @@ import androidx.core.content.pm.PackageInfoCompat
2020import kotlinx.coroutines.CancellationException
2121import kotlinx.coroutines.CoroutineScope
2222import kotlinx.coroutines.Deferred
23+ import kotlinx.coroutines.GlobalScope
2324import kotlinx.coroutines.Job
2425import kotlinx.coroutines.NonCancellable
2526import kotlinx.coroutines.async
2627import kotlinx.coroutines.launch
27- import kotlinx.coroutines.runBlocking
2828import kotlinx.coroutines.withContext
2929import mozilla.appservices.remotesettings.RemoteSettingsService
3030import mozilla.telemetry.glean.Glean
@@ -41,6 +41,7 @@ import org.mozilla.experiments.nimbus.internal.EnrollmentChangeEvent
4141import org.mozilla.experiments.nimbus.internal.EnrollmentChangeEventType
4242import org.mozilla.experiments.nimbus.internal.EnrollmentStatusExtraDef
4343import org.mozilla.experiments.nimbus.internal.FeatureExposureExtraDef
44+ import org.mozilla.experiments.nimbus.internal.FeatureUpdateDispatcher
4445import org.mozilla.experiments.nimbus.internal.GeckoPrefHandler
4546import org.mozilla.experiments.nimbus.internal.GeckoPrefState
4647import org.mozilla.experiments.nimbus.internal.MalformedFeatureConfigExtraDef
@@ -86,6 +87,8 @@ open class Nimbus(
8687
8788 private val logger = delegate.logger
8889
90+ private val updateDispatcher = FeatureUpdateDispatcher ()
91+
8992 private val metricsHandler = object : MetricsHandler {
9093 override fun recordDatabaseLoad (event : DatabaseLoadExtraDef ) {
9194 NimbusEvents .databaseLoad.record(
@@ -205,6 +208,8 @@ open class Nimbus(
205208 )
206209 }
207210
211+ override fun getFeatureUpdateDispatcher (): FeatureUpdateDispatcher ? = updateDispatcher
212+
208213 // This is currently not available from the main thread.
209214 // see https://jira.mozilla.com/browse/SDK-191
210215 @WorkerThread
@@ -279,7 +284,7 @@ open class Nimbus(
279284 @VisibleForTesting(otherwise = VisibleForTesting .PRIVATE )
280285 internal fun initializeOnThisThread () = withCatchAll(" initialize" ) {
281286 nimbusClient.initialize()
282- postEnrolmentCalculation()
287+ postEnrolmentCalculation(true )
283288 }
284289
285290 override fun fetchExperiments () {
@@ -342,9 +347,15 @@ open class Nimbus(
342347 events = nimbusClient.applyPendingExperiments()
343348 }
344349 NimbusHealth .applyPendingExperimentsTime.accumulateSingleSample(time)
350+
351+ // SAFETY: events is only null at declaration time and is
352+ // immediately assigned a non-null value inside the
353+ // measureTimeMillis lambda.
345354 recordExperimentTelemetryEvents(events!! )
355+
346356 // Get the experiments to record in telemetry
347- postEnrolmentCalculation()
357+ postEnrolmentCalculation(false )
358+ updateDispatcher.notifyChanged(events)
348359 } catch (e: NimbusException .InvalidExperimentFormat ) {
349360 reportError(" Invalid experiment format" , e)
350361 }
@@ -379,11 +390,22 @@ open class Nimbus(
379390 }
380391
381392 @WorkerThread
382- private fun postEnrolmentCalculation () {
383- nimbusClient.getActiveExperiments().let {
384- recordExperimentTelemetry(it )
393+ private fun postEnrolmentCalculation (initial : Boolean ) {
394+ nimbusClient.getActiveExperiments().also { experiments ->
395+ recordExperimentTelemetry(experiments )
385396 updateObserver { observer ->
386- observer.onUpdatesApplied(it)
397+ observer.onUpdatesApplied(experiments)
398+ }
399+
400+ if (initial) {
401+ val featureIds = mutableSetOf<String >()
402+ for (experiment in experiments) {
403+ for (featureId in experiment.featureIds) {
404+ featureIds.add(featureId)
405+ }
406+ }
407+
408+ updateDispatcher.notifyFeatures(featureIds)
387409 }
388410 }
389411 }
@@ -431,7 +453,8 @@ open class Nimbus(
431453 val enrolmentChanges = nimbusClient.setExperimentParticipation(active)
432454 if (enrolmentChanges.isNotEmpty()) {
433455 recordExperimentTelemetryEvents(enrolmentChanges)
434- postEnrolmentCalculation()
456+ postEnrolmentCalculation(false )
457+ updateDispatcher.notifyChanged(enrolmentChanges)
435458 }
436459 }
437460
@@ -442,7 +465,8 @@ open class Nimbus(
442465 val enrolmentChanges = nimbusClient.setRolloutParticipation(active)
443466 if (enrolmentChanges.isNotEmpty()) {
444467 recordExperimentTelemetryEvents(enrolmentChanges)
445- postEnrolmentCalculation()
468+ postEnrolmentCalculation(false )
469+ updateDispatcher.notifyChanged(enrolmentChanges)
446470 }
447471 }
448472
@@ -495,24 +519,35 @@ open class Nimbus(
495519 nimbusClient.optOut(experimentId).also (::recordExperimentTelemetryEvents)
496520 }
497521
522+ @AnyThread
498523 override fun resetTelemetryIdentifiers () {
499524 dbScope.launch {
500- withCatchAll(" resetTelemetryIdentifiers" ) {
501- nimbusClient.resetTelemetryIdentifiers().also { enrollmentChangeEvents ->
502- recordExperimentTelemetryEvents(enrollmentChangeEvents)
503- }
504- }
525+ resetTelemetryIdentifiersOnThisThread()
505526 }
506527 }
507528
529+ @WorkerThread
530+ @VisibleForTesting(otherwise = VisibleForTesting .PRIVATE )
531+ internal fun resetTelemetryIdentifiersOnThisThread () = withCatchAll(" resetTelemetryIdentifiers" ) {
532+ nimbusClient.resetTelemetryIdentifiers().also { enrollmentChangeEvents ->
533+ recordExperimentTelemetryEvents(enrollmentChangeEvents)
534+ updateDispatcher.notifyChanged(enrollmentChangeEvents)
535+ }
536+ }
537+
538+ @AnyThread
508539 override fun optInWithBranch (experimentId : String , branch : String ) {
509540 dbScope.launch {
510- withCatchAll(" optIn" ) {
511- nimbusClient.optInWithBranch(experimentId, branch).also (::recordExperimentTelemetryEvents)
512- }
541+ optInWithBranchOnThisThread(experimentId, branch)
513542 }
514543 }
515544
545+ @WorkerThread
546+ @VisibleForTesting(otherwise = VisibleForTesting .PRIVATE )
547+ internal fun optInWithBranchOnThisThread (experimentId : String , branch : String ) = withCatchAll(" optIn" ) {
548+ nimbusClient.optInWithBranch(experimentId, branch).also (::recordExperimentTelemetryEvents)
549+ }
550+
516551 override fun recordExposureEvent (featureId : String , experimentSlug : String? ) {
517552 recordExposureOnThisThread(featureId, experimentSlug)
518553 }
0 commit comments