Structured Concurrency (original) (raw)

Presented at Hydra Distr...")
@ Roman Elizarov relizarov 1

• Professional develope...")
developed high-perf trading software @ Devexperts • Teach concurrent & distributed programming @ St. Petersburg ITMO University • Chief judge @ Northern Eurasia Contest / ICPC • Now team lead in Kotlin Libraries @ JetBrains elizarov @ relizarov 2

Kotlin Coroutines design
3
")

async Task PostItem(Ite...")
= await RequestToken(); var post = await CreatePost(token, item); ProcessPost(post); } C# async modifier Returns a future 1. awaits a future other async functions return future 4 2. awaits a future

async Task PostItem(Ite...")
= await RequestToken(); var post = await CreatePost(token, item); ProcessPost(post); } C# 5

async Task PostItem(Ite...")
= await RequestToken(); var post = await CreatePost(token, item); ProcessPost(post); } C# Suspension points! But what about generators with yield return? 6

fun postItem(item...")
val token = await(requestToken()) val post = await(createPost(token, item)) processPost(post) } Kotlin Coroutine builder Coroutine scope 7 Regular function

fun postItem(item...")
val token = await(requestToken()) val post = await(createPost(token, item)) processPost(post) } Kotlin Coroutine builder await function Coroutine scope Suspending functions 8 Regular function

fun postItem(item...")
val token = await(requestToken()) val post = await(createPost(token, item)) processPost(post) } Kotlin 9

fun postItem(item...")
val token = await(requestToken()) val post = await(createPost(token, item)) processPost(post) } Kotlin async / await à future 10

fun postItem(item...")
val token = yield(requestToken()) val post = yield(createPost(token, item)) processPost(post) } Kotlin async / await à future generate / yield à sequence 11

fun postItem(item: Item) ...")
= await(requestToken()) val post = await(createPost(token, item)) processPost(post) } Kotlin 12

fun postItem(i...")
token = requestToken() val post = createPost(token, item) processPost(post) } Kotlin Returns a future 13

suspend fun po...")
= requestToken() val post = createPost(token, item) processPost(post) } Kotlin Suspending function modifier 14

suspend fun po...")
= requestToken() val post = createPost(token, item) processPost(post) } Kotlin 15

func postItem(...")
post := createPost(token, item) processPost(post) } Go 16

DSL for concurrency
17
")

https://tour.golang.org/concurrency/1
")

func say(s string) ...")
for i := 0; i < 5; i++ { time.Sleep(100 * time.Millisecond) fmt.Println(s) } } func main() { go say("world") say("hello") } Go https://tour.golang.org/concurrency/1 19

func say(s string) ...")
for i := 0; i < 5; i++ { time.Sleep(100 * time.Millisecond) fmt.Println(s) } } func main() { go say("world") say("hello") } Go 20 https://tour.golang.org/concurrency/1

func say(s string) ...")
for i := 0; i < 5; i++ { time.Sleep(100 * time.Millisecond) fmt.Println(s) } } func main() { go say("world") say("hello") } Go 21 https://tour.golang.org/concurrency/1

suspend fun sa...")
for (i in 0..4) { delay(100) println(s) } } fun main() = mainBlocking { go { say("world") } say("hello") } Kotlin 22 Suspending function modifier Suspending function

suspend fun sa...")
for (i in 0..4) { delay(100) println(s) } } fun main() = mainBlocking { go { say("world") } say("hello") } Kotlin 23 Suspending function modifier Suspending function Coroutine builder Another builder

suspend fun say(s: S...")
for (i in 0..4) { delay(100) println(s) } } fun main() = runBlocking { launch { say("world") } say("hello") } Kotlin 24

func fibonacci(c, q...")
int) { x, y := 0, 1 for { select { case c <- x: x, y = y, x+y case <-quit: fmt.Println("quit") return } } } Go https://tour.golang.org/concurrency/5 25

func fibonacci(c, q...")
int) { x, y := 0, 1 for { select { case c <- x: x, y = y, x+y case <-quit: fmt.Println("quit") return } } } Go https://tour.golang.org/concurrency/5 26

