Earlier quoted context omitted.
You're mixing up quite a few somewhat related but different concepts: data races, race conditions, concurrency and parallelism. Concurrency is needed for race conditions, parallelism is needed for data races. Many single threaded runtimes including JS have concurrency, and hence the potential for race conditions, but don't have parallelism and hence no data races.
Concurrency with a single thread of execution runs with complete mutual exclusion, so no "pure" single threaded concurrency is definitely race condition free. What we may argue over (and it becomes more of a what definition to use): IO/external event loop/signal handlers. These can cause race conditions even in a single threaded program, but one may argue (this is sort of where I am) that these are then not single th…
What async promised and what it delivered
301–310 of 317 posts
Re: What async promised and what it delivered
#302Earlier quoted context omitted.
You don’t have to make everything Send/Sync if you don’t need to. Use tokio’s local runtime and spawn_local(), or use one of the other async runtimes. You also don’t need to spawn() futures to await them. Spawn enables parallelism on the multithreaded runtime, holding join handles, etc. If all you need is to execute concurrent code, though, the various combinators and functions in the futures crate lets you do so wit…
You literally can't use one of the other async run times because of the current state of async/await does not allow library authors to easily write for multiple runtimes - they were written for one runtime in mind and that is just tokio. And if you're pulling in library methods you're still stuck with the method headers they specify. All of your arguments are just mental workarounds trying to justify how fucked the r…
Send/Sync/static is not needed on tokio’s local runtime, which doesn’t require any adjustments to your libraries.
Passing data between threads requires Send/Sync/static, except for certain cases like scoped threads, so making OS threads faster doesn’t seem to solve that issue like using a local runtime would.
Many async libraries (though certainly not all) are runtime-independent. If your library doesn’t have to spawn, it is easy to write runtime-independent code. I would like to see some spawn traits brought into std to make it easier to write libraries that have to spawn, though.
I’ll always try new ways of doing things, but you are making the assumption that the way you feel is the way everyone feels, and totally dismissing the opinions of those who don’t. It puts me off of whatever solution you might be proposing, since you clearly don’t have the empathy to understand the full range of positions of the people whose problems you’re ostensibly trying to solve.
I’m not trying to convince you the way you feel is wrong, but you are wrong that everyone thinks writing async code is miserable. There are times where it’s hard, or where the compiler emits confusing messages about async closures being not generic enough, but on the whole I enjoy writing async rust, so shoot me.
Re: What async promised and what it delivered
#303Earlier quoted context omitted.
> No, green threads / fibres or whatever you want to call them explicitly don't interleave executions. If you use them with a multithreaded executor (eg in Go), of course they interleave executions. I suppose all your green threads / fibers could run on a single CPU core. But what's the point? How would that be an improvement over what we have now? I suppose you could make something similar to async/await but with a…
> I suppose all your green threads / fibers could run on a single CPU core. yes. > But what's the point? How would that be an improvement over what we have now? > But its basically async/await but without declaring functions as async. You answered your own question: yes, you get what you have now, without all the overhead of async, await, promises and futures. > But if you do that, any function call you make could yi…
Good on the V8 team. Sounds like they’ve figured out a way to get the performance of green threads with the better ergonomics of effects systems (async await). Great!
You sound like an expert in cooperative multithreading. If async await can use real stacks, what actual benefits are there to cooperative multithreading? Why prefer them over what JS has now? Pitch them to me.
Re: What async promised and what it delivered
#304Zig is just doing vtable-based effect programming. This is the way to go for far more than async, but it also needs aggressive compiler optimization to avoid actual runtime dispatch.
I know what a vtable is, but what is vtable-based effect programming?
My primary inspiration for the concept is theorem proving languages like Lean in which typeclasses ("interfaces" in the OOP terminology) are implemented using structures passed down as arguments ("vtables" in the OOP terminology) separately from any receivers (values of the type implementing the interface, which doesn't actually need to exist for Lean). Typeclasses (and interfaces) are an effect, albeit a simple and limited one. Lean can't express effects in their generality due to totality requirements, but the same mechanism would work perfectly well for effects too. As for the "vtable" aspect: the primary distinction in implementing typeclasses using exposed vtable passing is that the language does not in any way limit the programmer to zero or one implementations of a typeclass per receiver type(s) (cf. orphan rules in Rust, cf. compiling effect systems to witness-passing, etc.).
Re: What async promised and what it delivered
#305Earlier quoted context omitted.
> And as for Rust - that's beyond inexplicable. No, you appear to have no idea what you're talking about here. Rust abandoned green threads for good reason, and no, the problems were not minor but fundamental, and had to do with C interoperability, which Go sacrifices upon the altar (which is a fine choice to make in the context of Go, but not in the context of Rust). And no, Rust does not today have a green thread i…
> the problems were not minor but fundamental, and had to do with C interoperability, The interoperability problems came from a design choice they made: they wanted to invisibly swap between OS threads if a blocking call was made. That was the wrong design choice for Rust. It is not something green threads require - it's an additional burden they imposed on themselves. > a fine choice to make in the context of Go, Co…
From what I understand the interop problems are more fundamental than that. The Rust devs wanted zero-cost FFI, but FFI for green threads would necessarily involve moving data between the green thread stack and the C stack, which is not zero-cost. Furthermore, the Rust devs wanted "zero-knowlege" FFI, in that calling code wouldn't need to know it's calling into or being called from Rust. That means that you can't assume whatever is on the other side of the FFI boundary knows how to switch/grow/otherwise handle green thread stacks, and inserting shims to do so would also not be zero-cost.
> and we re-implemented the CPU's stack management in software (invariably slower) because they didn't want to write the logic to grow a hardware stack.
If I'm understanding you correctly, part of the problem is that Rust didn't have a good mechanism to grow stacks. Segmented stacks were abandoned because of their allocations and performance cliffs, and stack copying a la Go is not feasible due to the inability to update pointers to the new larger stack. Rust ended up just using larger stacks for its green threads which kind of defeats a major reason to use green threads in the first place.
Re: What async promised and what it delivered
#306How did this article get back on the front page with all its comments time-shifted? My trite slop bashing was days ago: https://news.ycombinator.com/item?id=47862726
Sounds like the second-chance pool (dang's description at https://news.ycombinator.com/item?id=26998308)
Re: What async promised and what it delivered
#307Earlier quoted context omitted.
> I suppose all your green threads / fibers could run on a single CPU core. yes. > But what's the point? How would that be an improvement over what we have now? > But its basically async/await but without declaring functions as async. You answered your own question: yes, you get what you have now, without all the overhead of async, await, promises and futures. > But if you do that, any function call you make could yi…
If we invented a new language, sure. Cooperative multitasking might be a fun approach. The avalanche of bugs I’m imagining would come from existing JavaScript code being run in a different context than that in which it was written and tested. If you pass me a callback right now, and I call a(); callback(); b();. I can guarantee that the program doesn’t yield to the event loop or other executions between a() and b().…
Oh, right. As you said, the ship has sailed. I think you could bolt green threads onto javascript now without ill effects - apart from bloating the language. I can't see anything that could go badly (certainly no avalanche of bugs). But in javascript green threads are only mildly more ergonomic than async. I wouldn't be bloating the language for such a small return.
Rust is a different position. The current async implementation has two big black hairs. Firstly, they had to come up with a type-safe way saving the functions current state. By state, I mean what a function normally stores on its stack. What they came up with is a work of art in some ways, but it doesn't work well with the borrow checker. The borrow checker insists you prove that you have exclusive use of a variable while it exists. Things on the stack have a limited lifetime (the function call), so the compiler knows they don't exist for very long. Even with that small lifetime it's a battle, but it's workable. Async persists that state, usually to the heap, which can effectively live forever. That wreaks havoc with the borrow checker, causing comments like this: https://news.ycombinator.com/item?id=37436274, quote: "Yes, async is effectively a much harder version of Rust ...".
The second issue is colouring. In the current Rust async implementation of large chunks of it is left to libraries, like tokio. Each of these libraries has to provide their own I/O. They aren't compatible. So if you want to use a cute new HTTP server, you are out of luck unless they provided a version that talks to the async library you are using.
The library writers do their best to accommodate by providing interfaces to the popular async libraries. That forces them to do a extra work. Whereas before they could just call `std::file::File::read()`, now they have to abstract all the I/O they do to a different module, and provide an implementation of that module for each async library they want to support.
The outcome can only be described as a mess, and that's putting it politely. It's harming uptake of the language. It wasn't like they didn't know it was coming either - there were comments pleading for a better implementation. And it wasn't as if weren't better solutions weren't already apparent - they had green threads before, they made some wrong turns with its implementation that needed to be fixed. And it's not like these solutions were harder to do than the async implementation they came up with. Async needed new standard library features to stabilise (like `Pin`) and introduced new keywords - none of which was needed for green threads. (Although some would be useful for an efficient green thread implementation - like knowing the maximum amount of stack a function could use.)
In the face of all that, they persisted with async. You'd need a sociologist to explain how that happened - to my engineering brain it's inexplicable. Unlike Javascript it isn't just mildly ergonomic implementation of the same thing, it's a serious mistake - well worth the effort of throwing out and replacing.
Re: What async promised and what it delivered
#308Earlier quoted context omitted.
You're blending concepts. All parallelism is asynchronous, but not all "asynchrony" is parallel. I have to use Python as as an example since I don't have much experience with JavaScript, but when you're using asyncio, that's single threaded non-blocking IO (asynchronous). Each use of "await" yields execution back to the event loop, and it can can schedule some other task to run. Tasks can run asynchronously, but no i…
> All parallelism is asynchronous, but not all "asynchrony" is parallel. Sure, but my comment was not about parallelism compared to asynchronous, but about the idea that Javascript is above the "blocking first mindset". Javascript depends on a blocking (single-threaded, run-to-completion) backend. The asynchronicity on top of this blocking layer is an abstraction based on cooperative yielding. Whereas e.g. Erlang's a…
My point, coming from Python, was that whenever I can I write the entire application as an asyncio app, which cuts down on the function coloring problem. That doesn't mean you don't have to be careful, it's super easy to accidentally block the event loop.
Re: What async promised and what it delivered
#309Earlier quoted context omitted.
If we invented a new language, sure. Cooperative multitasking might be a fun approach. The avalanche of bugs I’m imagining would come from existing JavaScript code being run in a different context than that in which it was written and tested. If you pass me a callback right now, and I call a(); callback(); b();. I can guarantee that the program doesn’t yield to the event loop or other executions between a() and b().…
> The avalanche of bugs I’m imagining would come from existing JavaScript code being run in a different context than that in which it was written and tested. Oh, right. As you said, the ship has sailed. I think you could bolt green threads onto javascript now without ill effects - apart from bloating the language. I can't see anything that could go badly (certainly no avalanche of bugs). But in javascript green threa…
- You can't name the type of a impl Future.
- They play terribly with the borrow checker because the borrow checker can't handle self referential types.
- There's no future executor in the standard library. You need 3rd party libraries. And the most common library is tokio, which is a whale.
- Despite all the work, there's still no async streams in the language.
- Pin. !Unpin. pin_project. Unsafe pin_project. What are we even doing.
But async works really well in javascript. Maybe where we disagree is that I don't think any of these issues are because async itself is a bad idea. But, async has become the place dreams go to die in rust. Look at the issues above. They're all problems with rust's type system, borrow checker and standard library.
What I think rust needs is:
- A way to have self-borrows in a struct. Types with self borrows would be implicitly pinned.
- A way to name the return value of a function. Eg let x: ReturnType. People have been saying this is right around the corner since 2019.
- Generators. Futures are built on top of generators inside the compiler. But generators have - for some reason - never been exposed in stable rust. I think generators should have been stabilised first - since all the problems you need to solve to make generators work well (self referential types, return values you can name, etc) are things futures need too.
Unfortunately I think that ship has sailed too. I try to avoid async rust whenever I can. Its such a pity. I'm hoping someone makes a rust 2.0 language at some point which fixes this situation.
Re: What async promised and what it delivered
#310Earlier quoted context omitted.
You literally can't use one of the other async run times because of the current state of async/await does not allow library authors to easily write for multiple runtimes - they were written for one runtime in mind and that is just tokio. And if you're pulling in library methods you're still stuck with the method headers they specify. All of your arguments are just mental workarounds trying to justify how fucked the r…
You’re moving the goalposts, and seem to have a vested interest in this that I, frankly, don’t. Send/Sync/static is not needed on tokio’s local runtime, which doesn’t require any adjustments to your libraries. Passing data between threads requires Send/Sync/static, except for certain cases like scoped threads, so making OS threads faster doesn’t seem to solve that issue like using a local runtime would. Many async li…
My entire goal is to show that coding the same server with pre-async hyper vs post async hyper is nicer and more performant than async rust. I hope to show you it in just a few days.