Skip to content

Commit

Permalink
Defer session properties load until it's really needed (#538)
Browse files Browse the repository at this point in the history
## Goal

Similar to UserInfo, SessionProperties were also being initialized premature. Defer this to when it's actually read and synchronize access.

## Testing

Existing tests cover this.
  • Loading branch information
bidetofevil committed Mar 11, 2024
2 parents 46c1ee2 + abcc1ea commit d6103c8
Showing 1 changed file with 65 additions and 47 deletions.
Original file line number Diff line number Diff line change
@@ -1,26 +1,37 @@
package io.embrace.android.embracesdk.session.properties

import io.embrace.android.embracesdk.config.ConfigService
import io.embrace.android.embracesdk.internal.utils.Provider
import io.embrace.android.embracesdk.logging.InternalEmbraceLogger
import io.embrace.android.embracesdk.logging.InternalStaticEmbraceLogger
import io.embrace.android.embracesdk.prefs.PreferencesService
import java.util.concurrent.atomic.AtomicReference

internal class EmbraceSessionProperties(
private val preferencesService: PreferencesService,
private val configService: ConfigService,
private val logger: InternalEmbraceLogger = InternalStaticEmbraceLogger.logger
) {
private val temporary: MutableMap<String, String> = HashMap()
private var permanent: MutableMap<String, String>
private val permanentPropertiesReference = AtomicReference(NOT_LOADED)
private val permanentPropertiesProvider: Provider<MutableMap<String, String>> = {
preferencesService.permanentSessionProperties?.let(::HashMap) ?: HashMap()
}

private fun permanentProperties(): MutableMap<String, String> {
if (permanentPropertiesReference.get() === NOT_LOADED) {
synchronized(permanentPropertiesReference) {
if (permanentPropertiesReference.get() === NOT_LOADED) {
permanentPropertiesReference.set(permanentPropertiesProvider())
}
}
}

init {
// TODO: this blocks on the preferences being successfully read from this. Are we cool with this?
val existingPermanent: Map<String, String>? = preferencesService.permanentSessionProperties
permanent = existingPermanent?.let(::HashMap) ?: HashMap()
return permanentPropertiesReference.get()
}

private fun haveKey(key: String): Boolean {
return permanent.containsKey(key) || temporary.containsKey(key)
return permanentProperties().containsKey(key) || temporary.containsKey(key)
}

private fun isValidKey(key: String?): Boolean {
Expand Down Expand Up @@ -51,58 +62,64 @@ internal class EmbraceSessionProperties(
return value.substring(0, maxLength - endChars.length) + endChars
}

@Synchronized
fun add(key: String, value: String, isPermanent: Boolean): Boolean {
if (!isValidKey(key)) {
return false
}
val sanitizedKey = enforceLength(key, SESSION_PROPERTY_KEY_LIMIT)
if (!isValidValue(value)) {
return false
}
val sanitizedValue = enforceLength(value, SESSION_PROPERTY_VALUE_LIMIT)
val maxSessionProperties = configService.sessionBehavior.getMaxSessionProperties()
if (size() > maxSessionProperties || size() == maxSessionProperties && !haveKey(sanitizedKey)) {
logger.logError("Session property count is at its limit. Rejecting.")
return false
}
synchronized(permanentPropertiesReference) {
if (!isValidKey(key)) {
return false
}
val sanitizedKey = enforceLength(key, SESSION_PROPERTY_KEY_LIMIT)
if (!isValidValue(value)) {
return false
}
val sanitizedValue = enforceLength(value, SESSION_PROPERTY_VALUE_LIMIT)
val maxSessionProperties = configService.sessionBehavior.getMaxSessionProperties()
if (size() > maxSessionProperties || size() == maxSessionProperties && !haveKey(sanitizedKey)) {
logger.logError("Session property count is at its limit. Rejecting.")
return false
}

// add to selected destination, deleting the key if it exists in the other destination
if (isPermanent) {
permanent[sanitizedKey] = sanitizedValue
temporary.remove(sanitizedKey)
preferencesService.permanentSessionProperties = permanent
} else {
// only save the permanent values if the key existed in the permanent map
if (permanent.remove(sanitizedKey) != null) {
preferencesService.permanentSessionProperties = permanent
// add to selected destination, deleting the key if it exists in the other destination
if (isPermanent) {
permanentProperties()[sanitizedKey] = sanitizedValue
temporary.remove(sanitizedKey)
preferencesService.permanentSessionProperties = permanentProperties()
} else {
// only save the permanent values if the key existed in the permanent map
val newPermanent = permanentProperties()
if (newPermanent.remove(sanitizedKey) != null) {
permanentPropertiesReference.set(newPermanent)
preferencesService.permanentSessionProperties = permanentProperties()
}
temporary[sanitizedKey] = sanitizedValue
}
temporary[sanitizedKey] = sanitizedValue
return true
}
return true
}

@Synchronized
fun remove(key: String): Boolean {
if (!isValidKey(key)) {
return false
}
val sanitizedKey = enforceLength(key, SESSION_PROPERTY_KEY_LIMIT)
var existed = false
if (temporary.remove(sanitizedKey) != null) {
existed = true
}
if (permanent.remove(sanitizedKey) != null) {
preferencesService.permanentSessionProperties = permanent
existed = true
synchronized(permanentPropertiesReference) {
if (!isValidKey(key)) {
return false
}
val sanitizedKey = enforceLength(key, SESSION_PROPERTY_KEY_LIMIT)
var existed = false
if (temporary.remove(sanitizedKey) != null) {
existed = true
}

val newPermanent = permanentProperties()
if (newPermanent.remove(sanitizedKey) != null) {
permanentPropertiesReference.set(newPermanent)
preferencesService.permanentSessionProperties = permanentProperties()
existed = true
}
return existed
}
return existed
}

@Synchronized
fun get(): Map<String, String> = permanent.plus(temporary)
fun get(): Map<String, String> = synchronized(permanentPropertiesReference) { permanentProperties().plus(temporary) }

private fun size(): Int = permanent.size + temporary.size
private fun size(): Int = permanentProperties().size + temporary.size

fun clearTemporary() = temporary.clear()

Expand All @@ -113,5 +130,6 @@ internal class EmbraceSessionProperties(
*/
private const val SESSION_PROPERTY_KEY_LIMIT = 128
private const val SESSION_PROPERTY_VALUE_LIMIT = 1024
private val NOT_LOADED = mutableMapOf<String, String>()
}
}

0 comments on commit d6103c8

Please sign in to comment.