Half-day tutorial at PPoPP '24
Edinburgh, United Kingdom
March 2, 2024
Join us at PPoPP '24 to learn and discuss how the core constructs of Kotlin coroutines work under the hood!
This tutorial is run by those Kotlin team members who have significantly contributed to Kotlin Coroutines. Roman Elizarov introduced coroutines in Kotlin and led the Kotlin team from 2016 until 2023. Nikita Koval recently redesigned the internal synchronization and communication primitives in Kotlin coroutines and is currently working on the Lincheck framework for testing concurrent algorithms while simultaneously leading the Concurrent Computing lab at JetBrains Research.
Roman Elizarov
Introduction to Kotlin Coroutines
What were the design goals of the coroutines in Kotlin, and how are they implemented on the JVM? What is the concept behind suspending functions in Kotlin, and how are they implemented? Exactly what happens when you launch coroutines, and how much actual overhead do they have? This talk answers these and lots of other coroutine-related questions, showing and explaining the corresponding byte code produced by the Kotlin compiler with code samples and benchmarks.
Roman Elizarov
What were the design goals of the coroutines in Kotlin, and how are they implemented on the JVM? What is the concept behind suspending functions in Kotlin, and how are they implemented? Exactly what happens when you launch coroutines, and how much actual overhead do they have? This talk answers these and lots of other coroutine-related questions, showing and explaining the corresponding byte code produced by the Kotlin compiler with code samples and benchmarks.
Roman Elizarov
Coroutines Scheduler
Coroutines provide end-users with new programming paradigms that present unique challenges to the design of an efficient scheduler, whose task is to allocate coroutines among operating system threads. We will contrast the coroutines workloads to more traditional fork-join workloads, show the benchmarks that drove the design of a dedicated coroutines scheduler, and discuss its algorithm, implementation details, and engineering trade-offs.
Roman Elizarov
Coroutines provide end-users with new programming paradigms that present unique challenges to the design of an efficient scheduler, whose task is to allocate coroutines among operating system threads. We will contrast the coroutines workloads to more traditional fork-join workloads, show the benchmarks that drove the design of a dedicated coroutines scheduler, and discuss its algorithm, implementation details, and engineering trade-offs.
Nikita Koval
Fast and Scalable Communication Channels
The key data structure enabling communication between coroutines is the rendezvous channel. In essence, it is a blocking queue of size zero, so both send(e)
and receive()
operations wait for one another, performing a rendezvous when they meet. To optimize the message-passing pattern, channels are usually equipped with a fixed-size buffer, so sends do not suspend and put elements into the buffer until its capacity is exceeded. This primitive is known as a buffered channel. This talk presents a fast and scalable algorithm for both rendezvous and buffered channels developed for Kotlin coroutines.
This talk is based on our PPoPP '23 paper.
Nikita Koval
The key data structure enabling communication between coroutines is the rendezvous channel. In essence, it is a blocking queue of size zero, so both send(e)
and receive()
operations wait for one another, performing a rendezvous when they meet. To optimize the message-passing pattern, channels are usually equipped with a fixed-size buffer, so sends do not suspend and put elements into the buffer until its capacity is exceeded. This primitive is known as a buffered channel. This talk presents a fast and scalable algorithm for both rendezvous and buffered channels developed for Kotlin coroutines.
This talk is based on our PPoPP '23 paper.
Coffee Break
Nikita Koval
CQS: A Formally-Verified Framework for Fair and Abortable Synchronization Primitives
Programmers often use synchronization abstractions, such as mutex, semaphore, barriers, and count-down latches, to simplify their code. Despite much work in this area, only a few algorithms support both fair FIFO ordering of synchronization requests and abortability. Supporting such semantics is critical for coroutines, which abort frequently and are cheaper to suspend and resume than native threads. This talk introduces a new CancellableQueueSynchronizer (CQS) framework for Kotlin coroutines that enables simple yet efficient implementations of a wide range of fair and abortable synchronization primitives. Besides the algorithmic contribution, we also proved the correctness of all algorithms in Coq and compared them with the primitives in the standard Java library, demonstrating significant improvement.
This talk is based on our PLDI '23 paper.
Nikita Koval
Programmers often use synchronization abstractions, such as mutex, semaphore, barriers, and count-down latches, to simplify their code. Despite much work in this area, only a few algorithms support both fair FIFO ordering of synchronization requests and abortability. Supporting such semantics is critical for coroutines, which abort frequently and are cheaper to suspend and resume than native threads. This talk introduces a new CancellableQueueSynchronizer (CQS) framework for Kotlin coroutines that enables simple yet efficient implementations of a wide range of fair and abortable synchronization primitives. Besides the algorithmic contribution, we also proved the correctness of all algorithms in Coq and compared them with the primitives in the standard Java library, demonstrating significant improvement.
This talk is based on our PLDI '23 paper.
Nikita Koval
Lincheck: A Practical Framework for Testing Concurrent Data Structures on JVM
Kotlin coroutines are used to maintain a dozen non-trivial concurrent data structures, which need to be tested properly. Straightforward stress tests are unlikely to catch all possible bugs and flack periodically. The quality of such testing is below the bar. We need a way to write concurrent tests in a convenient, declarative way, keeping in mind "what" we are testing as opposed to "how" we are testing it. With this in mind, the Lincheck framework for writing concurrent tests was introduced. Users list all the data structure operations, and Lincheck verifies whether these operations are thread-safe. This talk presents Lincheck and explains how you can apply it to your setting while also giving a brief overview of the framework’s internals.
This talk is based on our CAV '23 paper.
Nikita Koval
Kotlin coroutines are used to maintain a dozen non-trivial concurrent data structures, which need to be tested properly. Straightforward stress tests are unlikely to catch all possible bugs and flack periodically. The quality of such testing is below the bar. We need a way to write concurrent tests in a convenient, declarative way, keeping in mind "what" we are testing as opposed to "how" we are testing it. With this in mind, the Lincheck framework for writing concurrent tests was introduced. Users list all the data structure operations, and Lincheck verifies whether these operations are thread-safe. This talk presents Lincheck and explains how you can apply it to your setting while also giving a brief overview of the framework’s internals.
This talk is based on our CAV '23 paper.
Open Discussion
The tutorial is co-located with the PPoPP '24 conference, which takes place on March 2 – March 6 in Edinburgh, UK. You need to register for the conference to participate. Don’t forget to select our tutorial, as it’s not included in the standard ticket!