crossbeam icon indicating copy to clipboard operation
crossbeam copied to clipboard

Memory leak in `crossbeam_utils::thread::scope()`: Unbounded memory usage with long-lived scopes

Open BGR360 opened this issue 3 years ago • 5 comments

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.

BGR360 avatar Apr 20 '22 05:04 BGR360

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.

nickkuk avatar Apr 20 '22 07:04 nickkuk

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.

m-ou-se avatar May 17 '22 15:05 m-ou-se

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.

m-ou-se avatar May 17 '22 15:05 m-ou-se

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.

taiki-e avatar May 22 '22 08:05 taiki-e

Filed #844 to fix this.

taiki-e avatar May 31 '22 16:05 taiki-e