17 ms·
Not OP, but it won't yield to the runtime. This blocks the executing thread until the operation completes. That's the only sense in which the standard library i
by herbstein 4y ago
Not OP, but it won't yield to the runtime. This blocks the executing thread until the operation completes. That's the only sense in which the standard library isn't async safe.
Additionally, according to a core Tokio developer, the time between .await calls should be in the order of 100s of microseconds at most to ensure the best throughput of the runtime.
- jamincan 4y agoIsn't that a bit different from it not being safe, though? Ill-advised perhaps, but not necessary unsafe.
- dcow 4y agoIf you make too many of these calls your program will stop executing until I/O becomes available because you've exhausted your execution threads. If I/O never comes, which is very possible especially over a network connection, then you're stalled. Same outcome as being deadlocked, essentially. I'd argue this is very much unsafe because it's not correct async programming and can cause really difficult to debug problems (which is the same reason we care about memory safety--technically I can access any part of the pages mapped to my process, it's just not advisable to make assumptions about the contents of regions that other parts of the program might be concurrently modifying). The fact that people have never heard of this or don't understand the nuance kinda proves the point (=. You should, ideally, have to opt in to making blocking calls from async contexts. Right now it's all too easy to stumble into doing it and the consequences are gnarly in the edge cases, just like memory corruption.
- brundolf 4y agoHmm. In the worst-case though, wouldn't it just be equivalent to a non-async version of the program? I.e. "blocking" is what every normal function already does. Blocking for IO might take longer, but again, that's what normal Rust programs already do. So to me it sounds like "not async-optimized" rather than "not async-safe" But none of that sounds anything like a deadlock, so maybe I'm missing some other aspect here As an aside: "safe" means a very specific thing in Rust contexts, so even if this is analogous to that (preventing a footgun that could have catastrophic consequences), it might be best to use a different word or at least disambiguate. Otherwise people will argue with you about it :)
- hdevalence 4y agoCorrect, there’s no actual problem here, just a potential performance pitfall, but that pitfall is no worse than a naive synchronous implementation would be. There’s no big problem here, just write code and run it. If it is a really big problem it will show up in a profiler (if you care enough about performance to care about this issue, you are using a profiler, right… right?)
- dcow 4y agoI guess you've never written async code. You can irreparably stall the entire program. I've done it. The person you're responding to is not correct in their analysis.
- dcow 4y ago> In the worst-case though, wouldn't it just be equivalent to a non-async version of the program? No. You can absolutely stall (I'm hesitant to use the phrase deadlock because you're not locking, specifically) an async program. More specifically, you can block all of the available scheduler threads. To see for yourself, spawn a bunch of async tasks in a loop that all increment and print a shared counter and then sleep forever. The counter will stop incrementing at some point. Compare that number to the number of scheduler threads your runtime uses. It should look similar. I think maybe you're conceptually comparing a single threaded Rust program to an async program, which isn't quite accurate. A "normal" program in my experience has many threads. All of these threads can block. One of them blocking does not inherently slow down the rest of the program. Try the above example but spawn a new thread instead of a new async task during each loop iteration. The number will increment much higher, at least until you've exhausted some system limit on the number of threads a process can have. Maybe there are fancy async runtimes that dynamically expand their threadpool as the number of blocked tasks increases. But naively they all use a threadpool sized at some multiple of the number of real cores your system has, and so will eventually stall if starved. In any case, it's not generally async-safe to block any thread, ever (obviously if you could prove that the number of async tasks that block is always less than the number of threads the runtime uses, this doesn't hold, but that's a rather wild assumption). And it's impossible for say a library to know the context in which it's being used, so it's never correct for a library that is to be called in an async context to block. It must always yield. You're right that in the common case things will just appear to slow down since most things don't block for a long time and eventually continue. But there are gnarly edge cases where the entire program stalls. Hence my comparison to memory safety. In the common case memory corruption and dangling pointers aren't horrible they just sometimes cause spurious errors which most programs can recover from. But every once in awhile they cause really bad problems which programs can't recover. Hence why we care. For a real world example, imagine a server that processes requests. In the async handler block, for some requests you access the filesystem and the device starts experiencing degraded performance due to media errors and read calls never complete. Should the server stop processing healthchecks because some of the requests are having trouble? The answer is almost certainly no. Instead the server should report in its healthcheck that one of its block devices has an increased error rate and tasks are piling up. Another case would be one involving locks. If you are dealing with a program that involves many workers/consumers waiting for a producer to produce a value and the workers use a blocking api to wait on a semaphore before reading a next value from memory and continuing, the program will almost certainly deadlock not because of a logic error leading to an imbalanced semaphore but instead because the async runtime will stall and the producer will never produce a value. If instead the tasks yield as they should, the program would progress normally.