Hacker News new | ask | show | jobs
by fesc 1367 days ago
> Kotlin's co-routines are simply syntactic sugar for simple callbacks

That's only partly true. And yeah they're callbacks but not like e.g. we know from JS.

As far as I know CPS basically means that the Kotlin compiler turns a function like that:

    suspend fun myFunction(param: String) {
      val asyncVal = someSuspendingFunction()
      val asyncVal2 = anotherSuspendingFunctio(asyncVal)
      return someCalculation(asyncVal2)
    }

Into a modified method and *class* like that:

     // The different parts of the function get put into different ifs
     // The state that is normally in the JVM stack frame is holded in a custom class which is a subclass of https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines/-continuation/
     // Some integer tracks where in the function we are
     fun myFunction(param: String, continuation: Something<MyFunctionClass>?) { 
        continuation = continuation ?: MyFunctionContinuation(param)
        if (continuation.state == 0) {
          contination.state = 1
          someSuspendingFunction(continuation)
          return SUSPEND
        }
        if (continuation.state == 1) {
           // The result mechanism is in the Contiuation interface/abstract class. It can be used to later receive the async result
           // of callig annother suspending function
           continuation.asyncVal = contiuation.getResult()
           anotherSuspendingFunction(continuation.asyncVal, continuation)
           return SUSPEND
        }
        if (continuation.state == 2) {
           continuation.asyncVal2 = contiuation.getResult()
           return FINISHED(someCalculation(continuation.asyncVal2))
        }
     }

     class MyFunctionContinuation: Continuation {
        
        // Function arguments
        val param: String
        
        // Every suspending function needs this. It tracks where in the function we currently are
        val state: Int = 0
       
        // Local variables inside the function. Those would normally live in the JVM function stack 
        // But the  Coroutine runtime needs to be able to "rehydrate" the JVM function stack once a non-blocking call finishes
        var asyncVal: <Don't remember>
        var asyncVal2: <Don't remember>

     }
I hope the basic idea comes across.

I read this sometime in some book about coroutines. But you can see that there is some overhead. The basic idea is to replicate the function call stack in a custom datastructure. This way we can be deep inside a normal JVM callstack and also have a separate Kotlin Coroutine callstack.

Once the coroutine wants to suspend it can simply return from the JVM callstack but the Kotlin callstack is preserved. Later when we want to continue we can call the same function again, with the Kotlin callstack now containing the result of the async operation.

If the JVM can do this natively we wouldn't need this second Kotlin coroutine callstack because the native JVM callstack would support this.

1 comments

Nice. This explains why the stacktraces are kinda useless unless you take special care with kotlin coroutines.