Live data from Hacker News

Asynchronous IO in Rust

medium.com

101–110 of 111 posts

Re: Asynchronous IO in Rust

#101
post #98

Earlier quoted context omitted.

>(Do you have an example of a standards complaint C++ freestanding standard library?) Yes, you've probably heard of libstdc++: https://gcc.gnu.org/onlinedocs/libstdc++/faq.html#faq.what_i... In other words, freestanding C++ allows you to to use new and delete normally (after providing malloc and free). Note that this a minimum requirement . There's nothing stopping you from using STL containers in embedded systems, a…

As you say, the libsupc++ requires you to provide malloc and free, just like libcore. > probably much of the safety Only if you want to make life hard for yourself. The standard library is not at all special in its ability to create safe abstractions for `unsafe` code, and doing this makes things so much smoother: you don't have to scatter `unsafe` all over your code, and you let the compiler help you as much as poss…

That code sample is really helpful, thanks.

But doesn't that seem kind of redundant? Box and std containers are great as is, except for the one line that aborts on OOM. Would it be possible to use traits to choose OOM behavior at compile time?

To be clear, this isn't just important for embedded. A production database or web server should be able to handle an allocation failure without blowing up the whole process.

And I'm glad there's some talk about a standard allocator trait. I'll look for the relevant issue.

Re: Asynchronous IO in Rust

#102
post #98

Earlier quoted context omitted.

As you say, the libsupc++ requires you to provide malloc and free, just like libcore. > probably much of the safety Only if you want to make life hard for yourself. The standard library is not at all special in its ability to create safe abstractions for `unsafe` code, and doing this makes things so much smoother: you don't have to scatter `unsafe` all over your code, and you let the compiler help you as much as poss…

That code sample is really helpful, thanks. But doesn't that seem kind of redundant? Box and std containers are great as is, except for the one line that aborts on OOM. Would it be possible to use traits to choose OOM behavior at compile time? To be clear, this isn't just important for embedded. A production database or web server should be able to handle an allocation failure without blowing up the whole process. An…

There could definitely be an alternate method on `Box` that returned Result, but I don't think this is so feasible for containers which allocate in many places and so would require duplicating most of the API, however there may be some tricks that work.

https://github.com/rust-lang/rfcs/issues/538 covers the allocator stuff.

Re: Asynchronous IO in Rust

#103

Earlier quoted context omitted.

Most of my work involves kernel programming (device drivers for hardware or virtualisation companies etc.); almost all of the rest of it is writing networking code (custom protocol implementations). So my reasons for evaluating[1] Rust are that I think C and C++ are pretty awful languages for writing code that's as security and reliability critical as the code I touch on a day to day basis, while memory-managed langu…

What do you think of C++ coroutines? Two competing standards proposals, already available as libraries: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n445... http://blogs.msdn.com/b/vcblog/archive/2014/11/12/resumable-...

They certainly look useful at first glance.

The first's library implementation is based on boost.coroutine which as far as I'm aware uses stack-switching, so this is more of a syntax demo than a demo of the final implementation details. The fact that you can apply sizeof() to the resumable type is important for the kernel-typical pool allocators.

The second seems to have preview support in VS2015's compiler. Too bad I'm not much of a Windows programmer these days… It's not quite clear what ther memory-allocation story is from that article.

Of course the downside is it's yet another graft onto the C++ language. After ~15 years of using it, my point of view is mainly that C++'s type system and unsafe-by-default behaviour inherited from C are a mistake…

Re: Asynchronous IO in Rust

#104
post #98

Earlier quoted context omitted.

>(Do you have an example of a standards complaint C++ freestanding standard library?) Yes, you've probably heard of libstdc++: https://gcc.gnu.org/onlinedocs/libstdc++/faq.html#faq.what_i... In other words, freestanding C++ allows you to to use new and delete normally (after providing malloc and free). Note that this a minimum requirement . There's nothing stopping you from using STL containers in embedded systems, a…

As you say, the libsupc++ requires you to provide malloc and free, just like libcore. > probably much of the safety Only if you want to make life hard for yourself. The standard library is not at all special in its ability to create safe abstractions for `unsafe` code, and doing this makes things so much smoother: you don't have to scatter `unsafe` all over your code, and you let the compiler help you as much as poss…

