Suppose I want to use a cache in a DownloadQueue like example.
The joy of coroutines there is that a simple data structure (e.g. HashMap) can be used in a single threaded algorithm.
However I want to use a limited cache, and evict extra entries to a slower persistent storage.
For instance, I can take Guava + CacheBuilder + RemovalListener: https://github.com/google/guava/wiki/CachesExplained#removal-listeners
Here's a problem: RemovalListener is a non-suspended callback, so there's no way to push data to Channels or whatever from the callback.
// This is placed in a suspend context (e.g. within `actor`)
val cache = CacheBuilder.newBuilder().maximumSize(1000)
.expireAfterWrite(10, TimeUnit.SECONDS)
.removalListener<Key, Value> { n ->
expireChannel.sendBlocking(n.value!!) // <-- could sendBlocking be used here???
}.build<Key, Value>()
As far as I understand, runBlocking blocks the execution of a current thread, thus it blocks current coroutine, and it won't be able to receive/handle the data.
Is there a way to make RemovalListener-like callbacks coroutine-friendly?
I can imagine a workaround of using an unbounded queue that is populated by a RemovalListener while the coroutine in question eventually checks the queue.
The only way to not block the removal listener (and thus break the cache) is to have a buffered channel. This means two changes:
SendChannelwith a non-zero capacity, seeChannel(capacity)to consider the different behaviors. It mostly depends on what the behavior should be if more items expire than the consumer can handle.SendChannel.offer()instead ofSendChannel.sendBlocking(). If it returnsfalseyou can implement backup behavior like logging the failure so you can figure out what the best balance between memory and losing events is.