func fibonacci(c, q...")
int) { x, y := 0, 1 for { select { case c <- x: x, y = y, x+y case <-quit: fmt.Println("quit") return } } } Go https://tour.golang.org/concurrency/5 27

func fibonacci(c, q...")
int) { x, y := 0, 1 for { select { case c <- x: x, y = y, x+y case <-quit: fmt.Println("quit") return } } } Go https://tour.golang.org/concurrency/5 28

func fibonacci(c, q...")
int) { x, y := 0, 1 for { select { case c <- x: x, y = y, x+y case <-quit: fmt.Println("quit") return } } } Go https://tour.golang.org/concurrency/5 29

suspend fun fi...")
ReceiveChannel) { var x = 0 var y = 1 whileSelect { c.onSend(x) { val next = x + y x = y y = next true // continue while loop } quit.onReceive { println("quit") false // break while loop } } } Kotlin 30

suspend fun fi...")
ReceiveChannel) { var x = 0 var y = 1 whileSelect { c.onSend(x) { val next = x + y x = y y = next true // continue while loop } quit.onReceive { println("quit") false // break while loop } } } Kotlin 31 Library types

suspend fun fi...")
ReceiveChannel) { var x = 0 var y = 1 whileSelect { c.onSend(x) { val next = x + y x = y y = next true // continue while loop } quit.onReceive { println("quit") false // break while loop } } } Kotlin 32 Library types Select DSL

Is concurrency support a sol...")
33

try {
// suspend while asynchronousl...")
val result = makeRequest() // display result in UI display(result) } catch (exception: Throwable) { // process exception in UI } } Thread-bound UI programming 34

try {
// suspend while asynchronousl...")
val result = makeRequest() // display result in UI display(result) } catch (exception: Throwable) { // process exception in UI } } Thread-bound UI programming 35

try {
// suspend while asynchronousl...")
val result = makeRequest() // display result in UI display(result) } catch (exception: Throwable) { // process exception in UI } } Thread-bound UI programming 36

try {
// suspend while asynchronousl...")
val result = makeRequest() // display result in UI display(result) } catch (exception: Throwable) { // process exception in UI } } Thread-bound UI programming 37

try {
// suspend while asynchronou...")
val result = makeRequest() // display result in UI display(result) } catch (exception: Throwable) { // process exception in UI } } Thread-bound UI programming 38 Coroutine context

try {
// suspend while asynchronou...")
val result = makeRequest() // display result in UI run { display(result) } } catch (exception: Throwable) { // process exception in UI } } Thread-bound UI programming 39 Higher-order function Coroutine context

40
")

41
await Run(() => ...")
C# Lambda

42
var task = Run((...")
Display(result); }); await task; C# 1. Function called first 2. Then await

43
var task = Run((...")
Display(result); }); await task; C# 1. Function called first 2. Then await A call to regular function

44
run {
display(re...")
is passed along the suspend callstack A call to suspending function

The stumbling block of concurrency...")

func fibonacci(c, quit...")
{ x, y := 0, 1 for { select { case c <- x: x, y = y, x+y case <-quit: fmt.Println("quit") return } } } Go https://tour.golang.org/concurrency/5 46 A CancellationToken

func fibonacci(c, quit...")
{ x, y := 0, 1 for { select { case c <- x: x, y = y, x+y case <-quit: fmt.Println("quit") return } } } Go https://tour.golang.org/concurrency/5 47 A CancellationToken A boilerplate

quit chan
")

49
type Context...")
} Go https://golang.org/pkg/context/

50
type Context...")
// Done returns a channel that's closed when work done on // behalf of this context should be canceled. … Done() <-chan struct{} } Go https://golang.org/pkg/context/ At Google, we require that Go programmers pass a Context parameter as the first argument to every function on the call path between incoming and outgoing requests.

51
")

52
interface Lifetime : Coro...")
} Kotlin

53
interface Lifetime : Coro...")
Throwable? = null): Boolean } Kotlin

54
interface Lifetime : Coro...")
Throwable? = null): Boolean fun onCompletion(handler: CompletionHandler): Registration } Kotlin

55
interface Job : CoroutineConte...")
Throwable? = null): Boolean fun onCompletion(handler: CompletionHandler): Registration } Kotlin

56
val job = lau...")
say("world") } Kotlin

57
val job = lau...")
say("world") } job.cancel() Kotlin

58
val job = lau...")
say("world") } job.cancel() Kotlin delay(…) throw CancellationException()

59
withTi...")
job1 job2 Prototype worked like a charm

Nesting concurrent computat...")

61
val job1 = launch {...")
val job2 = launch { say("world") } Kotlin These jobs are resources

62
val job1 = launch {...")
val job2 = launch { say("world") } val jobs = CompositeJob() Kotlin

63
val job1 = launch {...")
val job2 = launch { say("world") } val jobs = CompositeJob() jobs.add(job1) jobs.add(job2) Kotlin

64
val job1 = launch {...")
val job2 = launch { say("world") } val jobs = CompositeJob() jobs.add(job1) jobs.add(job2) jobs.cancel() Kotlin

65
interface Job...")
fun cancel(reason: Throwable? = null): Boolean fun onCompletion(handler: CompletionHandler): Registration } Kotlin

66
val jobs = Composit...")

67
val job = Job()
Kot...")

68
val job = Job()
lau...")
} launch(job) { say("world") } Kotlin Job is a coroutine context!

69
val job = Job()
lau...")
} launch(job) { say("world") } job.cancel() Kotlin

70
val job = Job()
lau...")
} launch(job) { say("world") } job.cancel() Kotlin !

71
val job = Job()
lau...")
} launch(job) { say("world") } job.cancel() Kotlin Job becomes a CancellationToken !

72
suspend fun doSomething(...")

73
suspend fun doSomething(...")
} launch(coroutineContext) { say("world") } } Kotlin !

74
val job = launch {
launc...")
say("hello") } launch(coroutineContext) { say("world") } } Kotlin ! !

75
val job = launch(UI) {
l...")
say("hello") } launch(coroutineContext) { say("world") } } Kotlin ! ! !

76
val job = launch {
launch...")
say("hello") } launch(coroutineContext) { say("world") } } Kotlin !

77
val job = launch {
say("he...")
} Kotlin It can fail

78
val job = launch {
say("he...")
} Kotlin It can fail

79
val job = launch {
launch(...")
say("hello") } launch(coroutineContext) { say("world") } } Kotlin It can fail It can fail They can fail concurrently Success or failure of this composite job can be known only when all children complete cancel(ex) cancel(ex)

80
https://kotlin.github.io...")

81
val job = launch {
launch(coroutineCon...")
} launch(coroutineContext) { say("world") } } Kotlin

82
val job...")
{ say("hello") } launch(coroutineContext) { say("world") } } Kotlin

83
val job...")
} suspend fun sayHelloWorld() { launch(coroutineContext) { say("hello") } launch(coroutineContext) { say("world") } } Kotlin

84
val job...")
} suspend fun sayHelloWorld() { launch(coroutineContext) { say("hello") } launch(coroutineContext) { say("world") } } Kotlin It can fail

