Live data from Hacker News

What async promised and what it delivered

causality.blog

271–280 of 317 posts

Re: What async promised and what it delivered

#271
post #260
post #94

Earlier quoted context omitted.

> If you are ten layers deep in a stack of synchronous functions and suddenly need to make an asynchronous call, the type signature of every individual function in the stack has to change. well, this isn't really true - at least for Rust: runtime.block_on(async{}); https://docs.rs/tokio/latest/tokio/runtime/struct.Handle.htm...

See my other post about the point. If you "just" turn an async back into a sync call by completely blocking the async scheduler, yes, you've turned the async call back into a sync call, but you've done that by completely eliminating async-ness. That's not a general solution. That is exactly what everyone back when Node was promoting this style spent paragraph upon paragraph warning you not to do, because it just punt…

> If you "just" turn an async back into a sync call by completely blocking the async scheduler,

I am not doing that. The caller (which is the only one being blocked here) is sync anyways and just wants to call an async function, so no async scheduler is blocked.

Re: What async promised and what it delivered

#272
post #180

Earlier quoted context omitted.

> Not all code written benefits from async nor even requires it. Running single threaded, sync programs is totally valid. Maybe, but is it useful to have sync options? You can still write single threaded programs

I mean single threaded + sync. Sync options are useful. If everything is on the net probably less so. But if you have a couple of 1ms io ops that you want to get done asap, it's better to get them done asap.

> But if you have a couple of 1ms io ops that you want to get done asap, it's better to get them done asap.

and async prevents this how?

Re: What async promised and what it delivered

#273

> OS threads are expensive: an operating system thread typically reserves a megabyte of stack space and takes roughly a millisecond to create. It's typically less than a hundred kilobytes and (on the systems I've benchmarked using std::thread) it takes 60usec (wall time in userspace) to create and destroy a thread. Threads have gotten so fast that paying the async function coloring price makes very little sense for m…

> Threads have gotten so fast that paying the async function coloring price makes very little sense for most software.

I agree with this 100%.

If you're getting paid to bin pack jobs that do lots of concurrent I/O into a server infrastructure, then yes, building these complex async "machines" and forcing everyone to do the extra labor to use them is necessary to avoid a lot of waste.

But for everyone else, it's a huge waste of time. Even low end modern embedded Linux systems are capable of running thousands of threads.

Synchronous code is always simpler to write, easier to reason about, more straightforward to test, and faster to debug. Nobody should ever reach for async by default.

Re: What async promised and what it delivered

#274

Earlier quoted context omitted.

In my experience people complain about it because they are coming from a blocking first mindset. They're trying to shoehorn async calls into an inherently synchronous structure. A while back I just started leaning in. I write a lot of Python at work, and anytime I have to use a library that's relies on asyncio, I just write the entire damn app as an asynchronous one. Makes function coloring a non-issue. If I'm in a s…

> In my experience people complain about it because they are coming from a blocking first mindset. They're trying to shoehorn async calls into an inherently synchronous structure. There's no "inherently synchronous structure", at least not in Javascript. The nature is synchronous, asynchronous is an illusion built on top of it. Which is why you can easily block an "asynchronous" program: while (true) {} on any async…

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 in parallel.

