@@ -4,14 +4,13 @@ import com.google.gson.Gson
4
4
import com.google.gson.JsonSyntaxException
5
5
import com.google.gson.reflect.TypeToken
6
6
import com.statsig.sdk.network.StatsigTransport
7
- import kotlinx.coroutines.CoroutineScope
8
- import kotlinx.coroutines.Job
9
- import kotlinx.coroutines.delay
7
+ import kotlinx.coroutines.*
10
8
import kotlinx.coroutines.flow.Flow
11
9
import kotlinx.coroutines.flow.filterNotNull
12
10
import kotlinx.coroutines.flow.flow
13
11
import kotlinx.coroutines.flow.map
14
- import kotlinx.coroutines.launch
12
+
13
+ private const val HEALTH_CHECK_INTERVAL_MS = 60_000L
15
14
16
15
internal class SpecUpdater (
17
16
private var transport : StatsigTransport ,
@@ -25,6 +24,7 @@ internal class SpecUpdater(
25
24
) {
26
25
var lastUpdateTime: Long = 0
27
26
27
+ private val monitorScope = CoroutineScope (SupervisorJob () + Dispatchers .IO )
28
28
private var configSpecCallback: suspend (config: APIDownloadedConfigs , source: DataSource ) -> Unit = { _, _ -> }
29
29
private var idListCallback: suspend (config: Map <String , IDList >) -> Unit = { }
30
30
private var backgroundDownloadConfigs: Job ? = null
@@ -56,36 +56,28 @@ internal class SpecUpdater(
56
56
}
57
57
58
58
fun startListening () {
59
- if (backgroundDownloadConfigs == null ) {
60
- logger.debug(" [StatsigSpecUpdater] Initializing new background polling job" )
61
- val flow = if (transport.downloadConfigSpecWorker.isPullWorker) {
62
- logger.debug(" [StatsigSpecUpdater] Using pull worker for config specs syncing" )
63
- pollForConfigSpecs()
64
- } else {
65
- logger.debug(" [StatsigSpecUpdater] Using streaming for config specs syncing." )
66
- transport.configSpecsFlow().map(::parseConfigSpecs).map { Pair (it.first, DataSource .NETWORK ) }
67
- }
59
+ startBackgroundDcsPolling()
60
+ startBackgroundIDListPolling()
61
+ startPeriodicHealthCheck()
62
+ }
68
63
69
- backgroundDownloadConfigs = statsigScope.launch {
70
- flow.collect { response ->
71
- val spec = response.first
72
- spec?.let {
73
- configSpecCallback(spec, response.second)
74
- }
64
+ private fun startPeriodicHealthCheck () {
65
+ monitorScope.launch {
66
+ while (true ) {
67
+ delay(HEALTH_CHECK_INTERVAL_MS ) // Check every 60 seconds by default
68
+
69
+ if (backgroundDownloadConfigs?.isActive != true ) {
70
+ logger.debug(" [StatsigPeriodicHealthCheck] Background polling is inactive. Restarting..." )
71
+ startBackgroundDcsPolling()
75
72
}
76
- }
77
- }
78
- if (backgroundDownloadIDLists == null ) {
79
- val idListFlow = if (transport.getIDListsWorker.isPullWorker) {
80
- pollForIDLists()
81
- } else transport.idListsFlow().map(::parseIDLists).filterNotNull()
82
73
83
- backgroundDownloadIDLists = statsigScope.launch {
84
- idListFlow.collect { idListCallback(it) }
74
+ if (backgroundDownloadIDLists?.isActive != true ) {
75
+ logger.debug(" [StatsigPeriodicHealthCheck] ID list polling is inactive. Restarting..." )
76
+ startBackgroundIDListPolling()
77
+ }
85
78
}
86
79
}
87
80
}
88
-
89
81
fun getInitializeOrder (): List <DataSource > {
90
82
val optionsOrder = options.initializeSources
91
83
if (optionsOrder != null ) return optionsOrder
@@ -149,6 +141,49 @@ internal class SpecUpdater(
149
141
return parseConfigsFromNetwork(this .transport.downloadConfigSpecs(this .lastUpdateTime))
150
142
}
151
143
144
+ /* *
145
+ * Starts background polling for config specs.
146
+ * If already running, it will not create a duplicate.
147
+ */
148
+ private fun startBackgroundDcsPolling () {
149
+ if (backgroundDownloadConfigs?.isActive == true ) {
150
+ return
151
+ }
152
+
153
+ logger.debug(" [StatsigSpecUpdater] Initializing new background polling job" )
154
+
155
+ val flow = if (transport.downloadConfigSpecWorker.isPullWorker) {
156
+ logger.debug(" [StatsigSpecUpdater] Using pull worker for config specs syncing" )
157
+ pollForConfigSpecs()
158
+ } else {
159
+ logger.debug(" [StatsigSpecUpdater] Using streaming for config specs syncing." )
160
+ transport.configSpecsFlow().map(::parseConfigSpecs).map { Pair (it.first, DataSource .NETWORK ) }
161
+ }
162
+
163
+ backgroundDownloadConfigs = statsigScope.launch {
164
+ flow.collect { response ->
165
+ val spec = response.first
166
+ spec?.let {
167
+ configSpecCallback(spec, response.second)
168
+ }
169
+ }
170
+ }
171
+ }
172
+
173
+ private fun startBackgroundIDListPolling () {
174
+ if (backgroundDownloadIDLists?.isActive == true ) {
175
+ return
176
+ }
177
+
178
+ val idListFlow = if (transport.getIDListsWorker.isPullWorker) {
179
+ pollForIDLists()
180
+ } else transport.idListsFlow().map(::parseIDLists).filterNotNull()
181
+
182
+ backgroundDownloadIDLists = statsigScope.launch {
183
+ idListFlow.collect { idListCallback(it) }
184
+ }
185
+ }
186
+
152
187
private fun parseConfigSpecs (specs : String? ): Pair <APIDownloadedConfigs ?, FailureDetails ?> {
153
188
if (specs.isNullOrEmpty()) {
154
189
return Pair (null , FailureDetails (FailureReason .EMPTY_SPEC ))
0 commit comments