diff --git a/cache/src/commonMain/kotlin/MemoryCache.kt b/cache/src/commonMain/kotlin/MemoryCache.kt index 0e932a6..13a9b01 100644 --- a/cache/src/commonMain/kotlin/MemoryCache.kt +++ b/cache/src/commonMain/kotlin/MemoryCache.kt @@ -5,6 +5,7 @@ import kotlinx.coroutines.flow.* import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit import opensavvy.cache.MemoryCache.Companion.cachedInMemory +import opensavvy.cache.PassThroughContext.Companion.onlyPassThrough import opensavvy.logger.Logger.Companion.trace import opensavvy.logger.loggerFor import opensavvy.state.progressive.ProgressiveOutcome @@ -81,7 +82,10 @@ class MemoryCache( if (job == null || !job.isActive) { // No one is currently making the request, I'm taking the responsibility to do it - jobs[id] = scope.launch(CoroutineName("${this@MemoryCache} for $id")) { + val childContext = currentCoroutineContext().onlyPassThrough() + + CoroutineName("$this for $id") + + jobs[id] = scope.launch(childContext) { log.trace(id) { "Subscribing to the previous layer for" } val state = cacheLock.withPermit { getUnsafe(id) } diff --git a/cache/src/commonMain/kotlin/PassThroughContext.kt b/cache/src/commonMain/kotlin/PassThroughContext.kt new file mode 100644 index 0000000..674f940 --- /dev/null +++ b/cache/src/commonMain/kotlin/PassThroughContext.kt @@ -0,0 +1,22 @@ +package opensavvy.cache + +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext + +/** + * Marker interface for coroutine context elements that should pass through the cache. + * + * On a cache miss, a cache may start a new request. + * In that case, all coroutine context elements implementing this interface will be retained for the real request. + */ +interface PassThroughContext : CoroutineContext { + + companion object { + + internal fun CoroutineContext.onlyPassThrough() = fold(EmptyCoroutineContext as CoroutineContext) { acc, it -> + if (it is PassThroughContext) acc + it + else acc + } + + } +} diff --git a/cache/src/commonTest/kotlin/PassThroughTest.kt b/cache/src/commonTest/kotlin/PassThroughTest.kt new file mode 100644 index 0000000..5a1b863 --- /dev/null +++ b/cache/src/commonTest/kotlin/PassThroughTest.kt @@ -0,0 +1,45 @@ +package opensavvy.cache + +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.withContext +import opensavvy.cache.CacheAdapter.Companion.cache +import opensavvy.cache.ExpirationCache.Companion.expireAfter +import opensavvy.cache.MemoryCache.Companion.cachedInMemory +import kotlin.coroutines.AbstractCoroutineContextElement +import kotlin.coroutines.CoroutineContext +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.time.Duration.Companion.minutes + +class ShouldNotPassThrough : AbstractCoroutineContextElement(ShouldNotPassThrough) { + companion object : CoroutineContext.Key +} + +class ShouldPassThrough : AbstractCoroutineContextElement(ShouldPassThrough), PassThroughContext { + companion object : CoroutineContext.Key +} + +@OptIn(ExperimentalCoroutinesApi::class) +class PassThroughTest { + + @Test + fun passThroughTest() = runTest { + val shouldNot = ShouldNotPassThrough() + val should = ShouldPassThrough() + + val cache = cache { + assertEquals(null, currentCoroutineContext()[ShouldNotPassThrough]) + assertEquals(should, currentCoroutineContext()[ShouldPassThrough]) + } + .cachedInMemory(backgroundScope.coroutineContext) + .expireAfter(2.minutes, backgroundScope.coroutineContext) + + withContext(should + shouldNot) { + cache[Unit].first() + } + } + +}