I really appreciate your excellent insight here, thank you.

Re: Asynchronous IO in Rust

#105
post #102

Earlier quoted context omitted.

That code sample is really helpful, thanks. But doesn't that seem kind of redundant? Box and std containers are great as is, except for the one line that aborts on OOM. Would it be possible to use traits to choose OOM behavior at compile time? To be clear, this isn't just important for embedded. A production database or web server should be able to handle an allocation failure without blowing up the whole process. An…

There could definitely be an alternate method on `Box` that returned Result, but I don't think this is so feasible for containers which allocate in many places and so would require duplicating most of the API, however there may be some tricks that work. https://github.com/rust-lang/rfcs/issues/538 covers the allocator stuff.

Right, duplicating every method that allocates also isn't very elegant.

But could Box and containers be polymorphic on an OOM handling trait?

Alternatively, one could imagine an OOM-safe container that returns results, and a convenience wrapper class that unwraps them. That might harm efficient code generation though.

Re: Asynchronous IO in Rust

#106
post #96

Earlier quoted context omitted.

> Scheduling in response to IO events (a coroutine can yield when making a "blocking" IO call and resume when it completes). You can write asynchronous code that looks synchronous! You can also write "actual" synchronous code which does the same thing in the kernel. :P And there are several CPU schedulers to choose from to fine-tune when things get woken up. I agree it would be interesting to test latency; actually,…

>You can also write "actual" synchronous code which does the same thing in the kernel. :P And there are several CPU schedulers to choose from to fine-tune when things get woken up. You can. But no matter how good your kernel scheduler is, that trip through it is going to cost you. Why? Because it's going to wreck your cache and TLB entries. I think the TLB is a big part of the story, given it's small, specific to the…

Kernel stacks are allocated by Linux in alloc_thread_info_node; user stacks by glibc in allocate_stack, which does have some sort of cache. Overall there's a fair bit of work going on that could be skipped...

But yeah, certainly userland context switches are faster; it's just that your comment seemed to postulate "scheduling in response to IO events" as a benefit of coroutines over threads, as if the kernel had no way to distinguish between threads waiting for IO and threads needing to be woken up. I presumably misinterpreted it.

Interesting paper. It seems like it could be a useful step toward my ideal imagined environment where the distinction between coroutines and threads would be meaningless - because scheduling would be a job shared between userland and the kernel, so userland could do fast thread switches, batch system calls, etc., while the kernel would still give them PIDs and let them take signals or be ptraced, and native tools (debuggers) would treat them as threads.

Though the paper is from 2010; do you know if anyone has tried to implement anything along similar lines in production?

And no, I didn't take that course.

Re: Asynchronous IO in Rust

#107

Earlier quoted context omitted.

> a way of writing code where "might this crash?" is a question you're never bothered to ask. Is that actually true? Yes, you won't get a partially written int, but you could still run into consistency issues if you don't lock. For example, if you need thread A to update both x and y to have a consistent state, and B is reading both x and y, A might update x, then the scheduler would switch to B, and be might read th…

It will do what you just described. And what you described won't crash. If you use the int as an offset into raw memory, then sure, that will. But if you use it as an offset into a list, then it won't. At most it will cause an exception to be generated. That will cause the reader thread to die, because it was the code that caused the exception, but it still won't crash.

Right, but I don't really see how that's freeing. A crash isn't good, but having your program be incorrect might be even worse. If the program crashes, you know it. But it might take some time before you recognize that your logic is wrong, which might cause all kinds of damage.

So I don't think it frees you from thinking about concurrency at all.

Re: Asynchronous IO in Rust

#108
post #106

Earlier quoted context omitted.

>You can also write "actual" synchronous code which does the same thing in the kernel. :P And there are several CPU schedulers to choose from to fine-tune when things get woken up. You can. But no matter how good your kernel scheduler is, that trip through it is going to cost you. Why? Because it's going to wreck your cache and TLB entries. I think the TLB is a big part of the story, given it's small, specific to the…

