Live data from Hacker News

Local async executors and why they should be the default

maciej.codes

181–190 of 327 posts

Re: Local async executors and why they should be the default

#181

Earlier quoted context omitted.

> The supervisory system is an extra, you don't technically need it but it can help make your application bullet proof and I would definitely recommend if you go the Erlang route to use it to your advantage. An actor system without supervisors is throwing away a lot. How do you handle an actor that crashes? The thing is, again, Actors are very low level - they're a foundational model. You end up having to build proto…

I am responding to the very last point about dropping a future and the resource usage. Let's assume a future that implements reading from a network socket. And the file descriptor (fd) was already passed to epoll/select (I am ignoring io_uring). After dropping the future, the socket is is still being considered by the kernel for reading. Eventually epoll will return, and the IO runtime (like tokio) will notice the st…

I'm not sure how an actor would solve that problem. You're describing the way a system call works, which is not relevant to how your userland asynchrony works. In either case you're always "leaking" (I'd say "holding") the resource while you wait for the kernel to give it back to you.

Re: Local async executors and why they should be the default

#182
post #165

Earlier quoted context omitted.

Maybe for prototypes or small scripts but disagree otherwise. The promise of automatic memory management is that you don't have to think about memory. But the second you don't think about memory you are doomed to write bad code anyway. Maybe because of performance but most probably due to architecture. To have a language that forces you to think about memory is not a curse, it is a blessing.

> But the second you don't think about memory you are doomed to write bad code anyway. Yeah but the failure modes are different. "Doomed" in C++ means now I've got worms all over my network. Pretty much a fully negative outcome. "Doomed" in Rust means I can't change it anymore until I relearn and refactor everything. If it's a pacemaker or rocket engine, that's probably ideal. But if it's not... "Doomed" in Go or Jav…

The promise with a good architecture is that the doom part is more infrequent though.

And that when the Go/Java doom scenario bites you you'll have a whole lot of technical debt to pay back.

Re: Local async executors and why they should be the default

#183
post #177
post #175

Earlier quoted context omitted.

Great that D, Go, Swift, C#, Nim, Eiffel, Common Lisp, Haskell, OCaml,.... also offer ways of doing it.

You don't seem to care but you are missing the point.

Nope, you're the one missing the point, just because a language has some kind of automatic memory management, it doesn't mean C like features for deterministic manual memory management, or any other kind of manual resource management aren't available.

Learn to use them, and then you'll get the point.

Re: Local async executors and why they should be the default

#184
post #182

Earlier quoted context omitted.

> But the second you don't think about memory you are doomed to write bad code anyway. Yeah but the failure modes are different. "Doomed" in C++ means now I've got worms all over my network. Pretty much a fully negative outcome. "Doomed" in Rust means I can't change it anymore until I relearn and refactor everything. If it's a pacemaker or rocket engine, that's probably ideal. But if it's not... "Doomed" in Go or Jav…

The promise with a good architecture is that the doom part is more infrequent though. And that when the Go/Java doom scenario bites you you'll have a whole lot of technical debt to pay back.

There are more languages to chose from with automatic memory management than Go and Java.

Chose the one that provides the right set of tools for high performance code.

Re: Local async executors and why they should be the default

#185
post #184
post #182

Earlier quoted context omitted.

The promise with a good architecture is that the doom part is more infrequent though. And that when the Go/Java doom scenario bites you you'll have a whole lot of technical debt to pay back.

There are more languages to chose from with automatic memory management than Go and Java. Chose the one that provides the right set of tools for high performance code.

Jeeez, I was just commenting on the scenarios from parent.

Re: Local async executors and why they should be the default

#186
post #183
post #177

Earlier quoted context omitted.

You don't seem to care but you are missing the point.

Nope, you're the one missing the point, just because a language has some kind of automatic memory management, it doesn't mean C like features for deterministic manual memory management, or any other kind of manual resource management aren't available. Learn to use them, and then you'll get the point.

? I didn't claim that.

Re: Local async executors and why they should be the default

#187
post #76

Earlier quoted context omitted.

I'm confused what you mean here. If you have a function that "depends on" another function you call it within the other function. If it's async you .await it. Do you mean something about spawning tasks or passing callbacks around?