85
val job...")
} suspend fun sayHelloWorld() { launch(coroutineContext) { say("hello") } launch(coroutineContext) { say("world") } } Kotlin It can fail But call returns normally! !

86
val job = lau...")
} suspend fun sayHelloWorld() { withScope { // new job in the context launch(coroutineContext) { say("hello") } launch(coroutineContext) { say("world") } } } Kotlin Throws exception on failure Encapsulated concurrent decomposition

87
val job = lau...")
} suspend fun sayHelloWorld() { withScope { launch(coroutineContext) { say("hello") } launch(coroutineContext) { say("world") } } } Kotlin

88
val ...")
sayHelloWorld() } suspend fun sayHelloWorld() { withScope { launch(coroutineContext) { say("hello") } launch(coroutineContext) { say("world") } } } Kotlin Error-pone

89
val ...")
sayHelloWorld() } suspend fun sayHelloWorld() { withScope { launch(coroutineContext) { say("hello") } launch(coroutineContext) { say("world") } } } Kotlin Error-pone Verbose

90
val ...")
sayHelloWorld() } suspend fun sayHelloWorld() { withScope { launch { say("hello") } launch { say("world") } } } Kotlin Extension function

91
val ...")
sayHelloWorld() } suspend fun sayHelloWorld() { withScope { // this: CoroutineScope launch { say("hello") } launch { say("world") } } } Kotlin Extension function

92
val job = laun...")
} suspend fun sayHelloWorld() { coroutineScope { // this: CoroutineScope launch { say("hello") } launch { say("world") } } } Kotlin Extension function

93
val job = laun...")
} suspend fun sayHelloWorld() { coroutineScope { // this: CoroutineScope launch { say("hello") } launch { say("world") } } } Kotlin !

94
suspend fun sa...")
// this: CoroutineScope for (w in listOf("hello", "world")) { launch { say(w) } } } }

95
suspend fun sa...")
// this: CoroutineScope for (w in listOf("hello", "world")) { launch { say(w) } } } }

96
suspend fun sa...")
// this: CoroutineScope for (w in listOf("hello", "world")) { launchSay(w) } } } fun CoroutineScope.launchSay(w: String) = launch { say(w) }

97
suspend fun sa...")
// this: CoroutineScope for (w in listOf("hello", "world")) { launchSay(w) } } } fun CoroutineScope.launchSay(w: String) = launch { say(w) }

98
suspend fun sa...")
// this: CoroutineScope for (w in listOf("hello", "world")) { launchSay(w) } } } fun CoroutineScope.launchSay(w: String) = launch { say(w) } But name for all of it?

https://vorpus.org/blog/notes-on-structured-...")

")

101
https://vorpus.org/b...")

102
https://vorpus.org/b...")

103
https://vorpus.org/blo...")

104
https://vorpus.org/b...")

105
coroutineScope {
}
l...")
https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/

Parent always waits for ...")
cleanup • Never loose a working coroutine • Error propagation • Never loose an exception 106

everywhere?
Similar prob...")

108
")

109
async wit...")
… Python https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/ CoroutineScope / Job

110
async wit...")
True: incoming_connection = await server_socket.accept() nursery.start_soon(connection_handler, incoming_connection) Python https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/ launch

111
")

112
ServerSoc...")
scope = FiberScope.cancellable()) { while (...) { Socket s = listener.accept(); scope.schedule(() -> handle(s)); } } Java https://trio.discourse.group/t/project-loom-lightweight-concurrency-for-the-jvm/97 CoroutineScope / Job launch

113
")

114
var g err...")
/ Job

115
var g err...")
:= range urls { // Launch a goroutine to fetch the URL. url := url // https://golang.org/doc/faq#closures\_and\_goroutines g.Go(func() error { resp, err := http.Get(url) if err == nil { resp.Body.Close() } return err }) } Go https://godoc.org/golang.org/x/sync/errgroup launch

116
var g err...")
:= range urls { // Launch a goroutine to fetch the URL. url := url // https://golang.org/doc/faq#closures\_and\_goroutines g.Go(func() error { resp, err := http.Get(url) if err == nil { resp.Body.Close() } return err }) } // Wait for all HTTP fetches to complete. if err := g.Wait(); err == nil { fmt.Println("Successfully fetched all URLs.") } Go https://godoc.org/golang.org/x/sync/errgroup scope completion

117
Get rid of unstructured concurr...")
… }

118
Get rid of unstructured concurr...")
… }

https://medium.com/@elizarov/the-reason-to-...")

• Coroutines design document
https...")
https://kotlinlang.org/docs/reference/coroutines/coroutines-guide.html • Library source and issues https://github.com/Kotlin/kotlinx.coroutines • Ongoing improvement work! 120

Want to learn more?
Questions?
elizar...")
Elizarov relizarov 121