Kernel stacks are allocated by Linux in alloc_thread_info_node; user stacks by glibc in allocate_stack, which does have some sort of cache. Overall there's a fair bit of work going on that could be skipped... But yeah, certainly userland context switches are faster; it's just that your comment seemed to postulate "scheduling in response to IO events" as a benefit of coroutines over threads, as if the kernel had no wa…

Oh OK, I definitely could have written that better.

From what I can tell, M:N threading has been thoroughly abandoned by Linux, but it might be worth revisiting given the horrendous I/O scaling of Linux kernel threads. I would imagine scheduling user threads cooperatively simplifies things a lot.

Google has a very interesting talk about cooperative native thread scheduling, but they haven't upstreamed their code:

https://www.youtube.com/watch?v=KXuZi9aeGTw

And here's what linux devs are actually doing (it's underwhelming, something like 50% of what the hardware is capable of):

https://lwn.net/Articles/629155/

In the meantime, people who actually need the performance (HPC and even some enterprise servers) have to bypass the kernel entirely. It looks like that's going to be possible even in virtualized environments, thanks to hardware support:

http://people.inf.ethz.ch/troscoe/pubs/peter-arrakis-osdi14....

Re: Asynchronous IO in Rust

#109

Earlier quoted context omitted.

> will give you some of the standard library. That's what libcore is. A lot of the stdlib is reexports over libcore. new and delete are C++ isms, their Rust counterparts are `Box::new` (and destructors are automatic). `Box::new`, like `new`, has the OOM issue. If you're okay with that, you are free to link to the `alloc` crate, which gives you `Box` without pulling in additional deps. There are plans for in-place box…

Just to be clear, the C++ equivalent of Box is unique_ptr. I don't see equivalents of new and delete, but I might be missing them. I see Box::from_raw and core::ops::Placer. Is that what you mean by in-place boxing? But now there are two problems: You have to manually allocate a buffer of the right size, check if the pointer is null, and box it. For every allocation . Forget a null check? Allocate too small a buffer?…

> Just to be clear, the C++ equivalent of Box is unique_ptr.

Yeah, I mean that it's used like `new` is. We don't have the notion of constructors.

> You have to manually allocate a buffer of the right size, check if the pointer is null, and box it. For every allocation.

No. With the placement API, you'll be able to use the `box` syntax (which at the moment just supports `Box`, which has OOM issues) for your custom wrapper (i.e., `Box` with OOM support, or whatever).

What `box` gives us is that `let x = box make_inner()` would have the returned value of `make_inner()` written directly to the heap location. (Unlike `let x = Box::new(make_inner())`, which may be a move of `make_inner()`)

Placer is a part of this (https://github.com/rust-lang/rfcs/blob/master/text/0809-box-... is the whole design). from_raw isn't, that's just a useful API for FFI.

> Did you want to use Rust's built in containers in your kernel? Well, sorry, you're going to have to write your own ones that don't panic on allocation failure.

When you're writing a kernel you're not supposed to be using the standard library at all, just libcore (which exists for this purpose). The same issue exists in C++.

Re: Asynchronous IO in Rust

#110

Earlier quoted context omitted.

Just to be clear, the C++ equivalent of Box is unique_ptr. I don't see equivalents of new and delete, but I might be missing them. I see Box::from_raw and core::ops::Placer. Is that what you mean by in-place boxing? But now there are two problems: You have to manually allocate a buffer of the right size, check if the pointer is null, and box it. For every allocation . Forget a null check? Allocate too small a buffer?…

> Just to be clear, the C++ equivalent of Box is unique_ptr. Yeah, I mean that it's used like `new` is. We don't have the notion of constructors. > You have to manually allocate a buffer of the right size, check if the pointer is null, and box it. For every allocation. No. With the placement API, you'll be able to use the `box` syntax (which at the moment just supports `Box`, which has OOM issues) for your custom wra…

Placer looks interesting. I'll try it out.

>When you're writing a kernel you're not supposed to be using the standard library at all, just libcore (which exists for this purpose). The same issue exists in C++.

Not quite. C++ standard containers are polymorphic on the allocator. That means you're welcome to use std::list or std::string in kernel mode. Just plug in a kernel allocator and you're good to go.

There's no technical reason why every Rust core project must reinvent the linked list and the hash map, like every C project.

Post reply on HN