Skip to content

Commit

Permalink
Updated listeners to support default scope (#34)
Browse files Browse the repository at this point in the history
* Updated listeners to support default scope

* Updated listeners to support default scope
  • Loading branch information
sksamuel authored Dec 6, 2024
1 parent ef89679 commit dcd0edf
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 31 deletions.
38 changes: 36 additions & 2 deletions aedile-core/src/main/kotlin/com/sksamuel/aedile/core/config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package com.sksamuel.aedile.core

import com.github.benmanes.caffeine.cache.Caffeine
import com.github.benmanes.caffeine.cache.RemovalCause
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.future.asCompletableFuture
import kotlinx.coroutines.launch
import kotlin.time.Duration
Expand All @@ -25,9 +28,22 @@ fun <K, V> Caffeine<K, V>.scheduler(scheduler: Scheduler): Caffeine<K, V> {

/**
* Specifies a listener that is notified each time an entry is removed.
* The provided listener will operate on a default coroutine scope.
*
* See full docs at [Caffeine.removalListener].
*/
fun <K, V> Caffeine<K, V>.removalListener(
fun <K, V> Caffeine<K, V>.withRemovalListener(
listener: suspend (K?, V?, RemovalCause) -> Unit,
): Caffeine<K, V> {
val scope = createScope("Aedile-RemovalListener-Scope")
return withRemovalListener(scope, listener)
}

/**
* Specifies a listener that is notified each time an entry is removed.
* See full docs at [Caffeine.removalListener].
*/
fun <K, V> Caffeine<K, V>.withRemovalListener(
scope: CoroutineScope,
listener: suspend (K?, V?, RemovalCause) -> Unit,
): Caffeine<K, V> {
Expand All @@ -40,9 +56,23 @@ fun <K, V> Caffeine<K, V>.removalListener(

/**
* Specifies a listener that is notified each time an entry is evicted.
* The provided listener will operate on a default coroutine scope.
*
* See full docs at [Caffeine.evictionListener].
*/
fun <K, V> Caffeine<K, V>.evictionListener(
fun <K, V> Caffeine<K, V>.withEvictionListener(
listener: suspend (K?, V?, RemovalCause) -> Unit,
): Caffeine<K, V> {
val scope = createScope("Aedile-EvictionListener-Scope")
return withEvictionListener(scope, listener)
}

/**
* Specifies a listener that is notified each time an entry is evicted.
*
* See full docs at [Caffeine.evictionListener].
*/
fun <K, V> Caffeine<K, V>.withEvictionListener(
scope: CoroutineScope,
listener: suspend (K?, V?, RemovalCause) -> Unit,
): Caffeine<K, V> {
Expand Down Expand Up @@ -73,3 +103,7 @@ fun <K, V> Caffeine<K, V>.expireAfterAccess(duration: Duration): Caffeine<K, V>
fun <K, V> Caffeine<K, V>.expireAfterWrite(duration: Duration): Caffeine<K, V> {
return this.expireAfterWrite(duration.toJavaDuration())
}

private fun createScope(name: String): CoroutineScope {
return CoroutineScope(Dispatchers.IO + CoroutineName(name) + SupervisorJob())
}
Original file line number Diff line number Diff line change
@@ -1,42 +1,48 @@
package com.sksamuel.aedile.core

import com.github.benmanes.caffeine.cache.Caffeine
import com.github.benmanes.caffeine.cache.RemovalCause
import io.kotest.assertions.timing.eventually
import io.kotest.assertions.nondeterministic.eventually
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.delay
import kotlin.time.Duration.Companion.seconds

class EvictionListenerTest : FunSpec() {
init {

test("cache should support eviction functions") {
var cause: RemovalCause? = null
val cache = caffeineBuilder<String, String> {
maximumSize = 1
evictionListener = { _, _, removalCause -> cause = removalCause }
}.build()
var c: RemovalCause? = null
val cache = Caffeine.newBuilder()
.maximumSize(1)
.withEvictionListener { _, _, cause -> c = cause }
.asCache<String, String>()

repeat(2) { k ->
cache.put("$k") { "bar" }
}

eventually(5.seconds) {
cause shouldBe RemovalCause.SIZE
c shouldBe RemovalCause.SIZE
}
}

test("cache should support suspendable eviction functions") {
var cause: RemovalCause? = null
val cache = caffeineBuilder<String, String> {
maximumSize = 1
evictionListener = { _, _, removalCause ->
var c: RemovalCause? = null
val cache = Caffeine.newBuilder()
.maximumSize(1)
.withEvictionListener { _, _, cause ->
delay(1)
cause = removalCause
c = cause
}
}.build()
.asCache<String, String>()

repeat(2) { k ->
cache.put("$k") { "bar" }
}

eventually(5.seconds) {
cause shouldBe RemovalCause.SIZE
c shouldBe RemovalCause.SIZE
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ class LoadingCacheTest : FunSpec() {
}
}

test("getAll should throw if build compute function throws and any key is missing") {
test("!getAll should throw if build compute function throws and any key is missing") {
val cache = cacheBuilder<String, String>().build {
error("kaput")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package com.sksamuel.aedile.core

import com.github.benmanes.caffeine.cache.Caffeine
import com.github.benmanes.caffeine.cache.RemovalCause
import io.kotest.assertions.timing.eventually
import io.kotest.assertions.nondeterministic.eventually
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.delay
Expand All @@ -10,33 +11,35 @@ import kotlin.time.Duration.Companion.seconds
class RemovalListenerTest : FunSpec() {
init {
test("cache should support removal listeners") {
var cause: RemovalCause? = null
val cache = caffeineBuilder<String, String> {
maximumSize = 1
removalListener = { _, _, removalCause -> cause = removalCause }
}.build()
var c: RemovalCause? = null
val cache = Caffeine.newBuilder()
.maximumSize(1)
.withRemovalListener { _, _, cause -> c = cause }
.asCache<String, String>()
repeat(2) { k ->
cache.put("$k") { "bar" }
}
eventually(5.seconds) {
cause shouldBe RemovalCause.SIZE
c shouldBe RemovalCause.SIZE
}
}

test("cache should support suspendable removal listeners") {
var cause: RemovalCause? = null
val cache = caffeineBuilder<String, String> {
maximumSize = 1
removalListener = { _, _, removalCause ->
var c: RemovalCause? = null
val cache = Caffeine.newBuilder()
.maximumSize(1)
.withRemovalListener { _, _, cause ->
delay(1)
cause = removalCause
c = cause
}
}.build()
.asCache<String, String>()

repeat(2) { k ->
cache.put("$k") { "bar" }
}

eventually(5.seconds) {
cause shouldBe RemovalCause.SIZE
c shouldBe RemovalCause.SIZE
}
}
}
Expand Down

0 comments on commit dcd0edf

Please sign in to comment.