3 ms·
It's amusing that the Rust Playground lets you run a hundred threads. That's generous of them. There's a ceiling below 1000, though. The author points out, "On
by Animats 2y ago
It's amusing that the Rust Playground lets you run a hundred threads. That's generous of them. There's a ceiling below 1000, though. The author points out, "On my Linux laptop I can spawn almost 19k threads before I hit this crash, but the Playground has tighter resource limits." Large servers can go higher than that.
The thread-based I/O example with the compute bound poll loop is kind of strange.
"Join" isn't really that useful when you have unrelated threads running finite tasks. Usually, you let the thread do its thing,finish, put results on a queue, and let the thread exit. Then it doesn't matter who finishes first.
Rust join is actually optional. You don't have to join to close out a thread and reap the thread resources. It's not like zombies in Unix/Linux, where some resources are tied up until the parent process calls wait().
Loops where you join all the threads that are supposedly finished are usually troublesome. If somebody gets stuck, the joiner stalls. Clean exits from programs with lots of channels are troublesome. Channels with multiple senders don't close until all senders exit, which can be hard to arrange when something detects an error.
In Rust, the main thread is special. (I consider this unfortunate, but web people like it, because inside browsers, the main thread is very special.) If the main thread exits, all the other threads are silently killed.
- diath 2y ago> Loops where you join all the threads that are supposedly finished are usually troublesome. If somebody gets stuck, the joiner stalls. That makes sense if the main thread is actually doing any useful work, but when its only job is to spawn threads and wait for them to finish before exiting, then it's a pretty common idiom.
- willglynn 2y ago> In Rust, the main thread is special. (I consider this unfortunate, but web people like it, because inside browsers, the main thread is very special.) If the main thread exits, all the other threads are silently killed. Rust inherits this from `pthread_detach()`: The detached attribute merely determines the behavior of the system when the thread terminates; it does not prevent the thread from being terminated if the process terminates using exit(3) (or equivalently, if the main thread returns).
- wahern 2y agoThe main thread is special because that's how the runtime works on Unix. In particular, when "main" exits, the process exits. This is required by the C standard. It's also fundamentally built into how Unix processes work, as certain global variables, like argv and environ strings, typically are stored on the main thread's stack, so if the main thread is destroyed those references become invalid. In principle Rust could have defined its environment to not make the main thread special, but then it would need some additional runtime magic on Unix systems, including having the main thread poll for all other threads to exit, which in turn would require it to add a layer of indirection to the system's threading runtime (e.g. wrapping pthreads) to be able to track all threads.
- kelnos 2y ago> In principle Rust could have defined its environment to not make the main thread special... Not to mention they'd have to be very careful with what they do on the main thread after they start up the application's first thread (e.g. allocating memory via malloc() is out), since there are quite a few things that are not safe to do (like fork() that's not immediately followed by exec()) in a multi-threaded program. So even a "single-threaded" Rust program would become multi-threaded, and assume all those problems.
- oconnor663 2y ago> Rust join is actually optional. I was recently surprised to learn that returning from main() with background threads still running is more or less UB in C++, because those threads can race against static destructors: https://www.reddit.com/r/cpp/comments/1fu0y6n/when_a_background_thread_races_against_the https://www.reddit.com/r/cpp/comments/1fu0y6n/when_a_backgro.... C doesn't have this issue, though, as far as I know?
- shepmaster 2y ago> the Rust Playground lets you run a hundred threads It's more that we don't do anything to prevent it, other than coarse process-wide memory / CPU time limits. IIRC, Rust-spawned threads on Linux use 2MiB of stack space by default, so that seems like a likely cap. Note that the playground is only 2 cores and you are sharing with everyone else, so you aren't likely to really benefit.
- littlestymaar 2y ago> Note that the playground is only 2 cores and you are sharing with everyone else This is amazing, I use it all the time with no performance issues so I expected it to be much beefier to support many simultaneous users. How many users does it serve? (Monthly or daily user and/or compilation job sent). And what tricks are used to keep it working? (I suspect it can re-use already compiled binaries of all supported dependencies and only need to compile the user's code and link it, but is there other clever strategies?)
- shepmaster 2y ago> How many users does it serve? I don't really track users, but over the last 24 hours, there were 47.8k meaningful [1] requests taking a total of 28.2 hours. That ~0.5 requests per second number has been relatively consistent. > re-use already compiled binaries of all supported dependencies and only need to compile the user's code and link it, but is there other clever strategies? Yes, we pre-compile all the available dependencies [2] and that's about it. > I use it all the time with no performance issues That's good to hear! There's been a long-running bug where the playground binary loses track of the child Docker container (maybe?) and then the machine runs out of memory and the OOM killer often does more harm than good [3]. While trying to pin that down, I've recently caused the entire process to get into what appears to be a complete deadlock where no requests can be serviced at all. This tends to happen while I'm asleep so either I have no chance to debug it before it is auto-killed or the playground is unresponsive for 8+ hours. [1]: compiling / executing code, running clippy/miri/rustfmt, expanding macros [2]: https://github.com/rust-lang/rust-playground/blob/c4d00b90aab3742c841011b29fba038f3cd99690/compiler/base/Dockerfile#L105-L107 https://github.com/rust-lang/rust-playground/blob/c4d00b90aa... [3]: somehow it does something that ends up killing the network stack and then the machine is basically dead in the water. Very similar to what is reported in https://serverfault.com/q/1125634/119136 https://serverfault.com/q/1125634/119136
- dwattttt 2y ago> Loops where you join all the threads that are supposedly finished are usually troublesome. If somebody gets stuck, the joiner stalls. Clean exits from programs with lots of channels are troublesome. Channels with multiple senders don't close until all senders exit, which can be hard to arrange when something detects an error. I wish join-with-timeout was a more common/supported operation.