diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/delivery/EmbraceDeliveryCacheManager.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/delivery/EmbraceDeliveryCacheManager.kt index 955ad3ec34..7a2bf1f411 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/delivery/EmbraceDeliveryCacheManager.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/delivery/EmbraceDeliveryCacheManager.kt @@ -4,6 +4,7 @@ import androidx.annotation.VisibleForTesting import io.embrace.android.embracesdk.internal.EmbraceSerializer import io.embrace.android.embracesdk.internal.clock.Clock import io.embrace.android.embracesdk.internal.utils.Uuid +import io.embrace.android.embracesdk.internal.utils.threadLocal import io.embrace.android.embracesdk.logging.InternalEmbraceLogger import io.embrace.android.embracesdk.payload.BackgroundActivityMessage import io.embrace.android.embracesdk.payload.EventMessage @@ -48,7 +49,7 @@ internal class EmbraceDeliveryCacheManager( private const val TAG = "DeliveryCacheManager" } - private val sessionMessageSerializer by lazy { + private val sessionMessageSerializer by threadLocal { SessionMessageSerializer(serializer) } diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/EmbraceSerializer.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/EmbraceSerializer.kt index a87b1a51a5..05bbcea803 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/EmbraceSerializer.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/EmbraceSerializer.kt @@ -1,12 +1,12 @@ package io.embrace.android.embracesdk.internal -import com.google.gson.Gson import com.google.gson.GsonBuilder import com.google.gson.JsonIOException import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonWriter import io.embrace.android.embracesdk.comms.api.EmbraceUrl import io.embrace.android.embracesdk.comms.api.EmbraceUrlAdapter +import io.embrace.android.embracesdk.internal.utils.threadLocal import io.embrace.android.embracesdk.logging.InternalStaticEmbraceLogger import java.io.BufferedWriter import java.lang.reflect.Type @@ -17,34 +17,32 @@ import java.nio.charset.Charset */ internal class EmbraceSerializer { - private val gson: ThreadLocal = object : ThreadLocal() { - override fun initialValue(): Gson { - return GsonBuilder() - .registerTypeAdapter(EmbraceUrl::class.java, EmbraceUrlAdapter()) - .create() - } + private val gson by threadLocal { + GsonBuilder() + .registerTypeAdapter(EmbraceUrl::class.java, EmbraceUrlAdapter()) + .create() } fun toJson(src: T): String { - return gson.get()?.toJson(src) ?: throw JsonIOException("Failed converting object to JSON.") + return gson.toJson(src) ?: throw JsonIOException("Failed converting object to JSON.") } fun toJson(src: T, type: Type): String { - return gson.get()?.toJson(src, type) + return gson.toJson(src, type) ?: throw JsonIOException("Failed converting object to JSON.") } fun fromJson(json: String, type: Type): T? { - return gson.get()?.fromJson(json, type) + return gson.fromJson(json, type) } fun fromJson(json: String, clz: Class): T? { - return gson.get()?.fromJson(json, clz) + return gson.fromJson(json, clz) } fun writeToFile(any: T, clazz: Class, bw: BufferedWriter): Boolean { return try { - gson.get()?.toJson(any, clazz, JsonWriter(bw)) + gson.toJson(any, clazz, JsonWriter(bw)) true } catch (e: Exception) { InternalStaticEmbraceLogger.logDebug("cannot write to bufferedWriter", e) @@ -53,11 +51,11 @@ internal class EmbraceSerializer { } fun loadObject(jsonReader: JsonReader, clazz: Class): T? { - return gson.get()?.fromJson(jsonReader, clazz) + return gson.fromJson(jsonReader, clazz) } fun bytesFromPayload(payload: T, clazz: Class): ByteArray? { - val json: String? = gson.get()?.toJson(payload, clazz.genericSuperclass) + val json: String? = gson.toJson(payload, clazz.genericSuperclass) return json?.toByteArray(Charset.forName("UTF-8")) } } diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/utils/ThreadLocalExtensions.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/utils/ThreadLocalExtensions.kt new file mode 100644 index 0000000000..d7d94f9223 --- /dev/null +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/utils/ThreadLocalExtensions.kt @@ -0,0 +1,25 @@ +package io.embrace.android.embracesdk.internal.utils + +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +/** + * Syntactic sugar that makes it easier to define a property in Kotlin whose value is backed by + * a ThreadLocal. + */ +internal inline fun threadLocal( + noinline provider: () -> T +): ReadOnlyProperty = ThreadLocalDelegate(provider) + +internal class ThreadLocalDelegate( + provider: () -> T +) : ReadOnlyProperty { + + private val threadLocal: ThreadLocal = object : ThreadLocal() { + override fun initialValue(): T = provider() + } + + override fun getValue(thisRef: Any?, property: KProperty<*>): T { + return checkNotNull(threadLocal.get()) + } +} diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/internal/utils/ThreadLocalExtensionsTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/internal/utils/ThreadLocalExtensionsTest.kt new file mode 100644 index 0000000000..b5552aa375 --- /dev/null +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/internal/utils/ThreadLocalExtensionsTest.kt @@ -0,0 +1,29 @@ +package io.embrace.android.embracesdk.internal.utils + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotEquals +import org.junit.Test +import java.util.concurrent.CountDownLatch +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit + +internal class ThreadLocalExtensionsTest { + + private val id: Long by threadLocal { + Thread.currentThread().id + } + + @Test + fun testThreadLocalProperties() { + val testThreadId = Thread.currentThread().id + assertEquals(id, testThreadId) + + val latch = CountDownLatch(1) + Executors.newSingleThreadExecutor().submit { + assertEquals(id, Thread.currentThread().id) + assertNotEquals(id, testThreadId) + latch.countDown() + } + latch.await(1, TimeUnit.SECONDS) + } +}