 Okay guys, let's start. So today we're gonna talk about async.io today and what might happen to tomorrow. My name is Yuri, Yuri Salavanov. I've been a core developer for about five years and during these five years I've been lucky enough to work on some pretty interesting stuff in Python that includes the async.io wait syntax, asynchronous generators, asynchronous comprehensions, lately context variables. I'm the lead maintainer of async.io, created UV loop library a while ago which is an alternative event loop implementation for async.io. Like it's used pretty wildly now. A lot of big companies used in production. I co-created the async.pg library with Elvis who is also here today. That's an asynchronous passgres scale driver. And I work on HDB which is an object relational database. It's a new thing. I, unfortunately I won't be able to talk about HDB during this talk but if you have any questions after the talk I'll be happy to answer them. And of course you can follow me on Twitter and github, my nickname there is 1st1. So before we get to the meat of the talk, let's briefly discuss how async.io was born. It's history because it explains some of the idiosyncrasies that it has and it's designed. So Guida started to work on async.io around Python 3.3. We just landed yield from syntax and partly we actually landed it for something like async.io. So Guida was working on some asynchronous stuff back when he was working on Google and back then we actually had two different approaches to asynchronous concurrent code in Python. That was greenlets and stackless Python and those frameworks still exist. That's G went to the bandplot. And also libraries like Twisted and Tornado which were pretty popular back these days. And they also influenced async.io in pretty significant way. For instance, transfers and protocols and callbacks, they kind of all came from Twisted. But it was obvious that we don't really want to move, don't really want to choose the G went way of doing things, we want the things to be explicit. So in Python 3.4 async.io lands to Python and becomes part of the standard library. It was more provisional and what that meant is that we could actually push new features and new things to async.io in bugfix releases, new APIs, we could even break things but we didn't really do that. Async.io was focused on mostly low level APIs so we recreated a lot of stuff from Twisted. We had protocols, we had transports, futures which are basically Twisted deferred objects with some modifications. And it also had coroutines via the yield from syntax and it was also a novel idea back then. Of course it had some high level APIs like streams and sub-processes that were supposed to work with yield from coroutines but because that was kind of a new thing, some of those APIs weren't perfect. In Python 3.5 we got the async await syntax and that was, I guess, first public acknowledgement that async trans-programming can be a first class feature in Python. Async.io was still provisional so we were trying to evolve it and even in bugfix releases. Received a bunch of new APIs but nothing significant, nothing that stands out. Back then I created UV loop and that was also pretty interesting because it was a first example of a library that kind of allowed you to swap the standard event loop with third-party event loop implementation completely and everything just continued to work magically. And back then also David Beesley created Curio which was a pretty interesting moment because he decided that we don't really need transfers, we don't really need protocols and callbacks and that you can basically write programs just with async await without all that craft. At first the idea kind of seemed ridiculous to a lot of us but then we realized that yes, he is right. It's actually perfectly reasonable and perfectly possible to write programs with async await and what's more important is that those programs are easier to read and easier to maintain. So we're looking at Curio and thinking what kind of features we can actually steal from it and kind of port to async.io, why people like Curio so much. So both me, Guido and other core developers were kind of paying attention. So in Python 3.6 async has stopped being provisional that means that the evolution cycle for it got longer. We had to wait for a major release. We got asynchronous generators and asynchronous comprehensions landed in Python and we also fixed get event loop function that basically a thanks to Curio because before Python 3.6 get event loop was really, really weird. It could return a new event loop or random event loop was basically completely unpredictable. It all depended on the current policy and even the default policy and I think I've had some weird bugs in it. So you couldn't really use this function to get the currently running event loop reliably and that in part kind of created this pattern in async.io that you had to care about the event loop. You had to use it even when you use the async.await you kind of always bust around the event loop to all libraries like A, H, D, T, B. So once we fixed this function and we fixed it in such a way that it always returned the currently running event loop if you call this function from a coroutine because when you call it from a coroutine there is only one event loop that currently runs this coroutine. So it's pretty well determined. Once we fixed it we kind of started to tell people well don't actually bust event loop around in async.await applications just you just don't need that. Just use this function because all other libraries and APIs use that function anyways it all kind of just started to work as expected correctly. We added a bunch of low level APIs to async.io of course in 36 but again nothing significant and I was another interesting point back then Nathaniel Smith created this library it's called Trio and just like Curio was developed basically from scratch. It's also only async and await library doesn't have any callbacks or protocols. But Nathaniel kind of focused more on predictability of your async.await code and we'll actually talk about Trio a little bit later. And Python 37 which is basically a couple of months old now the headline feature for async.io is context-wise and we'll also cover it a little bit later. We also converted async.io's own code to async.await. So if you look at async.io source code in Python 37 you will see that it uses async.await and slightly more readable and modern. And we finally landed the async.await.run function and again I first thought about that function when I saw Curio and wanted to actually land this function in 36 but didn't have time to properly do that before the feature freeze period. And very few other interesting APIs that we added in 37 actually a lot of them just to highlight if you now have a send file so you can send files in an optimized way using the OS send file system call on pretty much any transport in async.io you can upgrade transports from an unencrypted connection to a TLS connection, a few high level and top level functions like create task and get running loop and buffered protocol which is a super low level API but essentially some protocols that involve a lot of copying or just basically pass through the data. With buffered protocol you have a very tight control over buffers and you can avoid extra copies and that can lead to some pretty significant performance improvements. Now let's talk about async.await and then in modern async.io and a lot of people think that async.io is pretty complex and of course they are right but one problem with async.io, with current async.io is that we explain it wrong and how we should explain async.io is that it basically has two levels. One is normal, high level of async.io and it includes functions like run, gather, create tasks, sleep, streams API, sub-processes API, logs, all those primitives that you can use from async.await and then it has a hardcore layer. That's basically for people who write frameworks, who write libraries, low level details, interface with C code and that's pretty much all of the internals of async.io, protocols, transfers, all methods on event loop and even event loop itself. Async.io futures, basically you don't really need to know about them to program with modern async.io if you are creating an AAUCTP application, let's say you really don't need to bother with the event loop, you just use async.await.io. At this point of time, your application will kinda look like a tree or a query application because you just don't use those low level APIs. It's quickly about async.io run. This was the popular pattern to run an async.io application. You basically had some coroutine, you import async.io and then you had to do this weird dance. You had to get the current or create a new event loop with the getEventLoop function. You wanted to run this coroutine with this run until complete method and then you closed the event loop. The problem with this code isn't just that it's long and it's hard to type, it's that it's also incorrect because when you type loop close, it doesn't really close or cancel currently active asynchronous tasks, background asynchronous tasks. And if you have asynchronous generators in your code, those won't be canceled as well. And that's pretty important because those asynchronous generators, they might have a lot of logic in them. They might have a tri-final statement. You actually want this code to run. But if you just close the loop, nothing will happen. So starting with Python 3.7, that's how you would run your program. And that's the recommended way to run an async.io program. You just use the async.io run function. And async.io run isn't a simple function. That's basically the source code of async.io run without some extra checks and comments. You don't need to read it. It's only here to kind of show that a lot of stuff is actually happening in async.io run. It's really hard to get all the details right, right this function. So just use it. Ideally, your async.io program should have just one coroutine in which you initialize your program and you run that coroutine with async.io run. Just don't use those loop run until complete and run forever method. There is, they are no longer necessary basically. And try to use async.io for everything. You shouldn't really try to use the low level APIs. I cannot really stress this point enough. At this point of time, you really don't need to know about futures and callbacks. Just use async.io. Don't pass a reference to the currently running event loop to any kind of libraries. They will be able to get it reliably without your help. So when we're working on async.io run function, there was another pattern in async.io and that's basically around the run forever function and servers. So when you create a server, you kind of want to create it and accept incoming connections and you want event loop to just run it forever. Just execute it. This is an actual example from Python documentation how you would run it. And you see a lot of low level code and it's really hard to get a sense of what this code is doing. So in Python 3.7, we added a few more APIs to server. Now, servers are asynchronous context managers. So when they exit the context, they are appropriately closed. And we also have the serve forever method which you can just use inside that statement and you can use async.io run. So what we have here is essentially the same code, equivalent code, but it's twice as short and it's actually more correct. And then we have this function and it's kind of low level. It kind of goes against what I was saying that you shouldn't care about event loop. There are some, so sometimes you have to care about it only because we don't have async await versions of all APIs yet. In Python 3.8 we'll try to fix that. So for instance, if you want to listen for an OS signal you have to use this add signal handler function on the event loop. So if you have a code like this you should basically rewrite it to use async.io run and you use this new get running loop function that is allowed to be used only from async await. And just a couple of don'ts. Don't use the courting decorator. Don't use the yield from. We'll just deprecate it in Python 3.8 and remove it in 3.8 or 3.9 or maybe Python 4. We'll just remove this whole legacy thing forever. And don't use, again, don't use low level APIs. You don't really need them. So now let's talk about good async await code and what code is good. Maybe the one that you can write quickly or maintainable code or beautiful or robust and fast. Many of those things are subjective and some of those things are highly subjective. But the last two robust and fast those you can actually measure and how you measure them. The only way to measure them correctly is to do monitoring in production. And the sad thing here is that it's not really possible to monitor correctly async at your code in Python 3.6 and prior. In Python 3.7 we have now context variables and now I'll try to explain you why it was such a big problem before. So in order to understand why we needed context variables in Python 3.7 here is how event loop sees your program. For an event loop it's just basically a sequence of callbacks and once it is done with the current sequence of callbacks there is another sequence of callbacks another and another one. So all that event loop knows about it is basically those callbacks completely not connected with each other. Whereas in reality usually you have some structure in your program. Those callbacks might be all related to some asynchronous task or something like that. So in Python 3.7 we decided to address this problem we decided that we need this structure in our async program to at least monitor them to at least profile them reliably. So I started to work on context bars and there are a couple of PEPs that you might be interested in. That's PEP 550, that's how it's all started. Besides just fixing the async IO or async await problem it also tried to address this problem for generators and it was too complex. So we created a spin of PEP 5.67 with a slightly different API but simpler design. There are around 900 emails on Python ideas and Python dev because suddenly everybody had an opinion about context variables and async await was really a tough battle but we finally got it in. So first of all it's magic. And like with any magic you shouldn't just use it because you want to because sometimes a global variable is good enough or you can just use a keyword argument to your function and pass things explicitly but there are some areas where you can't really do that. They're shipped with Python 37 part of standard library. Async IO supports them out of the box and I think 3O supports them now out of the box as well. The decimal module uses it. So that was actually a bug I think before because in Python 36 and earlier if you do some decimal calculations and two different asynchronous tasks run in parallel and you have different decimal context all those decimal context will be mixed up and you would get the wrong calculation. So in Python 37 we fixed it. So because context variables aren't really a syntax extension you have to work with them programmatically so you import the context. Varus module you create, you declare a context variable programmatically you just create an object with a name you use the set function to assign a value to a context variable and you use the get method to look up the value, the current value for this context variable. So if we get back to async IO and how event loop sees your program and the structure of it. If you actually create a context variable and just assign some random number to it in every top level asynchronous task in your application you will see a picture like this. So basically you will be able to trace the, to trace that number, to trace the origin of this callback throughout the execution. So you can use context variables for monitoring obviously for profiling, for tracing. You can use it for localization. For instance you, if you know that the current user is an English or German speaking guy you can just put that language to a context variable and you will always know that. You can use it for security, you can put the current user ID or current security capabilities in the context variable and you will be able to read those from basically any point in your application. You can use it obviously for debug you can just create a temporary context variable put something into it and see if it reads in some callback or some asynchronous function. Basically do check if the things are connected something like that. And you can use it to store execution context for for instance decimal context or numpy error context will use context variables to work correctly in async await. But the big question is what we have in mind for async i.o in Python 3.8 and maybe Python 3.9 and here I think it's a good time to start to talk about trio. So trio was created by Nathaniel Smith around Python 3.6 and it's also designed from scratch. It's incompatible with async i.o it has completely different design under the hood. I think there is some inter-op layer I'm not sure how well it works right now and trio has very hard focus on usability and readability of the code. So basically Nathaniel is obsessed with allowing you to control your code with the highest precision possible and trio I think got many things right. And there is a YouTube talk of Nathaniel explaining trio and why he created trio at Bycon US a few months ago and if you haven't seen that talk please do it. It's an amazing talk. And actually I used couple of slides with Nathaniel permissions from his talk. So this is how programs looked like in 1958. So basically you have a line number and you have a sequence of codes or commands and you could actually jump from one line to another and if you visualize all those jumps in your program you basically see something like that. So it was completely untraceable and then you wouldn't be actually able to tell what this program is doing just by glancing at it. And 10 years later Dijkstra wrote a letter which was titled go to state and consider it harmful. And came up with this idea of structured programming and the idea of structured programming and that is that you have some primitives that you can use to build your programs with. You can have an if block, you can have a loop, you can have loops, you can have function calls and the idea is that there is only one entrance point and there is only one exit point in your logic. So the code is always predictable. There is no go to, there is no this random jumps in the program. And if you really think about go to and you think about asynchronous programming you will see a lot of parallels. If you use in async IEO, if you use create task function it will create a task and it will run in parallel and you have no control over that task. If it fails with an exception nothing will happen for you. You will probably see it in the logs but you can't really react to the exception. So back to trio. In trio Nathaniel added this concept of nursery nursery is the only way to spawn some asynchronous task in trio. It's a funny name I think because tasks are born in nursery. They die in nursery basically. It's not possible to graduate from nursery. Yeah it's a dark place but that's the point. That's kind of the whole point of nurseries is that once you go through this asynchronous with block you are sure that all your tasks are done. If one of them fails the other tasks will be canceled and the exception will be propagated correctly. So you can surround this async with statement with the try accept block and you will get your logic back. So there is no go to here. There is some out of order execution because those two courtings will run in parallel in this example but at the end of the async with block they both will be done and that's a guarantee. So in trio there is almost no out of order execution and you can always trace the control flow. Exceptions are never lost. So it's up to you if you want to ignore the exception by default you have to handle them. And therefore with and try blocks always work and I think that this approach kind of solve this go to problem and concurrency. Your program becomes traceable. So if you go back to async I even this whole mess of callbacks and allocate resources and event looping imagine how an HTTP client library might look like. So it probably has some HTTP client object which spawns those callbacks and those callbacks spawn another callback and you maybe have a transport or some other resource allocated by your HTTP client. And then something happens an exceptional error so what will happen? And the current answer to that is undefined like we don't know what will happen. If you have a callback written like this so you have a try accept statement in it then you can actually handle any exception in your callback but you have to be explicit about it you have to care about it. And most libraries actually don't they kind of think that everything will go just fine and exceptions basically don't exist. But some events some mechanism to propagate those exceptions back to the client but even in HTTP and async PGA I'm pretty sure there are lots of bugs where we didn't really handle those exceptions correctly. So it's essentially a bug magnet in async and it's constant point of pain because you have to care about those callbacks all the time. So what we actually need? We need a way to handle those exceptions callbacks or any other resources that event loop allocate us. So if something happens even the async event loop async itself should help us to close those and deallocate those resources. So I have an idea for Python 3.8 and this is just an idea. I discussed it briefly with a few core developers and I think we'll have something like that in async 3.8 and that's a low level API it's called create supervisor. And create supervisor returns you an asynchronous context manager context object. You can use it in async with statement and supervisor kind of mirrors all async event loop APIs. So it will have the call soon method it will have call later method create connection create server. So the idea is that we can kind of give a virtual event loop to any library that wants to use the event loop and we can track all those resources and you can pass it around if you want just like you would an event loop and this is a low level API so it meant to be used in libraries like A, HTTP or async PGA. And this is an example of such a hypothetical HTTP library where you have a get method. So you get a reference to the currently running event loop then you create a supervisor and you can work with the supervisor just the way you would with the normal async event loop. But what we have here is that because the supervisor will be unique to that point in your library when we know all the resources that will allocate so any unhandled exception will first propagate correctly and then we'll be able to clean up all those resources. And there is another similar idea and again thanks to Curio is to add async IO task group in Python 3.8. That's basically an alternative to async IO gather which is I think very badly designed. There are lots of hysterical reasons why gather works like it does but this thing would be way more convenient and way more flexible. And this actually looks pretty similar to 3.0 and Curio and it's a very high level code. So task group will likely just use the create supervisor under the hood and will behave pretty similar to how 3.0 nurseries are working. It's more convenient than gather. Gather has some very strange default default config options. So for instance by default if you run five things, five different asynchronous tasks with gather and one of them fails, it will just propagate the exception and the rest of them will continue the execution and those exceptions or results they will be just lost and a lot of people just don't know about that but it's pretty dangerous. You have to be careful when you use async IO gather and you can do more magic with task groups like if you have a million tasks to execute it would be unwise to push them all at once. You probably want to bucket them and execute them in portions. So what else can we expect from Python 3.0? Well, first of all we hear you even if you don't submit your bugs and feature requests to boxpyton.org even if you're just crying and Reddit or Twitter or Hacker News we usually try to find out those pain points that people have but please do submit your bugs and feature requests to boxpyton.org because we are really listening. A lot of people have this idea that async IO core developers don't really care about what people say or want. That's not true. The documentation has to be improved. About two years ago I was standing at EuroPython and promised that we'll update the documentation and I'm excited to announce that nothing has changed. It still sucks. But we'll try to improve it as soon as possible. Likely we'll have create supervisor and task groups in Python 3.8 and they might have different names or maybe we'll change some API details but it's very likely that we'll have something like that. I'm also working to add low level tracing API. I'll be doing that in you the loop first to kind of prototype and let people play with it. The idea is that there are some services like Zipkin or New Relic and they allow you to look deeply in your code and figure out how it works and what was happening with it. So we actually want to know for instance all tail latencies or all timings or how many bytes are pushed through some protocol or transports and currently there is absolutely no way to do that in Async IO without patching the core. So we want this tracing API to be flexible and usable for people. Maybe we'll implement timeout and cancel scope just like trio and that's also a pretty unique feature in trio because in trio you don't really care about timeouts at all. You don't care about them when you write third party libraries when you write HTTP, let's say HTTP client. You don't really care about timeouts. Trio kind of does all this work to handle them and to implement them in the core. So you just write your code and if you want a timeout you just use a context manager around that call site and the timeout logic will be applied correctly and all allocated things will be cleaned up and managed for you. And the same idea and I actually recommend you to read some of Nathaniel's blog posts. Those are pretty, you can Google them easily. Those are also interesting ideas about cancel scopes and how he does cancellation. So a lot of those ideas they can be applied to Async IO in one way or another and we are now thinking about how we can have an Async IO. There are other, another idea that I have is to improve the streams API and basically not just improve it but to substitute it with something sane because right now when you use streams you have this reader and writer object with different methods and for instance you want to close your stream. Which closed method you should call, should you call it on the reader or on the writer or maybe you should call two closed methods. So it's really hard for people to grasp how streams work and I think that's part of the reason, one of the reasons why people don't really use those streams. So I have an idea to design new API for Async IO with two top level functions. Async IO Connect and Async IO Serve and have a single streams object working for them. Maybe we'll add a context manager for the shield function and shield function allows you to shield some code or some coroutine from consolation and right now the only way in Async IO is to use this function but then you have to always refactor your code. You have to move your final block into a function and then use it with the shield. So it's really painful. We're also rewriting the SSL implementation in Async IO from scratch. Again, I'm doing this work in UV loop to kind of try this rewrite in the wild. But it will handle a lot of different bugs and a lot of inconsistencies that we now have in our SSL implementation in Async IO. Like doing the consolation correctly and doing things like SSL over SSL. And there is another thing that really worries me is that they cancel their in Async IO. It's derived from exception and not from base exception. So when people put try accept exception in their code base and that's a pretty popular way to catch all exceptions they kind of break the Async IO consolation completely and entirely. So we are thinking about fixing this but this is a backwards incompatible change. So I have no idea if we actually push this through. And that's pretty much it. Thank you very much. So we have pretty much time for Q&A. Please raise your hand or you can go here. Okay, there's not so much people to. Okay, please. Hey, so I'm pretty much satisfied with the gather although I've been tweaking its options a bit. For example, I can have like a list of stuff that- Return exception. Results and exceptions. So how is Trio's nursery, for example, better than just using gather with some more information about it? Yeah, so that's a very good question actually because that's a fundamental problem right now in Python. In Python you usually have some synchronous code and we can write only one exception. In things like Trio, Async, or Curio you can have your task group and we'll execute many different things and perils. We can have many different exceptions coming from one point. And the idea is to create some sort of multi-error. A thing that can develop a few exceptions in it. So we'll likely add a mechanism, a built-in Python mechanism to be able to create those kinds of multi-errors in Python code. So you will get, and that multi-error, you will be able to catch it and then you will have to use some API to kind of go through those exceptions and make sense of them. And the other thing is that gather uses some magic under the hood, but if you just pass coroutines to gather it will wrap them implicitly with tasks and you won't have references to those tasks in your code. With Async, with Async task groups you will do that explicitly. So you will have a reference to a task. So you will be able to collect your results of the computation explicitly if you want after the Async with block. So yeah, that's the idea. The main challenge will be to add the multi-error and have a nice UI attached to it. Okay, thanks. Can I have one more follow-up? Actually, if you do gather, do context variables from 3.7 go to those spawn nodes. Great, thanks. So any more questions? Okay, please don't forget to read this session. Just go to conference app and do it. Please, don't leave this doc in nursery. Thank you. So I've been using Python since Python 3.3 and some older stuff but I was wondering, it seems like a lot of broken things were actually introduced in the older versions while I was kind of thinking, you know, everything is stable about Async I.O. Can we expect more broken stuff or are we reaching some kind of stability then or are these just minor things in the end? Yeah, we don't want to break anything in Async I.O. or Python 3.0. We'll try to be very careful about that. I also don't expect Async I.O. to completely stabilize and freeze. We actually want to add those new APIs. We want to keep improving it. Unfortunately, we cannot really improve it with the as fast as we want because it's tied to Python release cycle but I think that Async I.O. will be pretty different in Python 4.2 or 4.3. But back there's compatible, of course. All right, another one question then. So if you introduce Shield, do you then also have to introduce Destroy Shield or is that not going to be an endless battle? Yeah, I don't know, maybe we'll add levels so you can't really destroy a 80-level Shield or something like that. Really, I have no idea about that. But I kind of think that that isn't really necessary. If you Shield something, then it's just important that you should always let it finish. And if you have some hard time out, well, then just kill the sh9 program. It will probably clean up and just fine. Thank you. Any more? Okay, let's first. I'm just wondering, what do you think is the future of UV loop? Like, is it still more performant than the standard AsyncIO loop and that's the main reason to keep using it? Will it ever be part of the standard library? It can be part of standard library the way it is right now because it uses Siphon, and Siphon is a huge dependency. And also it uses LibUV, and LibUV is also a very complicated dependency. To include something like UV loop right in AsyncIO you will have to write a lot of C code and that's basically hundreds of thousands of lines of code. So probably it's not gonna happen anytime soon, so it will just happily leave as a separate PIPI module for a long time to come. And by the way, also check out another third-party event loop implementation. It's called Tokyo, by Nikolai Kim, the original creator of AI HTTP and it's written Rust. And the whole point is that you can use Tokyo as kind of a single bridge between Python asynchronous world and Rust asynchronous frameworks. So it's pretty interesting and I think it's pretty stable right now, so you can use that as well. Hi there, thanks for the talk. So one of the things I was sort of confused about with the context bars is if you start doing things like you ensure future on a query team and then sort of pass it around, which context does that query team get? It always inherits the context of where it was called, the ensure future, where the task is created. Basically when task is created, captures the current context and keeps it forever. Okay, thanks. Hi, do you think that you could add the concept of tasks having names to async I.O. Because threads have names, trios tasks have names, but async I.O. has no way to add names to tasks. It's very useful when you're debugging stuff and for example PyCharm has a tool that allows you to view all those separate tasks, but if they don't have names, it gets a lot harder. Yeah, I think it's a good idea. So it can be very good first contribution to the Python, so go ahead. Okay. Okay, another question. Trios nursery, really trios spawned tasks don't have any way of getting their results. How do you feel about that? Getting results. Yeah, when you spawn task in trio, you don't really get the task object that you can just wait on. So you have to use other primitives to get the results. Yeah, so async I.O. has a lot of history and we cannot just adopt that trio pattern. And again, Nathaniel is very obsessed with providing just one way of launching tasks and that's on purpose. That's why trio programs has some qualities that async I.O. programs probably never will, but I think that the current async I.O. way of doing things, getting the task object is flexible enough and people will do just fine if we give them enough primitives like task groups to work with them in coherent way. Okay, thanks. We have time for one more short question. Hi, is there a difference between context variables and dynamic variables in Lisp? So back then when we had those 900 emails on Python ideas and Python Dev, I knew the answer and I was able to articulate it 20 or 30 times but now I don't remember like all the differences in dynamic scoping and context variables. One particular difference is that context is basically an immutable mapping in memory. So once you capture it, it loses this link with the old context where it came from. So it won't see updates in the original parent context. So this is probably the key difference but there are some other differences. Unfortunately, I don't remember them all right now. Thanks. Thanks so much.