Skip to content

Commit

Permalink
Adjustable polling (#101)
Browse files Browse the repository at this point in the history
  • Loading branch information
nitinsampath authored Oct 4, 2023
1 parent 6949418 commit 998a10c
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.joinforage.forage.android.core.StopgapGlobalState
import com.joinforage.forage.android.core.telemetry.Log
import com.launchdarkly.sdk.ContextKind
import com.launchdarkly.sdk.LDContext
import com.launchdarkly.sdk.LDValue
import com.launchdarkly.sdk.android.LDClient
import com.launchdarkly.sdk.android.LDConfig
import com.launchdarkly.sdk.android.integrations.TestData
Expand All @@ -20,6 +21,7 @@ internal enum class VaultType(val value: String) {

internal object LDFlags {
const val VAULT_PRIMARY_TRAFFIC_PERCENTAGE_FLAG = "vault-primary-traffic-percentage"
const val ISO_POLLING_WAIT_INTERVALS = "iso-polling-wait-intervals"
}

internal object LDContexts {
Expand All @@ -33,7 +35,9 @@ internal object LDContextKind {
internal object LDManager {
private val LD_MOBILE_KEY = StopgapGlobalState.envConfig.ldMobileKey
private var internalVaultType: VaultType? = null

private var internalLogger: Log = Log.getSilentInstance()
private var client: LDClient? = null

internal var vaultType: VaultType?
get() = internalVaultType
Expand All @@ -51,15 +55,7 @@ internal object LDManager {
}
}

// vaultType is instantiated lazily and is a singleton. Once we set the vault type once, we don't
// want to overwrite it! We must take in the application as a parameter, which means that a
// ForagePINEditText must be rendered before any of the ForageSDKApi functions are called.
internal fun getVaultProvider(app: Application, logger: Log, dataSource: TestData? = null): VaultType {
if (vaultType != null) {
return vaultType as VaultType
}
internalLogger = logger
// Datasource is required for testing purposes!
internal fun initialize(app: Application, logger: Log, dataSource: TestData? = null) {
val ldConfig = if (dataSource != null) {
LDConfig.Builder()
.mobileKey(LD_MOBILE_KEY)
Expand All @@ -72,11 +68,25 @@ internal object LDManager {
}
val contextKind = ContextKind.of(LDContextKind.SERVICE)
val context = LDContext.create(contextKind, LDContexts.ANDROID_CONTEXT)
val client = LDClient.init(app, ldConfig, context, 0)
client = LDClient.init(app, ldConfig, context, 0)

internalLogger = logger
}

// vaultType is instantiated lazily and is a singleton. Once we set the vault type once, we don't
// want to overwrite it! We must take in the application as a parameter, which means that a
// ForagePINEditText must be rendered before any of the ForageSDKApi functions are called.
internal fun getVaultProvider(): VaultType {
if (vaultType != null) {
return vaultType as VaultType
}
// default to 100% VGS usage in case LD flag retrieval fails
val vaultPercent = client.doubleVariation(LDFlags.VAULT_PRIMARY_TRAFFIC_PERCENTAGE_FLAG, 0.0)
val defaultVal = 0.0
val vaultPercent =
client?.doubleVariation(LDFlags.VAULT_PRIMARY_TRAFFIC_PERCENTAGE_FLAG, defaultVal) ?: defaultVal
internalLogger.i("[LaunchDarkly] Vault percent of $vaultPercent return from LD")
val randomNum = Math.random() * 100

vaultType = if (randomNum < vaultPercent) {
VaultType.BT_VAULT_TYPE
} else {
Expand All @@ -85,4 +95,21 @@ internal object LDManager {
internalLogger.i("[LaunchDarkly] Vault type set to $vaultType")
return vaultType as VaultType
}

internal fun getPollingIntervals(): LongArray {
val defaultVal = LDValue.buildObject().put(
"intervals",
LDValue.Convert.Long.arrayFrom(List(10) { 1000L })
).build()

val jsonIntervals = client?.jsonValueVariation(LDFlags.ISO_POLLING_WAIT_INTERVALS, defaultVal) ?: defaultVal

val intervals = jsonIntervals.get("intervals")

// Converting the LDArray into a LongArray
val pollingList = LongArray(intervals.size()) { intervals.get(it).longValue() }

internalLogger.i("[LaunchDarkly] polling intervals $pollingList")
return pollingList
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.joinforage.forage.android.network.data

import com.joinforage.forage.android.LDManager
import com.joinforage.forage.android.collect.PinCollector
import com.joinforage.forage.android.core.telemetry.Log
import com.joinforage.forage.android.getJitterAmount
Expand Down Expand Up @@ -91,6 +92,7 @@ internal class CapturePaymentRepository(
paymentRef: String
): ForageApiResponse<String> {
var attempt = 1
val pollingIntervals = LDManager.getPollingIntervals()

while (true) {
logger.i(
Expand Down Expand Up @@ -151,8 +153,15 @@ internal class CapturePaymentRepository(
return ForageApiResponse.Failure(listOf(ForageError(500, "unknown_server_error", "Unknown Server Error")))
}

val index = attempt - 1
var intervalTime: Long = if (index < pollingIntervals.size) {
pollingIntervals[index]
} else {
1000L
}

attempt += 1
delay(POLLING_INTERVAL_IN_MILLIS + getJitterAmount())
delay(intervalTime + getJitterAmount())
}

logger.i(
Expand All @@ -167,7 +176,6 @@ internal class CapturePaymentRepository(
}

companion object {
private const val POLLING_INTERVAL_IN_MILLIS = 1000L
private const val MAX_ATTEMPTS = 10

private fun ForageApiResponse<String>.getStringResponse() = when (this) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.joinforage.forage.android.network.data

import com.joinforage.forage.android.LDManager
import com.joinforage.forage.android.collect.PinCollector
import com.joinforage.forage.android.core.telemetry.Log
import com.joinforage.forage.android.getJitterAmount
Expand Down Expand Up @@ -74,6 +75,7 @@ internal class CheckBalanceRepository(
paymentMethodRef: String
): ForageApiResponse<String> {
var attempt = 1
val pollingIntervals = LDManager.getPollingIntervals()

while (true) {
logger.i(
Expand Down Expand Up @@ -134,8 +136,15 @@ internal class CheckBalanceRepository(
return ForageApiResponse.Failure(listOf(ForageError(500, "unknown_server_error", "Unknown Server Error")))
}

val index = attempt - 1
var intervalTime: Long = if (index < pollingIntervals.size) {
pollingIntervals[index]
} else {
1000L
}

attempt += 1
delay(POLLING_INTERVAL_IN_MILLIS + getJitterAmount())
delay(intervalTime + getJitterAmount())
}

logger.i(
Expand Down Expand Up @@ -163,7 +172,6 @@ internal class CheckBalanceRepository(
}

companion object {
private const val POLLING_INTERVAL_IN_MILLIS = 1000L
private const val MAX_ATTEMPTS = 10

private fun ForageApiResponse<String>.getStringResponse() = when (this) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,10 @@ class ForagePINEditText @JvmOverloads constructor(
// its relying on forageConfig under the hood. This
// relationship will be made explicit once we drop
// StopgapGlobalState
val vaultType = LDManager.getVaultProvider(context.applicationContext as Application, logger)

LDManager.initialize(context.applicationContext as Application, logger)
val vaultType = LDManager.getVaultProvider()

_SET_ONLY_vault = if (vaultType == VaultType.BT_VAULT_TYPE) {
btVaultWrapper
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,15 @@ class LaunchDarklyTest() {
// Set the test data to send all traffic to BT
td.update(td.flag(LDFlags.VAULT_PRIMARY_TRAFFIC_PERCENTAGE_FLAG).variations(LDValue.of(alwaysBT)))
val app = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as Application
val vaultType = LDManager.getVaultProvider(app, logger = Log.getSilentInstance(), dataSource = td)
LDManager.initialize(app, logger = Log.getSilentInstance(), dataSource = td)
val vaultType = LDManager.getVaultProvider()
assertThat(vaultType).isEqualTo(VaultType.BT_VAULT_TYPE)

// Update the test data to send all traffic to VGS
// Since ForageSDK is a singleton, we should still return BT in this instance
td.update(td.flag(LDFlags.VAULT_PRIMARY_TRAFFIC_PERCENTAGE_FLAG).variations(LDValue.of(alwaysVGS)))
val secondVaultType = LDManager.getVaultProvider(app, logger = Log.getSilentInstance(), dataSource = td)

val secondVaultType = LDManager.getVaultProvider()
assertThat(secondVaultType).isEqualTo(VaultType.BT_VAULT_TYPE)
}

Expand All @@ -57,13 +59,27 @@ class LaunchDarklyTest() {
// Set the test data to send all traffic to VGS
td.update(td.flag(LDFlags.VAULT_PRIMARY_TRAFFIC_PERCENTAGE_FLAG).variations(LDValue.of(alwaysVGS)))
val app = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as Application
val vaultType = LDManager.getVaultProvider(app, logger = Log.getSilentInstance(), dataSource = td)

LDManager.initialize(app, logger = Log.getSilentInstance(), dataSource = td)
val vaultType = LDManager.getVaultProvider()
assertThat(vaultType).isEqualTo(VaultType.VGS_VAULT_TYPE)

// Update the test data to send all traffic to BT
// Since ForageSDK is a singleton, we should still return VGS in this instance
td.update(td.flag(LDFlags.VAULT_PRIMARY_TRAFFIC_PERCENTAGE_FLAG).variations(LDValue.of(alwaysBT)))
val secondVaultType = LDManager.getVaultProvider(app, logger = Log.getSilentInstance(), dataSource = td)

val secondVaultType = LDManager.getVaultProvider()
assertThat(secondVaultType).isEqualTo(VaultType.VGS_VAULT_TYPE)
}

@Test
fun `Default polling intervals`() = runTest {
// Set the test data to be {"intervals" : [1000]}
td.update(td.flag(LDFlags.ISO_POLLING_WAIT_INTERVALS).variations(LDValue.buildObject().put("intervals", LDValue.buildArray().add(1000L).build()).build()))
val app = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as Application

LDManager.initialize(app, logger = Log.getSilentInstance(), dataSource = td)
val pollingIntervals = LDManager.getPollingIntervals()
assertThat(pollingIntervals).isEqualTo(longArrayOf(1000L))
}
}

0 comments on commit 998a10c

Please sign in to comment.