I have this function that I still struggle to write. async fn function_handler(_event: Request) -> Result , Error> { let aws_clients = AwsClients { s3_client: S3_CLIENT.get().await.to_owned(), glue_client: GLUE_CLIENT.get().await.to_owned(), athena_client: ATHENA_CLIENT.get().await.to_owned(), }; println!("{}", aws_clients); let config = CONFIG.get().await; let version = VERSION.get().await; let db_eid_cache = create…

If you want to .await multiple futures that can return an error you can use tokio::try_join, futures_utils::try_join_all, etc. For example:

   let (
        db_eid_cache,
        table_eid_cache,
        job_eid_cache,
        table_summaries_cache,
        job_summaries_cache,
        query_summaries_cache,
    ) = try_join!(
        create_db_eid_cache(aws_clients.clone(), config),
        create_table_eid_cache(aws_clients.clone(), config),
        create_job_eid_cache(aws_clients.clone(), config),
        create_table_summaries_cache(aws_clients.clone(), config),
        create_job_summaries_cache(aws_clients.clone(), config),
        create_query_summaries_cache(aws_clients.clone(), config),
    )?;

The code dealing with your clients looks suspect. Why do you have a global with a .get method that is async and returns a reference? It should probably be sync. On top of that it probably shouldn't be a global variable. Pass it in as context to the handler.

I'm also suspicious of having this `AwsClients` struct and a bunch of free functions that take it by owner. Why aren't you adding an `impl` block with those as methods? And why are they taking ownership requiring the .clone`?

Re: Local async executors and why they should be the default

#188
post #182

Earlier quoted context omitted.

> But the second you don't think about memory you are doomed to write bad code anyway. Yeah but the failure modes are different. "Doomed" in C++ means now I've got worms all over my network. Pretty much a fully negative outcome. "Doomed" in Rust means I can't change it anymore until I relearn and refactor everything. If it's a pacemaker or rocket engine, that's probably ideal. But if it's not... "Doomed" in Go or Jav…

The promise with a good architecture is that the doom part is more infrequent though. And that when the Go/Java doom scenario bites you you'll have a whole lot of technical debt to pay back.

But you're arguing in circles.

If attention is paid, if the architecture is good, all else like programmer salary/skill being equal, any of those languages (and plenty of others) will work fine. (And honestly, not a huge difference in the work to achieve that, a good design will look about the same in all four.)

It's only an interesting question which to use if attention is not paid, or you don't want to invest enough to ensure attention will be paid; in which case you need to look at the causes and effects of that.

Re: Local async executors and why they should be the default

#189
post #52
post #38

Earlier quoted context omitted.

Eh, in that case, you can also just write single or multithreaded Rust code, entirely ignoring async Rust. I do. And multithreaded Rust is much more pleasant experience than multithreaded C++. Which dependency is forcing async Rust on you? Alternatives are usually available.

Postgres (which is a wrapper around tokio-postgres, and as a result drags the whole tokio bloated dependency graph). I would love to have a purely sync alternative to that.

I am so glad someone said this. This also shows nicely how async is wrong, not just that it's viral, but it's bad design because it forces code duplication.

Middleware/library writers that touch on anything that could be async (db, SPI, network etc.) will now have to write two versions of their API and duplicate most code.

Re: Local async executors and why they should be the default

#190
The thing the article calls bad is not the thing the examples illustrate. The set-up is supposed to be that multithreading is a pain, but none of the examples actually do that. Take the initial list. "These are not async primitives, these are multi-threading synchronization primitives." Yeah, but they're multi-threaded forms of stuff you still need. If you needed RwLock in multi-threaded mode, you need RefCell in single-threaded mode, and the API is virtually identical. If your state needed to be in an Arc, it'll need to be in an Rc. And if you needed sync::mpsc, you'll instead need... sync::mpsc.

A Send bound is no great burden. The only type that you will regularly interact with that is not Send is the lock type from a Mutex or RwLock, which is good because if you hold it across a long await you can slow down your app, a bug prevention mechanism that does not exist in straight multithreading. The only point that actually illustrates a threading-caused problem is the thing about multithreading sockets, which it admits is almost imperceptible, and which you can also solve by not doing that.

Almost everything the author identifies as a parallelism problem is a 'static problem. Tokio is missing a scoped spawn like std has, and if it gained one then the much described multithreading woes would reduce to basically nothing.

Post reply on HN