Is async/await a good idea for an OS kernel, even a toy one? Cooperative multitasking tends to break down at scale, because the probability that all the "threads" you're cooperating with are playing nice goes to zero as the number of threads increases. An OS will tend to have a concentrated number of the pathological cases in it as it deals with hardware and all the other hardest timing & concurrency problems. It's a…
True for a general-purpose OS kernel. But in an embedded system or unikernel it could make a lot of sense.
Writing an OS in Rust: Async/Await
91–100 of 107 posts
Re: Writing an OS in Rust: Async/Await
#92Earlier quoted context omitted.
There's a few different pieces to your question. Async/await is a programming paradigm that moves the state management of an asynchronous program into the compiler rather than being explicitly managed by the user. The paradigm is almost universally a win: if you don't want to write asynchronous code, just don't use it; it doesn't impact your code at all. A second question is how the language actually drives the execu…
"Now do note that this requires a far more complex executor model than most async/await toolkits (including this tutorial) provide." I'm assuming the async/await language support in Rust is intrinsically tied to a cooperative approach, which is the part I'm questioning. Obviously a kernel needs to be able to generically work in an asynchronous fashion... the question is, is this asynchronous fashion appropriate? If t…
That assumption is incorrect. The blog post explains this in detail better than anyone could here - and if that does not suffice, the Rust book and the async book, as well as the Fuchsia kernel documentation, also all go into this.
> If the Rust async/await support is indeed so flexible
It is indeed even more flexible than that. You don't even need a run-time to drive progress. You can manually drive progress of async tasks by polling them throughout your code at specific points if you wanted - essentially interleaving the async processing of tasks with your main thread's task (and well, you can build any sort of executor you want, with different priority levels or whatever you feel like doing). I have an HPC app that does this to drive MPI non-blocking communication.
The blog post explains all of this. It's pointless for you to invest a lot of your and others time into speculations built on top of incorrect assumptions, when the first 2 minutes of actually reading the blog post clarify this for you.
Re: Writing an OS in Rust: Async/Await
#93Earlier quoted context omitted.
> Error: you cannot open another Chrome tab because all your cores are already used up by Slack and VSCode You would probably still want to preempt, because you're not going to rewrite all the widely used software to actually yield. Because that's the thing about cooperative multithreading, the participating parties need to cooperate. And if you look at the amount of threads that some software open it's just crazy. L…
The intention of the author's OS is to use cooperation in kernel space and preemption in user space so your entire comment makes absolutely no sense.
Re: Writing an OS in Rust: Async/Await
#94Earlier quoted context omitted.
There is one significant difference: Windows did context-switches in the blocking calls and did not rely on the program code having "the right structure" needed for straight co-routines to work. The difference between preemptive and cooperative multitasking is not whether you do full context switches, but whether there is a way to do context switch at a point where the process does not expect it (ie. by handling some…
Does Rust allow a computational for-loop to be interrupted somehow? Computation can also be viewed as a blocking operation.
Re: Writing an OS in Rust: Async/Await
#95Earlier quoted context omitted.
The only yield points are .await points. If there's an .await in a loop, then sure, but otherwise no.
What is the cost of an .await point? For this to work, perhaps Rust should cooperate too, inserting .await points at strategic places in the code, to keep cost low but still guaranteeing a certain responsiveness of the overall system.
Re: Writing an OS in Rust: Async/Await
#96Earlier quoted context omitted.
Does Rust allow a computational for-loop to be interrupted somehow? Computation can also be viewed as a blocking operation.
The only yield points are .await points. If there's an .await in a loop, then sure, but otherwise no.
Re: Writing an OS in Rust: Async/Await
#97Earlier quoted context omitted.
> Error: you cannot open another Chrome tab because all your cores are already used up by Slack and VSCode You would probably still want to preempt, because you're not going to rewrite all the widely used software to actually yield. Because that's the thing about cooperative multithreading, the participating parties need to cooperate. And if you look at the amount of threads that some software open it's just crazy. L…
And what are those threads doing all the time? On N-core, 2-way SMT hardware (which describes pretty much all consumer hardware), the maximum amount of threads that can be usefully doing work in parallel is between N and 2 * N. Any more threads than that, and it must be the case that you expect those threads to spend a lot of time doing nothing to justify the cost of their creation. There's two main categories that c…
It might not lock-up your OS but it would lock up all of the userland and the kernel couldn't do anything about it, because it can't pre-empt. All it could do under that model is let the user kill the browser or just kill the browser itself automatically.
I think most people would prefer being able to use their PC for other things while transcoding a video or compiling a program at near the PCs full capability. Instead of sacrificing a whole core to the OS and then also having to wait longer for those tasks to finish, plus not being able to do anything else.
A model with less pre-emption is certainly possible, but current end-user software makes an approach without any pre-emption very in-advisable.
Re: Writing an OS in Rust: Async/Await
#98Earlier quoted context omitted.
The only yield points are .await points. If there's an .await in a loop, then sure, but otherwise no.
If I understand the futures::pending!() macro correctly, one can use that to add a forced yield point in a loop, right?
Re: Writing an OS in Rust: Async/Await
#99I've shied away from async/await because I haven't seen a good writeup on how to make it deterministic. Come to think of it, all of the times I've encountered it, there was no way to analyze the codepaths and prove that every exceptional situation and failure mode was handled. Maybe I missed something along the way? So my feeling about it is that it may turn out to be an evolutionary dead end, or anti-pattern at the…
What do you mean by "deterministic" here? > Come to think of it, all of the times I've encountered it, there was no way to analyze the codepaths and prove that every exceptional situation and failure mode was handled. There is nothing special about async/await with regards to this in Rust, at least if I'm understanding you correctly. Async functions can return Results like any other function for recoverable errors, a…
Then if something does fail (wifi blips out without a retry, who knows) the page just hangs with no indication of what went wrong.
Contrast this with Unix-style synchronous blocking code piping data between threads with no shared state. Since every step of execution happens in a single thread, blocking until a pipe returns data, it's trivial to step through the code.
Async/await really becomes a problem in Javascript though because they made it a language keyword in Node.js. So you never know if you are dealing with a sync function or async function. Eventually the entire program has async in front of everything and ends up being written just exactly like sync blocking (but with superfluous syntactic sugar everywhere). So I question what was really gained there. IMHO it just doubles the workload in the mind since now we have to juggle both types of functions and explore those permutations. That's the last thing we need when we're trying to brainstorm a solution from a large possible search space.
I'm also looking at this from a one-shot functional programming perspective. I realize that sync blocking code blocks the main thread, which is usually the GUI rendering loop. There are ways around that though. The best solution I've seen so far is Apple's Grand Central Dispatch:
https://en.wikipedia.org/wiki/Grand_Central_Dispatch
https://www.raywenderlich.com/5370-grand-central-dispatch-tu...
https://www.swiftbysundell.com/articles/a-deep-dive-into-gra...
Basically it works by being sync blocking and providing simple ways to run closures on other threads. I find it much easier to debug than async/await though. Rust probably already has all of that functionality, so I don't understand the advantage of copying Javascript model with all of its caveats. I think what's happening is that people just want to run with the context they're already familiar with.
Re: Writing an OS in Rust: Async/Await
#100I've shied away from async/await because I haven't seen a good writeup on how to make it deterministic. Come to think of it, all of the times I've encountered it, there was no way to analyze the codepaths and prove that every exceptional situation and failure mode was handled. Maybe I missed something along the way? So my feeling about it is that it may turn out to be an evolutionary dead end, or anti-pattern at the…
There are two important things that are often conflated with async/await: the programming model, and the executor model. The programming model of async/await is essentially syntactic sugar for a state machine, where each of the states are determined implicitly by the site of the call to await, and the ancillary storage of the state machine is managed implicitly by the compiler. It's basically no different from a gene…
Edit: I forgot to mention that I came to your same conclusion a few years ago about async/await being equivalent to a state machine. It happened a different way for me, when I realized that a coroutine performs the same computation as a state machine, but is much easier to trace. I was working on a game, and made a rather large state machine with perhaps 20-50 states and some side effects where those states interacted with game state. But it simplified down to just a few lines of ordinary flow control code when I tried it as coroutines with message passing. So to me, async/await carries the burden of state machine complexity but loses the simplicity of coroutine (and sync/blocking obviously) traceability. So I just don't see a compelling reason to use it.