Redisson & Kotlin Coroutines – asynchronous programming on the rocks

In Wonderful Redis & Kotlin – Awesome With Redisson we explored how to use Redis/Redisson & Kotlin.

Asynchronous programming is covered as Redisson mode, however not Kotlin asynchronous programming. The Kotlin “way” of asynchronous programming is Coroutines. In this post we explore how to combine Coroutines and Redisson in two flavors:

Redisson default client & kotlin coroutines

Now let’s combine the redisson default client with kotlin coroutines:

    private fun atomicLongCoroutines(redisson: RedissonClient, newValue: Long) {
        val myAtomicLong: RAtomicLong = redisson.getAtomicLong("myAtomicLongCoRoutine")

        val job = GlobalScope.launch {
            for (i in 1..7) {
                myAtomicLong.incrementAndGet()
            }
        }
        // Waiting for the increments to finish
        job.join()

        val jobs = mutableListOf<Job>()
        repeat(1) {
            jobs.add(GlobalScope.launch {
                myAtomicLong.get()
            })
        }

        // Waiting for the jobs to finish
        jobs.joinAll()
        myAtomicLong.unlink() // clean up, happens async
    }

source

Make sure you call this function in a runblocking block:

    runBlocking {
        atomicLongCoroutines(redisson.redissonClient)
    }

source

Redisson reactive client & kotlin coroutines

    private suspend fun coroutinesLock(redissonClient: RedissonClient, range: IntProgression = 1 ..< 5) {
        val delayTime: Long = 1000L
        val redissonReactive: RedissonReactiveClient = redissonClient.reactive()
        val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
        range.map { entry ->
            scope.launch(CoroutineIdentifier(identifier = entry.toLong())) {
                val lock = redissonReactive.getLock("l$entry")
                val id:Long? = currentCoroutineContext()[CoroutineIdentifier.CoroutineKey]?.identifier
                if (null != id) {
                    lock.lock(id)
                    try {
                        println("lock(id: $id) acquired")
                        delay(delayTime)
                    } finally {
                        lock.unlock(id)
                        println("lock(id: $id) released")
                    }
                }
            }
        }.joinAll()
    }

source

As the previous example, make sure the function is called in a runblocking block

    runBlocking {
        coroutinesLock(redisson.redissonClient)
    }

source

One sample output would be:

lock(id: 1) acquired
lock(id: 2) acquired
lock(id: 3) acquired
lock(id: 4) acquired
lock(id: 1) released
lock(id: 2) released
lock(id: 3) released
lock(id: 4) released

While the order of IDs may vary in different code execution runs, a lock is guaranteed to be acquired first and released after.

The code above is inspired by the code examples of Don’t Use Java RedissonClient With Kotlin Coroutines for Distributed Redis Locks. However this code did not work/compile for me in Kotlin 1.9.0. The main reason it did not work, was the lock.lock().awaitFirstOrNull() call.


The reactive client code above uses the rangeUntil operator that is stable as part of the standard library API with Kotlin v1.9.0.

1 Comment

Leave a Reply

Your email address will not be published.


*


This site uses Akismet to reduce spam. Learn how your comment data is processed.