Structured Concurrency
Sustrik, Martin
We've got rid of GOTO and the spaghetti code it leads to in 1970's. However, we are encoutering very much the same problems when writing concurrent code even now, 50 years later. Programmers are still stuck deep in the callback hell or state machine hell. Concurrent code still looks like spaghetti. And while Go language provided a tool to write unspaghettified concurrent code to the masses, by no way are all the pieces of the puzzle in place. This talk focuses on what's missing. It explains where the current mainstream paradigms break the vital principles of encapsulation, separation of concerns and correspondence between layout of the code and the execution flow. It introduces a bunch of current approaches covered by the umbrella term of "structured concurrency" that attempt to solve these problems. The talk may be interesting to anyone who has to deal with concurrency, irrespective of the language they are using. Besides looking at the problems at hand, the talk will show examples of structured concurrency in Python (Trio) and C (libdill).
 Okay, so maybe this should be part of a retro computing track because it goes as far back as 1968. When Distra sent a memo talking about why go to is bad and why we should avoid it. The memo was then redistributed by Nikas Werf under the name go to consider harmful. And that's the original source of the consider harmful phrase. Okay, so what's wrong with the go to in the first place? Do we have to get some context? You can put it in different ways but this particular way of putting it is I shamelessly stole from Nathan O. Smith who wrote a nice blog post about it. And it goes like this. This is a program in Formatic which is a predecessor of Cobol and it's an unstructured language. So as you see there are no loops and nothing. It's not even invented. And you see a lot of jumps there. So you look at a program and you have no idea what's going on. Then you start drawing arrows like how the control flow looks like. And it looks like this. So you look at it and you have no idea what's going on. So what's wrong with that? You have different like constructs, like if, four, whatever, and go to and some of them are good, some of them are bad. And how should we know which ones are good and which ones are bad? And one way of thinking about it is let's say the construct is good if the control flow starts in the top and then it ends up in the bottom. And if you look at all those constructs, like all of them have this nice property that just goes from the top to the bottom. If you do an if block, it can do the if part or the else part but eventually it will arrive in the bottom. It's the same for four or function call or just sequential sequence of commands but not so for go to. For go to it just jumps somewhere and it never returns. It never reaches the bottom of the code block. So we like to believe that the functions are black boxes. Like we call a function and the function eventually returns. Like nice and easy to reason about. So we have a main function and it calls function foo, which then calls function bar but eventually it will return from bar and it will return to foo. And it's also nice for encapsulation because main doesn't have to know that foo calls bar because like you change the implementation, it calls something else, you don't care, it still returns back. But try to use a go to somewhere and suddenly that's no longer a case. It just somewhere in the nested function, it jumps somewhere else, never to return. And now here we have developer Bob who's very cautious, he doesn't use any go tos but he happens to use a library written by Alice who's very cautious as well. She doesn't use any go tos but she uses a library written by Colin who's careless and who uses a go to. And suddenly Bob is hurt by that because like now inside the dependency tree, just control flow just somewhere else and Bob's function call foo to foo never returns. Like all the assumptions he had about his code are broken. So this is kind of, it's not only like bad design. It's bad design plus plus because it's transitive. It takes only one dependency in your whole dependency tree and you are screwed. So what I was talking about was the old kind of go to that probably not many people here really remember. There were like big bad go tos which could jump around the code base like from a function to a different function and you don't see those anymore. But it's actually not that the long ago. When I started programming in 1984 I've got Sinclair's spectrum and it had like built-in basic and it actually looks very much like the flowmatic code I've shown before. There were go tos all around the place and it just jumped around. So you don't see this anymore but what we have now is this kind of thing. You go to which can jump only inside a function. So that's the go to in C. You can jump inside a function but there's no way to jump out. So it's still bad but it's at least not transitive. So the calling, the developer of nested dependency can really break your workflow. So now that I've explained the whole go to thing from 50 years ago, what does it have to do with modern programming thing? And I'm coming from the networking background. I'm an author of Zeramq and I've made several network libraries and the problem that occurs all the time is how to handle asynchronous calls basically. So this is what you have on wire. The following field is going to be 13 bytes long and then you have the actual data. And how are you going to process that? So the old school way of doing that was like classic Unix way where you launch a process for each connection and the process will not nice and sequential and just read this, read that and that was it. But then people realize that launching a process per connection is probably not a good thing because it's slow and takes a lot of resources. So we have to do better and so the threads were born. And the interesting thing is about threads is you can't really find out who invented them. But probably the person was actually to ashamed to. So what we have to deal with nowadays is this kind of thing. We have this async function which looks like a function but it's not really a function because you would expect it to do whatever is inside the block and then return as a normal function but it doesn't. It returns immediately and then at some later point the async part is done. So if you just look at this naively as if this was a normal function you would expect it to print one and then to print three and then to print five and four and two. But actually what you have is one, two and this part is executed three, four and then five. So it's weird. And yeah, it gets a good verse in JavaScript where basically those functions are anonymous and nested inside the code. And this looks like a normal structure code but it executes in very different ways. So the layout of the program has basically nothing to do with the order of execution. So what you see here is one, three, five, four, two. What's printed out is one, two, three, four, five. I guess. Well, okay. The other option to do it in is classic state machines where you remember what state you are in and the variable. And well, this is at least explicit about what it's doing. So you go, you are in this state. So you switch to a different state. You will launch a async call and at some later point the function is called again with the reading done message and the other part of the state machine is executed. But look at this code. It's terrible. And this is one slide. But if you look at the real implementation, it's like pages and pages of this kind of stuff. So nobody can really grasp it to that first site. So then enter go, enter go lang. So then the nice thing about go lang is that it introduced coroutines as a first class citizen. And you are back to the old Unix style programming where you basically launch a process for each connection and it's nice and sequential. And then the process is coroutine, which means it's cheap. The stack is at the beginning like two kilobytes and the context rate is pretty quick like 29 seconds or something. So it seems that we are done. But what's wrong with the go to? With go construct, watching of coroutine. You see it kind of looks like the old go because in one way it just continues. But in the background, somehow the control flow just jumps away to some different function. It's very similar to go in a way. And you have the same problem as we had before. So once again, Bob launches coroutine from Alice's library, which now still has a call from Collin's library and Collin does go and launches coroutine which is running in the background. And the call returns to Bob's code. Bob thinks, okay, the bar thing is done, but there's actually a coroutine running in the background. And Bob has no idea. So it's sensitive again. If any of your dependencies launches a coroutine in the background, you have the old problem from 50 years ago with this vile big bad go to. So how easy is it to fix? So one would say, okay, let's make coroutine scoped. So we have a scope, we launch a coroutine and when we get to the end of the scope, the coroutine will be canceled. And that's it. Except that that doesn't really work because look here, this is the classical code. So we have a server which accept connections and each connection is handled by a handler which is a separate coroutine. But if it was canceled at the end of the block, well, it would actually cancel immediately. Nothing would happen. So that's not good enough. So what we really need is two different kind of scopes. One is the normal scope and one is the better scope which cancels all the coroutines that were launched inside it. So this is in C. This is code using a library that I wrote called libdale. And basically you see that the two scopes here, there is a thing called a bundle. It's a bundle of coroutines. You initialize it. In the while, you launch any number of coroutines inside it. And once you want to shut down your server or something, you jump out of the loop, here at the break, and you close the bundle and that cancels all the coroutines. So you see it's indented like a nice structure. So there are several languages that implemented this, like this is in Python, the library code trial, which does the pretty similar thing except it's nice. So you open a thing, they call it nursery, which is the same as the bundle in the previous example. And you basically do the same thing. You launch the coroutines and once you get out of the block, all the coroutines are canceled. So is that it? Is that easy? So there are still some open problems here. We are not yet there. For example, there is a difference between how libdale handles the termination and how trial and error handles the termination. What trial does is you start your nursery, you launch three coroutines, and when you get to the end of the block, it waits. It waits till all of them are finished. When they are finished, it just continues. And if there is an error in any of them, you just write an exception. And when the exception reaches this point, it cancels all the other coroutines and then it continues. Libdale does it differently. So there you open a bundle, you learn some threads, and at some point you say close bundle, and at that point the coroutines are closed. So the difference really is that in this case you have kind of like main thread, which is in control of all other threads. In the other case, all of them are created equal, and there is no like the controller and the controller. So which way is better? It's pretty hard to tell because in the trial version, you have this nice error propagation where you throw an error and the error gets to the caller coroutine and it bubbles up until it reaches main. But the problem there is if that happens, then it kind of looks like that one of the coroutines can cancel its siblings and even its parents, and we have just very like unstructured way of doing stuff. So that's not nice about it. So it would be nice to know like whether we can combine the two approaches that we would have nice error handling and kind of nice encapsulation where coroutine cannot cancel its parents. So some other problems that are still unsolved or partially solved. How will you handle timeouts? So one way is to use something like as a go links context where you have an object on which you can call, cancel the timeout function and it will cancel after it or you can pass it manually to each function. Who knows if it's better? Great spirit. That's a hard problem. So for example, you want to shut down your server and you say, okay, I am giving you like five seconds to finish whatever stuff you are doing to close the connection decently and then shut down. So we don't have a good story for that. Okay, I'm sorry to interrupt. We're out of time unfortunately. The timer started a bit late. So thank you very much for the talk.
 Okay, so maybe this should be part of a retro computing track because it goes as far back  as 1968.  When Distra sent a memo talking about why go to is bad and why we should avoid it.  The memo was then redistributed by Nikas Werf under the name go to consider harmful.  And that's the original source of the consider harmful phrase.  Okay, so what's wrong with the go to in the first place?  Do we have to get some context?  You can put it in different ways but this particular way of putting it is I shamelessly  stole from Nathan O. Smith who wrote a nice blog post about it.  And it goes like this.  This is a program in Formatic which is a predecessor of Cobol and it's an unstructured language.  So as you see there are no loops and nothing.  It's not even invented.  And you see a lot of jumps there.  So you look at a program and you have no idea what's going on.  Then you start drawing arrows like how the control flow looks like.  And it looks like this.  So you look at it and you have no idea what's going on.  So what's wrong with that?  You have different like constructs, like if, four, whatever, and go to and some of them  are good, some of them are bad.  And how should we know which ones are good and which ones are bad?  And one way of thinking about it is let's say the construct is good if the control flow  starts in the top and then it ends up in the bottom.  And if you look at all those constructs, like all of them have this nice property that  just goes from the top to the bottom.  If you do an if block, it can do the if part or the else part but eventually it will arrive  in the bottom.  It's the same for four or function call or just sequential sequence of commands but not  so for go to.  For go to it just jumps somewhere and it never returns.  It never reaches the bottom of the code block.  So we like to believe that the functions are black boxes.  Like we call a function and the function eventually returns.  Like nice and easy to reason about.  So we have a main function and it calls function foo, which then calls function bar but eventually  it will return from bar and it will return to foo.  And it's also nice for encapsulation because main doesn't have to know that foo calls bar  because like you change the implementation, it calls something else, you don't care,  it still returns back.  But try to use a go to somewhere and suddenly that's no longer a case.  It just somewhere in the nested function, it jumps somewhere else, never to return.  And now here we have developer Bob who's very cautious, he doesn't use any go tos but he  happens to use a library written by Alice who's very cautious as well.  She doesn't use any go tos but she uses a library written by Colin who's careless and  who uses a go to.  And suddenly Bob is hurt by that because like now inside the dependency tree, just control  flow just somewhere else and Bob's function call foo to foo never returns.  Like all the assumptions he had about his code are broken.  So this is kind of, it's not only like bad design.  It's bad design plus plus because it's transitive.  It takes only one dependency in your whole dependency tree and you are screwed.  So what I was talking about was the old kind of go to that probably not many people here  really remember.  There were like big bad go tos which could jump around the code base like from a function  to a different function and you don't see those anymore.  But it's actually not that the long ago.  When I started programming in 1984 I've got Sinclair's spectrum and it had like built-in  basic and it actually looks very much like the flowmatic code I've shown before.  There were go tos all around the place and it just jumped around.  So you don't see this anymore but what we have now is this kind of thing.  You go to which can jump only inside a function.  So that's the go to in C. You can jump inside a function but there's no way to jump out.  So it's still bad but it's at least not transitive.  So the calling, the developer of nested dependency can really break your workflow.  So now that I've explained the whole go to thing from 50 years ago, what does it have  to do with modern programming thing?  And I'm coming from the networking background.  I'm an author of Zeramq and I've made several network libraries and the problem that occurs  all the time is how to handle asynchronous calls basically.  So this is what you have on wire.  The following field is going to be 13 bytes long and then you have the actual data.  And how are you going to process that?  So the old school way of doing that was like classic Unix way where you launch a process  for each connection and the process will not nice and sequential and just read this, read  that and that was it.  But then people realize that launching a process per connection is probably not a good thing  because it's slow and takes a lot of resources.  So we have to do better and so the threads were born.  And the interesting thing is about threads is you can't really find out who invented  them.  But probably the person was actually to ashamed to.  So what we have to deal with nowadays is this kind of thing.  We have this async function which looks like a function but it's not really a function  because you would expect it to do whatever is inside the block and then return as a normal  function but it doesn't.  It returns immediately and then at some later point the async part is done.  So if you just look at this naively as if this was a normal function you would expect  it to print one and then to print three and then to print five and four and two.  But actually what you have is one, two and this part is executed three, four and then  five.  So it's weird.  And yeah, it gets a good verse in JavaScript where basically those functions are anonymous  and nested inside the code.  And this looks like a normal structure code but it executes in very different ways.  So the layout of the program has basically nothing to do with the order of execution.  So what you see here is one, three, five, four, two.  What's printed out is one, two, three, four, five.  I guess.  Well, okay.  The other option to do it in is classic state machines where you remember what state you  are in and the variable.  And well, this is at least explicit about what it's doing.  So you go, you are in this state.  So you switch to a different state.  You will launch a async call and at some later point the function is called again with the  reading done message and the other part of the state machine is executed.  But look at this code.  It's terrible.  And this is one slide.  But if you look at the real implementation, it's like pages and pages of this kind of  stuff.  So nobody can really grasp it to that first site.  So then enter go, enter go lang.  So then the nice thing about go lang is that it introduced coroutines as a first class  citizen.  And you are back to the old Unix style programming where you basically launch a process for each  connection and it's nice and sequential.  And then the process is coroutine, which means it's cheap.  The stack is at the beginning like two kilobytes and the context rate is pretty quick like  29 seconds or something.  So it seems that we are done.  But what's wrong with the go to?  With go construct, watching of coroutine.  You see it kind of looks like the old go because in one way it just continues.  But in the background, somehow the control flow just jumps away to some different function.  It's very similar to go in a way.  And you have the same problem as we had before.  So once again, Bob launches coroutine from Alice's library, which now still has a call  from Collin's library and Collin does go and launches coroutine which is running in the  background.  And the call returns to Bob's code.  Bob thinks, okay, the bar thing is done, but there's actually a coroutine running in the  background.  And Bob has no idea.  So it's sensitive again.  If any of your dependencies launches a coroutine in the background, you have the old problem  from 50 years ago with this vile big bad go to.  So how easy is it to fix?  So one would say, okay, let's make coroutine scoped.  So we have a scope, we launch a coroutine and when we get to the end of the scope, the  coroutine will be canceled.  And that's it.  Except that that doesn't really work because look here, this is the classical code.  So we have a server which accept connections and each connection is handled by a handler  which is a separate coroutine.  But if it was canceled at the end of the block, well, it would actually cancel immediately.  Nothing would happen.  So that's not good enough.  So what we really need is two different kind of scopes.  One is the normal scope and one is the better scope which cancels all the coroutines that  were launched inside it.  So this is in C.  This is code using a library that I wrote called libdale.  And basically you see that the two scopes here, there is a thing called a bundle.  It's a bundle of coroutines.  You initialize it.  In the while, you launch any number of coroutines inside it.  And once you want to shut down your server or something, you jump out of the loop, here  at the break, and you close the bundle and that cancels all the coroutines.  So you see it's indented like a nice structure.  So there are several languages that implemented this, like this is in Python, the library  code trial, which does the pretty similar thing except it's nice.  So you open a thing, they call it nursery, which is the same as the bundle in the previous  example.  And you basically do the same thing.  You launch the coroutines and once you get out of the block, all the coroutines are canceled.  So is that it?  Is that easy?  So there are still some open problems here.  We are not yet there.  For example, there is a difference between how libdale handles the termination and how  trial and error handles the termination.  What trial does is you start your nursery, you launch three coroutines, and when you get  to the end of the block, it waits.  It waits till all of them are finished.  When they are finished, it just continues.  And if there is an error in any of them, you just write an exception.  And when the exception reaches this point, it cancels all the other coroutines and then  it continues.  Libdale does it differently.  So there you open a bundle, you learn some threads, and at some point you say close bundle,  and at that point the coroutines are closed.  So the difference really is that in this case you have kind of like main thread, which is  in control of all other threads.  In the other case, all of them are created equal, and there is no like the controller  and the controller.  So which way is better?  It's pretty hard to tell because in the trial version, you have this nice error propagation  where you throw an error and the error gets to the caller coroutine and it bubbles up  until it reaches main.  But the problem there is if that happens, then it kind of looks like that one of the  coroutines can cancel its siblings and even its parents, and we have just very like unstructured  way of doing stuff.  So that's not nice about it.  So it would be nice to know like whether we can combine the two approaches that we would  have nice error handling and kind of nice encapsulation where coroutine cannot cancel  its parents.  So some other problems that are still unsolved or partially solved.  How will you handle timeouts?  So one way is to use something like as a go links context where you have an object on  which you can call, cancel the timeout function and it will cancel after it or you can pass  it manually to each function.  Who knows if it's better?  Great spirit.  That's a hard problem.  So for example, you want to shut down your server and you say, okay, I am giving you like  five seconds to finish whatever stuff you are doing to close the connection decently  and then shut down.  So we don't have a good story for that.  Okay, I'm sorry to interrupt.  We're out of time unfortunately.  The timer started a bit late.  So thank you very much for the talk.
