Memory leak in `crossbeam_utils::thread::scope()`: Unbounded memory usage with long-lived scopes
crossbeam_utils::thread::Scope owns a vector of shared join handles:
https://github.com/crossbeam-rs/crossbeam/blob/450d237d1546ac135d21f75b61fdf5a944e8421c/crossbeam-utils/src/thread.rs#L193-L203
Each call to scope.spawn() pushes a new handle to the vec:
https://github.com/crossbeam-rs/crossbeam/blob/450d237d1546ac135d21f75b61fdf5a944e8421c/crossbeam-utils/src/thread.rs#L458-L459
The vec never shrinks until it is drained all at once at the end of scope():
https://github.com/crossbeam-rs/crossbeam/blob/450d237d1546ac135d21f75b61fdf5a944e8421c/crossbeam-utils/src/thread.rs#L167-L176
Thus, any long-lived thread scope will exhibit unbounded memory usage as it spawns more and more threads, even if those threads finish executing or are joined by the user. This is a huge problem for servers or other such long-running applications that want to spawn scoped threads.
I would understand if there are reasons that scope() can't be implemented in such a way where these handles are cleaned up as threads complete. But if that is the case, then I feel this needs to be a very explicitly warned about in the crate's documentation and README. Cuz this is really bad. We believe that this issue is the root cause of a recent out-of-memory crash on an enterprise customer's deployment of our software product.
Partially related: as I know this functionality is slowly moving to std. As far as I can see their implementation doesn't have such a problem.
Partially related: as I know this functionality is https://github.com/rust-lang/rust/issues/93203. As far as I can see their implementation doesn't have such a problem.
Indeed! The not-yet-stable std::thread::scope's memory usage stays constant, independent of how many threads it spawned over its lifetime.
Note that since crossbeam's scope function returns an Err containing a Vec of all the panic payloads of all the threads that panicked, this is partially a problem of the API itself. Even if it didn't keep join handles for already-finished threads, it'd still have to keep all the panic payloads around to be able to return them, resulting in unbounded memory usage when threads keep panicking.
If someone can implement an equivalent to the standard library's scoped thread API without relying on unstable features, I would like to change the crossbeam's scoped thread API in the next breaking release.
In any case, once the standard library's scoped thread is stable, I plan to soft deprecate the crossbeam's scoped thread.
Filed #844 to fix this.