-
Notifications
You must be signed in to change notification settings - Fork 368
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2168 from OneSignal/read-your-write
Read-Your-Write Consistency
- Loading branch information
Showing
25 changed files
with
2,130 additions
and
1,432 deletions.
There are no files selected for viewing
41 changes: 41 additions & 0 deletions
41
...K/onesignal/core/src/main/java/com/onesignal/common/consistency/IamFetchReadyCondition.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package com.onesignal.common.consistency | ||
|
||
import com.onesignal.common.consistency.enums.IamFetchRywTokenKey | ||
import com.onesignal.common.consistency.models.ICondition | ||
import com.onesignal.common.consistency.models.IConsistencyKeyEnum | ||
|
||
/** | ||
* Used for read your write consistency when fetching In-App Messages. | ||
* | ||
* Params: | ||
* key : String - the index of the RYW token map | ||
*/ | ||
class IamFetchReadyCondition( | ||
private val key: String, | ||
) : ICondition { | ||
companion object { | ||
const val ID = "IamFetchReadyCondition" | ||
} | ||
|
||
override val id: String | ||
get() = ID | ||
|
||
override fun isMet(indexedTokens: Map<String, Map<IConsistencyKeyEnum, String>>): Boolean { | ||
val tokenMap = indexedTokens[key] ?: return false | ||
val userUpdateTokenSet = tokenMap[IamFetchRywTokenKey.USER] != null | ||
|
||
/** | ||
* We always update the session count so we know we will have a userUpdateToken. We don't | ||
* necessarily make a subscriptionUpdate call on every session. The following logic | ||
* doesn't consider tokenMap[IamFetchRywTokenKey.SUBSCRIPTION] for this reason. This doesn't | ||
* mean it isn't considered if present when doing the token comparison. | ||
*/ | ||
return userUpdateTokenSet | ||
} | ||
|
||
override fun getNewestToken(indexedTokens: Map<String, Map<IConsistencyKeyEnum, String?>>): String? { | ||
val tokenMap = indexedTokens[key] ?: return null | ||
// maxOrNull compares lexicographically | ||
return listOfNotNull(tokenMap[IamFetchRywTokenKey.USER], tokenMap[IamFetchRywTokenKey.SUBSCRIPTION]).maxOrNull() | ||
} | ||
} |
12 changes: 12 additions & 0 deletions
12
...nesignal/core/src/main/java/com/onesignal/common/consistency/enums/IamFetchRywTokenKey.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package com.onesignal.common.consistency.enums | ||
|
||
import com.onesignal.common.consistency.models.IConsistencyKeyEnum | ||
|
||
/** | ||
* Each enum is a key that we use to keep track of read-your-write tokens. | ||
* Although the enums are named with "UPDATE", they serve as keys for tokens from both PATCH & POST | ||
*/ | ||
enum class IamFetchRywTokenKey : IConsistencyKeyEnum { | ||
USER, | ||
SUBSCRIPTION, | ||
} |
92 changes: 92 additions & 0 deletions
92
.../onesignal/core/src/main/java/com/onesignal/common/consistency/impl/ConsistencyManager.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package com.onesignal.common.consistency.impl | ||
|
||
import com.onesignal.common.consistency.models.ICondition | ||
import com.onesignal.common.consistency.models.IConsistencyKeyEnum | ||
import com.onesignal.common.consistency.models.IConsistencyManager | ||
import kotlinx.coroutines.CompletableDeferred | ||
import kotlinx.coroutines.sync.Mutex | ||
import kotlinx.coroutines.sync.withLock | ||
|
||
/** | ||
* Manages read-your-write tokens for more accurate segment membership | ||
* calculation. Uses customizable conditions that block retrieval of the newest token until met. | ||
* | ||
* Usage: | ||
* val consistencyManager = ConsistencyManager<MyEnum>() | ||
* val updateConditionDeferred = consistencyManager.registerCondition(MyCustomCondition()) | ||
* val rywToken = updateConditionDeferred.await() | ||
*/ | ||
class ConsistencyManager : IConsistencyManager { | ||
private val mutex = Mutex() | ||
private val indexedTokens: MutableMap<String, MutableMap<IConsistencyKeyEnum, String>> = mutableMapOf() | ||
private val conditions: MutableList<Pair<ICondition, CompletableDeferred<String?>>> = | ||
mutableListOf() | ||
|
||
/** | ||
* Set method to update the token based on the key. | ||
* Params: | ||
* id: String - the index of the token map (e.g. onesignalId) | ||
* key: K - corresponds to the operation for which we have a read-your-write token | ||
* value: String? - the token (read-your-write token) | ||
*/ | ||
override suspend fun setRywToken( | ||
id: String, | ||
key: IConsistencyKeyEnum, | ||
value: String, | ||
) { | ||
mutex.withLock { | ||
val rywTokens = indexedTokens.getOrPut(id) { mutableMapOf() } | ||
rywTokens[key] = value | ||
checkConditionsAndComplete() | ||
} | ||
} | ||
|
||
/** | ||
* Register a condition with its corresponding deferred action. Returns a deferred condition. | ||
*/ | ||
override suspend fun registerCondition(condition: ICondition): CompletableDeferred<String?> { | ||
mutex.withLock { | ||
val deferred = CompletableDeferred<String?>() | ||
val pair = Pair(condition, deferred) | ||
conditions.add(pair) | ||
checkConditionsAndComplete() | ||
return deferred | ||
} | ||
} | ||
|
||
override suspend fun resolveConditionsWithID(id: String) { | ||
val completedConditions = mutableListOf<Pair<ICondition, CompletableDeferred<String?>>>() | ||
|
||
for ((condition, deferred) in conditions) { | ||
if (condition.id == id) { | ||
if (!deferred.isCompleted) { | ||
deferred.complete(null) | ||
} | ||
} | ||
completedConditions.add(Pair(condition, deferred)) | ||
} | ||
|
||
// Remove completed conditions from the list | ||
conditions.removeAll(completedConditions) | ||
} | ||
|
||
/** | ||
* IMPORTANT: calling code should be protected by mutex to avoid potential inconsistencies | ||
*/ | ||
private fun checkConditionsAndComplete() { | ||
val completedConditions = mutableListOf<Pair<ICondition, CompletableDeferred<String?>>>() | ||
|
||
for ((condition, deferred) in conditions) { | ||
if (condition.isMet(indexedTokens)) { | ||
val newestToken = condition.getNewestToken(indexedTokens) | ||
if (!deferred.isCompleted) { | ||
deferred.complete(newestToken) | ||
} | ||
completedConditions.add(Pair(condition, deferred)) | ||
} | ||
} | ||
|
||
// Remove completed conditions from the list | ||
conditions.removeAll(completedConditions) | ||
} | ||
} |
21 changes: 21 additions & 0 deletions
21
...nalSDK/onesignal/core/src/main/java/com/onesignal/common/consistency/models/ICondition.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package com.onesignal.common.consistency.models | ||
|
||
interface ICondition { | ||
/** | ||
* Every implementation should define a unique ID & make available via a companion object for | ||
* ease of use | ||
*/ | ||
val id: String | ||
|
||
/** | ||
* Define a condition that "unblocks" execution | ||
* e.g. we have token (A && B) || A | ||
*/ | ||
fun isMet(indexedTokens: Map<String, Map<IConsistencyKeyEnum, String>>): Boolean | ||
|
||
/** | ||
* Used to process tokens according to their format & return the newest token. | ||
* e.g. numeric strings would be compared differently from JWT tokens | ||
*/ | ||
fun getNewestToken(indexedTokens: Map<String, Map<IConsistencyKeyEnum, String?>>): String? | ||
} |
3 changes: 3 additions & 0 deletions
3
...esignal/core/src/main/java/com/onesignal/common/consistency/models/IConsistencyKeyEnum.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
package com.onesignal.common.consistency.models | ||
|
||
interface IConsistencyKeyEnum |
31 changes: 31 additions & 0 deletions
31
...esignal/core/src/main/java/com/onesignal/common/consistency/models/IConsistencyManager.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package com.onesignal.common.consistency.models | ||
|
||
import kotlinx.coroutines.CompletableDeferred | ||
|
||
interface IConsistencyManager { | ||
/** | ||
* Set method to update the RYW token based on the key. | ||
* Params: | ||
* id: String - the index of the RYW token map (e.g., onesignalId) | ||
* key: IConsistencyKeyEnum - corresponds to the operation for which we have a read-your-write token | ||
* value: String? - the read-your-write token | ||
*/ | ||
suspend fun setRywToken( | ||
id: String, | ||
key: IConsistencyKeyEnum, | ||
value: String, | ||
) | ||
|
||
/** | ||
* Register a condition with its corresponding deferred action. Returns a deferred condition. | ||
* Params: | ||
* condition: ICondition - the condition to be registered | ||
* Returns: CompletableDeferred<String?> - a deferred action that completes when the condition is met | ||
*/ | ||
suspend fun registerCondition(condition: ICondition): CompletableDeferred<String?> | ||
|
||
/** | ||
* Resolve all conditions with a specific ID | ||
*/ | ||
suspend fun resolveConditionsWithID(id: String) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
15 changes: 15 additions & 0 deletions
15
...SDK/onesignal/core/src/main/java/com/onesignal/core/internal/http/impl/OptionalHeaders.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,20 @@ | ||
package com.onesignal.core.internal.http.impl | ||
|
||
data class OptionalHeaders( | ||
/** | ||
* Used as an E-Tag | ||
*/ | ||
val cacheKey: String? = null, | ||
/** | ||
* Used for read your write consistency | ||
*/ | ||
val rywToken: String? = null, | ||
/** | ||
* Current retry count | ||
*/ | ||
val retryCount: Int? = null, | ||
/** | ||
* Used to track delay between session start and request | ||
*/ | ||
val sessionDuration: Long? = null, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.