Structured_Concurrency_Finding_our_way_out_of_callback_hell_llYv1AitDH8_0_179.png Structured_Concurrency_Finding_our_way_out_of_callback_hell_llYv1AitDH8_180_419.png Structured_Concurrency_Finding_our_way_out_of_callback_hell_llYv1AitDH8_420_767.png Structured_Concurrency_Finding_our_way_out_of_callback_hell_llYv1AitDH8_768_983.png Structured_Concurrency_Finding_our_way_out_of_callback_hell_llYv1AitDH8_984_2039.png Structured_Concurrency_Finding_our_way_out_of_callback_hell_llYv1AitDH8_2040_2291.png Structured_Concurrency_Finding_our_way_out_of_callback_hell_llYv1AitDH8_2292_3107.png Structured_Concurrency_Finding_our_way_out_of_callback_hell_llYv1AitDH8_3108_3815.png Structured_Concurrency_Finding_our_way_out_of_callback_hell_llYv1AitDH8_3816_4811.png Structured_Concurrency_Finding_our_way_out_of_callback_hell_llYv1AitDH8_4812_6455.png Structured_Concurrency_Finding_our_way_out_of_callback_hell_llYv1AitDH8_6456_7535.png Structured_Concurrency_Finding_our_way_out_of_callback_hell_llYv1AitDH8_7536_8303.png Structured_Concurrency_Finding_our_way_out_of_callback_hell_llYv1AitDH8_8304_9095.png Structured_Concurrency_Finding_our_way_out_of_callback_hell_llYv1AitDH8_9096_9155.png Structured_Concurrency_Finding_our_way_out_of_callback_hell_llYv1AitDH8_9156_10127.png Structured_Concurrency_Finding_our_way_out_of_callback_hell_llYv1AitDH8_10128_11279.png Structured_Concurrency_Finding_our_way_out_of_callback_hell_llYv1AitDH8_11280_12155.png Structured_Concurrency_Finding_our_way_out_of_callback_hell_llYv1AitDH8_12156_13271.png Structured_Concurrency_Finding_our_way_out_of_callback_hell_llYv1AitDH8_13272_14219.png Structured_Concurrency_Finding_our_way_out_of_callback_hell_llYv1AitDH8_14220_14771.png Structured_Concurrency_Finding_our_way_out_of_callback_hell_llYv1AitDH8_14772_15491.png Structured_Concurrency_Finding_our_way_out_of_callback_hell_llYv1AitDH8_15492_15887.png Structured_Concurrency_Finding_our_way_out_of_callback_hell_llYv1AitDH8_15888_15995.png Structured_Concurrency_Finding_our_way_out_of_callback_hell_llYv1AitDH8_15996_16247.png Structured_Concurrency_Finding_our_way_out_of_callback_hell_llYv1AitDH8_16248_17111.png Structured_Concurrency_Finding_our_way_out_of_callback_hell_llYv1AitDH8_17112_18071.png Structured_Concurrency_Finding_our_way_out_of_callback_hell_llYv1AitDH8_18072_18683.png Structured_Concurrency_Finding_our_way_out_of_callback_hell_llYv1AitDH8_18684_18887.png Structured_Concurrency_Finding_our_way_out_of_callback_hell_llYv1AitDH8_18888_19703.png Structured_Concurrency_Finding_our_way_out_of_callback_hell_llYv1AitDH8_19704_20279.png Structured_Concurrency_Finding_our_way_out_of_callback_hell_llYv1AitDH8_20280_21683.png Structured_Concurrency_Finding_our_way_out_of_callback_hell_llYv1AitDH8_21684_22871.png Structured_Concurrency_Finding_our_way_out_of_callback_hell_llYv1AitDH8_22872_22919.png Structured_Concurrency_Finding_our_way_out_of_callback_hell_llYv1AitDH8_22920_22967.png Structured_Concurrency_Finding_our_way_out_of_callback_hell_llYv1AitDH8_22968_23080.png
Okay, so maybe this should be part of a retro computing track because it goes as far back as 1968. When Distra sent a memo talking about why go to is bad and why we should avoid it. The memo was then redistributed by Nikas Werf under the name go to consider harmful. And that's the original source of the consider harmful phrase. Okay, so what's wrong with the go to in the first place? Do we have to get some context? You can put it in different ways but this particular way of putting it is I shamelessly stole from Nathan O. Smith who wrote a nice blog post about it. And it goes like this. This is a program in Formatic which is a predecessor of Cobol and it's an unstructured language. So as you see there are no loops and nothing. It's not even invented. And you see a lot of jumps there. So you look at a program and you have no idea what's going on. Then you start drawing arrows like how the control flow looks like. And it looks like this. So you look at it and you have no idea what's going on. So what's wrong with that? You have different like constructs, like if, four, whatever, and go to and some of them are good, some of them are bad. And how should we know which ones are good and which ones are bad? And one way of thinking about it is let's say the construct is good if the control flow starts in the top and then it ends up in the bottom. And if you look at all those constructs, like all of them have this nice property that just goes from the top to the bottom. If you do an if block, it can do the if part or the else part but eventually it will arrive in the bottom. It's the same for four or function call or just sequential sequence of commands but not so for go to. For go to it just jumps somewhere and it never returns. It never reaches the bottom of the code block. So we like to believe that the functions are black boxes. Like we call a function and the function eventually returns. Like nice and easy to reason about. So we have a main function and it calls function foo, which then calls function bar but eventually it will return from bar and it will return to foo. And it's also nice for encapsulation because main doesn't have to know that foo calls bar because like you change the implementation, it calls something else, you don't care, it still returns back. But try to use a go to somewhere and suddenly that's no longer a case. It just somewhere in the nested function, it jumps somewhere else, never to return. And now here we have developer Bob who's very cautious, he doesn't use any go tos but he happens to use a library written by Alice who's very cautious as well. She doesn't use any go tos but she uses a library written by Colin who's careless and who uses a go to. And suddenly Bob is hurt by that because like now inside the dependency tree, just control flow just somewhere else and Bob's function call foo to foo never returns. Like all the assumptions he had about his code are broken. So this is kind of, it's not only like bad design. It's bad design plus plus because it's transitive. It takes only one dependency in your whole dependency tree and you are screwed. So what I was talking about was the old kind of go to that probably not many people here really remember. There were like big bad go tos which could jump around the code base like from a function to a different function and you don't see those anymore. But it's actually not that the long ago. When I started programming in 1984 I've got Sinclair's spectrum and it had like built-in basic and it actually looks very much like the flowmatic code I've shown before. There were go tos all around the place and it just jumped around. So you don't see this anymore but what we have now is this kind of thing. You go to which can jump only inside a function. So that's the go to in C. You can jump inside a function but there's no way to jump out. So it's still bad but it's at least not transitive. So the calling, the developer of nested dependency can really break your workflow. So now that I've explained the whole go to thing from 50 years ago, what does it have to do with modern programming thing? And I'm coming from the networking background. I'm an author of Zeramq and I've made several network libraries and the problem that occurs all the time is how to handle asynchronous calls basically. So this is what you have on wire. The following field is going to be 13 bytes long and then you have the actual data. And how are you going to process that? So the old school way of doing that was like classic Unix way where you launch a process for each connection and the process will not nice and sequential and just read this, read that and that was it. But then people realize that launching a process per connection is probably not a good thing because it's slow and takes a lot of resources. So we have to do better and so the threads were born. And the interesting thing is about threads is you can't really find out who invented them. But probably the person was actually to ashamed to. So what we have to deal with nowadays is this kind of thing. We have this async function which looks like a function but it's not really a function because you would expect it to do whatever is inside the block and then return as a normal function but it doesn't. It returns immediately and then at some later point the async part is done. So if you just look at this naively as if this was a normal function you would expect it to print one and then to print three and then to print five and four and two. But actually what you have is one, two and this part is executed three, four and then five. So it's weird. And yeah, it gets a good verse in JavaScript where basically those functions are anonymous and nested inside the code. And this looks like a normal structure code but it executes in very different ways. So the layout of the program has basically nothing to do with the order of execution. So what you see here is one, three, five, four, two. What's printed out is one, two, three, four, five. I guess. Well, okay. The other option to do it in is classic state machines where you remember what state you are in and the variable. And well, this is at least explicit about what it's doing. So you go, you are in this state. So you switch to a different state. You will launch a async call and at some later point the function is called again with the reading done message and the other part of the state machine is executed. But look at this code. It's terrible. And this is one slide. But if you look at the real implementation, it's like pages and pages of this kind of stuff. So nobody can really grasp it to that first site. So then enter go, enter go lang. So then the nice thing about go lang is that it introduced coroutines as a first class citizen. And you are back to the old Unix style programming where you basically launch a process for each connection and it's nice and sequential. And then the process is coroutine, which means it's cheap. The stack is at the beginning like two kilobytes and the context rate is pretty quick like 29 seconds or something. So it seems that we are done. But what's wrong with the go to? With go construct, watching of coroutine. You see it kind of looks like the old go because in one way it just continues. But in the background, somehow the control flow just jumps away to some different function. It's very similar to go in a way. And you have the same problem as we had before. So once again, Bob launches coroutine from Alice's library, which now still has a call from Collin's library and Collin does go and launches coroutine which is running in the background. And the call returns to Bob's code. Bob thinks, okay, the bar thing is done, but there's actually a coroutine running in the background. And Bob has no idea. So it's sensitive again. If any of your dependencies launches a coroutine in the background, you have the old problem from 50 years ago with this vile big bad go to. So how easy is it to fix? So one would say, okay, let's make coroutine scoped. So we have a scope, we launch a coroutine and when we get to the end of the scope, the coroutine will be canceled. And that's it. Except that that doesn't really work because look here, this is the classical code. So we have a server which accept connections and each connection is handled by a handler which is a separate coroutine. But if it was canceled at the end of the block, well, it would actually cancel immediately. Nothing would happen. So that's not good enough. So what we really need is two different kind of scopes. One is the normal scope and one is the better scope which cancels all the coroutines that were launched inside it. So this is in C. This is code using a library that I wrote called libdale. And basically you see that the two scopes here, there is a thing called a bundle. It's a bundle of coroutines. You initialize it. In the while, you launch any number of coroutines inside it. And once you want to shut down your server or something, you jump out of the loop, here at the break, and you close the bundle and that cancels all the coroutines. So you see it's indented like a nice structure. So there are several languages that implemented this, like this is in Python, the library code trial, which does the pretty similar thing except it's nice. So you open a thing, they call it nursery, which is the same as the bundle in the previous example. And you basically do the same thing. You launch the coroutines and once you get out of the block, all the coroutines are canceled. So is that it? Is that easy? So there are still some open problems here. We are not yet there. For example, there is a difference between how libdale handles the termination and how trial and error handles the termination. What trial does is you start your nursery, you launch three coroutines, and when you get to the end of the block, it waits. It waits till all of them are finished. When they are finished, it just continues. And if there is an error in any of them, you just write an exception. And when the exception reaches this point, it cancels all the other coroutines and then it continues. Libdale does it differently. So there you open a bundle, you learn some threads, and at some point you say close bundle, and at that point the coroutines are closed. So the difference really is that in this case you have kind of like main thread, which is in control of all other threads. In the other case, all of them are created equal, and there is no like the controller and the controller. So which way is better? It's pretty hard to tell because in the trial version, you have this nice error propagation where you throw an error and the error gets to the caller coroutine and it bubbles up until it reaches main. But the problem there is if that happens, then it kind of looks like that one of the coroutines can cancel its siblings and even its parents, and we have just very like unstructured way of doing stuff. So that's not nice about it. So it would be nice to know like whether we can combine the two approaches that we would have nice error handling and kind of nice encapsulation where coroutine cannot cancel its parents. So some other problems that are still unsolved or partially solved. How will you handle timeouts? So one way is to use something like as a go links context where you have an object on which you can call, cancel the timeout function and it will cancel after it or you can pass it manually to each function. Who knows if it's better? Great spirit. That's a hard problem. So for example, you want to shut down your server and you say, okay, I am giving you like five seconds to finish whatever stuff you are doing to close the connection decently and then shut down. So we don't have a good story for that. Okay, I'm sorry to interrupt. We're out of time unfortunately. The timer started a bit late. So thank you very much for the talk.
Okay, so what's wrong with the go to in the first unstructured language. So as you see there are no control flow looks like. And it looks like this. It never reaches the bottom of the code block. So library written by Colin who's careless and who libraries and the problem that occurs all the basically. So this is what you have on wire. The So what we have to deal with nowadays is this executes in very different ways. So the layout of state machine is executed. But look at this code. it seems that we are done. But what's wrong with similar to go in a way. And you have the same now still has a call from Collin's library and the old problem from 50 years ago with this vile will be canceled. And that's it. Except that that launched inside it. So this is in C. This is code that implemented this, like this is in Python, that easy? So there are still some open problems exception. And when the exception reaches this and kind of nice encapsulation where coroutine So thank you very much for the talk.