Hello. I have a question whether coroutines are su...
# coroutines
g
Hello. I have a question whether coroutines are suitable for my application.
I wrote a quiz loop consisting of: • Checking users answer input • Giving a hint after time x • Giving another hint after time x • Giving the solution after time x Now I want to let multiple users start quizzes for their own playerbase. After reading a bit and watching videos I found actors in combination with coroutines and thought that this might be a good fit for my application. Especially with the Send and ReceiveChannel. I am unsure whether that would work with an amount of for example 1000 running quizzes and them all being nonblocking. Further I tried to find information regarding getting a reference to a coroutine job so I can stop the quiz for a specific user when he requests to do so. Where can I find more examples or explanations regarding this: https://stackoverflow.com/questions/65762439/get-reference-to-a-coroutine-job-in-coroutine-scope I am also not sure how I would store the references and then how exactly to stop the specific job. Thanks for your help. 🙂
j
Why do you think you should use Actors? In my experience, they're rarely needed. Cancelling a Job is just
job.cancel()
. Storing job references is simplified by Structured Concurrency. Also, a
Job
instance is returned by any call to
launch
or
async
coroutine builders.
g
Maybe I misunderstood something. My planned flow looks like this: - User1 clicks a start button on the frontend - A quiz for User1 and his playerbase is started on the backend - User1 leaves the page and the quiz keeps running - On a different page the playerbase of User1 keeps interacting with the quiz - User1 comes back to the frontend and clicks a button to stop the quiz - With the reference of the job started for the coroutine of User1 the quiz is stopped While the quiz for User1 is running a second, third... user can also start a quiz for their own playerbase.
d
While you can probably implement it as described. The issue with having the state of a quiz within a single-coroutine vs serialized to a database is that the single coroutine doesn't allow your server to horizontally scale. e.g. there's no way of moving quiz N's coroutine from Server A to Server B. You can certainly host the quiz with the actor pattern (which could be nice if you want to use a websocket to actively shutdown the page of quiz takers) But as far as storing "active quizes" as individual coroutines. That just sounds like a less refined final model of the application. Would it scale to a 1000 active quizes? Probably quite easily! Is it the right tool for the data storage job of "which quizes are active"? Probably not just use a database/Redis. But I want all Kotlin K and to experiment? Go for it!
g
My initial idea was to store the quiz configuration in a mongoDB so I can restart and restore the previous quizzes if the program should restart for some reason and every running coroutine can get its own configuration from the db. Using redis to store the runtime data seems like a good idea. I do not really understand what you mean by storing "active quizzes".
c
Coroutines are just a runtime concept, they work in-memory. Coroutines themselves scale very, very well with concurrent jobs as seen in this example which launched 100,000 jobs no problem. So having 1000 concurrent users will not be a problem with coroutines at all, but would be limited more by the application server it’s running on or your horizontal scaling strategy. But working with databases or other serialized data isn’t something that “just happens” with Coroutines. You’ll probably be able to find a coroutine-friendly API to most database engines nowadays, but it’s still up to you to write your application logic to load the data from the database into your app. If you’re looking at starting/stopping the quiz over time and persisting that to the database, that’s not really something you’d implement “in coroutines” because you can’t save a
Job
to the database, for example
g
Okay. I think I understood that. Now as a very simple example I thought it could look similar to this:
Copy code
suspend fun main() {
    class Bot() {
        val jobs = mutableListOf<Job>()
        init {
            val activeQuizzes = getActiveQuizzes()
            for(username in activeQuizzes) {
                start(username)
            }

        }
        private fun getActiveQuizzes(): List<String> {
            return listOf("User1", "User2")
        }
        fun start(username: String) {
            val job = Job()
            val scope = CoroutineScope(Dispatchers.Default + job)
            scope.launch {
                while (true) {
                    delay(500)
                    println("Delayed $username")
                }
            }
            jobs.add(job)
        }
        fun stop(id: Int) {
            println("Cancel job: $id")
            jobs[id].cancel()
        }
    }
    val bot = Bot()
    bot.start("User3")
    delay(5000)
    bot.stop(2)
    bot.jobs.joinAll()
}
Is it okay to use coroutines like this or should I do something completely different?