Skip to content

Instantly share code, notes, and snippets.

@recheej

recheej/blog.md Secret

Created February 18, 2021 00:39
Show Gist options
  • Save recheej/1e01a8d5abaf12979e7f1169d3c66207 to your computer and use it in GitHub Desktop.
Save recheej/1e01a8d5abaf12979e7f1169d3c66207 to your computer and use it in GitHub Desktop.

Welcome to my first blog post! I'd like to start it off with a topic that I spend a lot of my professional time working with/thinking about: Kotlin Coroutines! Coroutines aren't a new concept in programming. In short, coroutines allow a function to suspend and resume. The ability to suspend a function has a lot of use cases. In Kotlin, it's most used for performing asynchronous computations. To suspend in Kotlin Coroutines, you'd simply add the suspend modifier to your function. Just what exactly does adding this suspend modifier do? How does it work behind the scenes? That's what we'll be exploring in this blog.

First let's see a simple example of a suspending function. The following suspend function just waits for 1 second:

https://gist.github.com/17b3d7b20628be78d2237ccfad7a69c7

Here we waited for one second but note: we're not blocking whatever thread it's being called on (let's just say main thread for this example). This is all asynchronous. We simply suspended execution of the function, and then 1 second later control of the function is given back. What's even more interesting is that there's no callback involved here.

How are we waiting asynchronously for some amount of time, yet our code is in a synchronous style?. This may seem like magic, but in reality, suspend functions just hide what's actually happening at the Java Bytecode level. We write suspend in our signature, but the code is actually transformed without us knowing. Let's break it down.

Suspend function signature transformation

Here's what the previous foo function signature actually looks like after transformation:

https://gist.github.com/1adc008013706c148f0f139aeb13ee02

What's happening here? A new parameter was added and the function now returns a nullable Any. The reason we have an Any? return type is because suspend functions may not actually suspend. They have the suspend modifier on the signature, but in some cases they may just return without ever suspending or throw an exception (before any suspend). When a suspend function like foo actually suspends, the actual value of the Any? will be a special singleton object: COROUTINE_SUSPENDED. Otherwise it just returns the result.

Now for the Continuation parameter. It is an interface in the kotlinx.coroutines package:

https://gist.github.com/90863923b9919506296abfaf8e4b2559

We'll get more into CoroutineContext in another blog, but essentially the Continuation interface just represents a callback (with some state from CoroutineContext). That's right: coroutines is just writing callbacks for you!

We explained how the signature of a suspend function is transformed, but what does the implementation of suspend functions look like?

Suspend function state machine

I mentioned before that suspend functions are essentially writing callbacks for you. You may think that under the hood there's just this auto generated callback hell happening. But that's not actually the case. Having a bunch of nested callbacks means that you'd have to allocate function objects. And of course that's not very efficient. Instead, the implementation of suspend functions are done with a state machine.

Each suspend function allocates a single state machine object where each suspension point is a state. Also, any local variables in between any suspension points are stored on the state machine. Confusing? Let's look at an example of how this state machine would look like.

First a more complicated suspend function example:

https://gist.github.com/e81e946b4222b91472a53ece31f3ff93

Here's what the function would look like after being transformed by the compiler:

https://gist.github.com/36a9f9006169ca36c280bf985036702b

Of course my post-compiler example is not 100% accurate, but it's pretty close. Again as I said, each suspension point is assigned a "state" (the label + any local variables if applicable). The one main object allocated was the state machine. That's what makes suspend function very efficient vs generating a bunch of callbacks!

Conclusion

And that's it! I hope you learned a lot about how suspend functions work underneath the hood! I plan to do multiple blogs that take deep dives into other aspects of Kotlin Coroutines. Next I'll take a look at CoroutineContext.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment