Live data from Hacker News

Writing an OS in Rust: Async/Await

os.phil-opp.com

61–70 of 107 posts

Re: Writing an OS in Rust: Async/Await

#61

I'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…

This is a huge issue when trying to fuzz async code also.

Dropbox does, essentially, fuzzing of Rust async code and it is determistic.

I couldn't find a post about it, sadly, but I know the information is out there somewhere.

Re: Writing an OS in Rust: Async/Await

#62
post #15

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…

Probably not at all related to the OP. But I found the thought entertaining, so offering it as such.

One way to address you concern would be to guarantee that task always terminate in a bounded amount of instructions.

One way to solve that would be offer a limited buffer in which the program instructions for the task might be expressed, and a language to express them in that is strongly normalizing (think something like Morte)

Re: Writing an OS in Rust: Async/Await

#63
post #4

An OS with async/await sounds awfully similar to Windows 3.1 with its cooperative multitasking model. What's old is new again ...

Didn't most old operating systems use cooperative multitasking? I remember, at least, that classic Mac OS (i.e. pre-OSX) didn't use preemptive multitasking, either. Anyway, this SO answer[0] explains why early Linux, much like the hobby kernel in this article, used cooperative scheduling inside the kernel, and only preempted user-space stuff. [0]: https://stackoverflow.com/a/16766562

Thanks for the link!

I don't understand why this would be different for a Rust based OS..

Re: Writing an OS in Rust: Async/Await

#64

I'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…

async/await-based code is deterministic until you deliberately introduce the ability for tasks to race. Look at Noether for a language design that explicitly makes those steps. Whereas if you have channels (and don't have some kind of single ownership) then you have nondeterministic races right from the start. So I rather doubt that any such thin wrapper could be formed.

Certainly as a human reader, Future-based code (which async/await is sugar for) is the easiest form of concurrency/parallelism to reason about: yes you have small tasks that might execute in parallel, but all your functions still return values, function composition still works the way you'd expect, there's no way for parallel tasks to silently interfere with each other in some hidden way. The control flow is still what it looks like (unlike with general coroutines). Of course if you can afford to avoid parallelism entirely you probably should, but async/await is the least intrusive/most understandable way to manage it if you do need it.

Re: Writing an OS in Rust: Async/Await

#65
post #37

Earlier 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.

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

#66
post #15

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…

you can have a kernel thread pool dedicated to one kernel feature. This way only task created by this kernel feature will run on this pool and compete together .

Re: Writing an OS in Rust: Async/Await

#67
post #26

Earlier quoted context omitted.

I'm not convinced that's actually true though, is what I mean. The big drawback of cooperative is just that a single process can monopolize resources, which in the singlecore days mean the system became unresponsive and you had no way to recover from it. That isn't really true now with multicore. As long as the OS has a core to work on it can terminate or otherwise deal with misbehaving processes. Again, this is base…

> 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 can justify that. The first is threads that are spending a lot of time stuck in blocking I/O--i.e., they're calling things that exist as yield-points in practical kernels anyways. The second thing is some sort of event processing loop, which looks like kind of like this:

  while (!shutdown) {
    event_t *e = get_next_event(queue);
    if (!e) { sleep_until_work(queue); }
    else { execute(e); }
  }
The call in sleep_until_work already today uses a syscall to indicate that the thread shouldn't be scheduled. But even get_next_event can likely be trivially modified to use a syscall to add an event loop. Since multiple threads are able to enqueue events (else who would fill in work while you're sleeping?), you need some sort of threading library support to implement get_next_event correctly. Change the equivalent of pthread_condvar_wait for your OS to introduce a yield point, and you'll have introduced yield points to the vast majority of applications.

Re: Writing an OS in Rust: Async/Await

#68
post #64

I'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…

async/await-based code is deterministic until you deliberately introduce the ability for tasks to race. Look at Noether for a language design that explicitly makes those steps. Whereas if you have channels (and don't have some kind of single ownership) then you have nondeterministic races right from the start. So I rather doubt that any such thin wrapper could be formed. Certainly as a human reader, Future-based code…

A state machine's control flow is even easier to follow, although more verbose.

Re: Writing an OS in Rust: Async/Await

#69
post #26

Earlier 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…

That does not follow. Long-running CPU-only tasks are a thing and they may not be aware of each other.

With cooperative multitasking, launching something like a parallel compilation would make your machine unusable.

Re: Writing an OS in Rust: Async/Await

#70
post #65

Earlier 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.

That would explode the state machine size and make its performance characterization very hard.

And you would need to avoid having any kind of call to external code (or yield before and after every such call, and pray for the best).

Post reply on HN