When you use the multiprocess library you are actually creating new threads that run in parallel (I'm ignoring the threading library because it just muddies the water). That's also asynchronous execution.

I don't know the semantics as well in JavaScript, but I'm sure the principle is the same. At certain points you can yield to the runtime and it will schedule other pending tasks to execute while the current one is pending.

My point was that mistake I see people make (in Python) is they think of their program in blocking terms by default. So they get this frustrating coloring problem because they are trying to shoehorn in non-blocking calls. If instead you design the application from the start with asyncio in mind, it makes things much simpler.

Re: What async promised and what it delivered

#276
post #270
post #193

Earlier quoted context omitted.

Basically it’s the non-linear execution flow creating situations which are harder to reason about. Here’s an example I’m trying to help a Node team fix right now: something is blocking the main loop long enough that some of the API calls made in various places are timing out or getting auth errors due to the signature expiring between when the request was prepared and when it is actually dispatched because that’s spo…

I still don't get it. The execution flows of individual async tasks are still linear, much like individual threads are linear. Scheduling (tasks by the async runtime vs threads by the OS), however results in random execution order either way. If there is a slow resource, both, async tasks as well as threads will pile potentially increasing response times. Wether async or threads, you can easily put a concurrency limi…

> The execution flows of individual async tasks are still linear, much like individual threads are linear.

Think about what happens:

1. Request one hits an await in foo()

2. Runtime switches to request two in bar() until it awaits

3. Runtime switches to request three in baaz(), which blocks the loop for a while

4. Request one gets a socket timeout or expired API key

That error in #4 does not tell you anything about #2 or #3, and because execution spreads across everything in that process you have to check everything. If it was a thread, you would either not have the problem at all, it would show up clearly in request three, or you’d have a clear informative failure on a synchronization primitive saying that #3 held a lock for too long.

That makes it harder to control when memory is allocated or released in garbage collected languages, too, because you have to be very careful to trigger gc before doing something which can suspend execution for a while or you’ll get odd patterns when a small but non-zero percentage of those async requests take longer than expected (i.e. load image master, create derivative, send response needs care to release the first two steps before the last or you’ll have weird behavior when a slow client takes 5 minutes to finish transferring that response).

Arguably that’s something you want to do anyway but it dramatically undercuts the simplicity benefits of async code. I’m not saying that we should all give up async but there are definitely some pitfalls which many people stumble into.

Re: What async promised and what it delivered

#277
post #236

Earlier quoted context omitted.

I think they mean tokio::spawn’s signature forces libraries that want to be easy to use with it to expose send+sync APIs (and thus use Arc+Mutex internally)

See what they wrote: > Async ruined Rust for me, even though I write exactly the kind of highly concurrent servers to which it's supposed to be perfectly suited. It degrades API It refers to async in Rust, and everyone else is responding as if it is talking about async in Rust. That's a mischaracterization. You don't have to use the Tokio executor. It's a bit like saying, "Graphics in Rust are ruined. I need to use V…

I generally agree but taking a birds eye view I totally understand why tokio’s defaults cloud the image of the entire ecosystem

Re: What async promised and what it delivered

#278
post #272

Earlier quoted context omitted.

I mean single threaded + sync. Sync options are useful. If everything is on the net probably less so. But if you have a couple of 1ms io ops that you want to get done asap, it's better to get them done asap.

> But if you have a couple of 1ms io ops that you want to get done asap, it's better to get them done asap. and async prevents this how?

my statement was in response to "fs.readSync shouldn't exist". that is how.

Re: What async promised and what it delivered

#279

Earlier quoted context omitted.

I completely disagree. Having to make sure every little function is Send + Sync + lifetime even if it doesn't need it is fucking hell. writing concurrent code with plain kernel threads is so much easier to write and read. If you just want to build a normal backend service, you can't escape async libraries. Wrapping the async functions with `block_on` is not ideal I'd rather just have access to standard sync primitive…

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 rust ecosystem is for traditional backend services.

The project I'm working on is specific to making traditional kernel threads faster (150-200 nanosecond context switches compared to 1500-2000 nano seconds for normal kernel threads). It requires a user scheduler but you can swap those out without any changes to how you write rust. In my testing, it's not only faster than async rust but also much easier to write. I hope it convinces people like you, that are hell-bent on defending the current state of async rust, that there are better paradigms and we don't have to be locked in to shitty, verbose concurrent code.

Re: What async promised and what it delivered

#280

Earlier quoted context omitted.

Thank you for this! This is really helpful. The UMCG implementation allows kernel thread context switches to happen in 150-200 microseconds, compared to the 1500-2000 microseconds for normal kernel thread context switches. My goal is to show that if UMCG could be merged into the Linux run time then then it would be competitive with async rust without the headache.

Did you mean nanoseconds instead of microseconds?

Yes - brain fart.
Post reply on HN