Not joining all threads = memory leaked on process exit
- Dominant language
- Rust
- Stars
- 4.1k
- Forks
- 339
- PR merge metrics
- No merged PRs in 30d
Description
Hi. I'm implementing a library at a low level which allocates and frees memory manually. To try to keep it correct I run example binaries and tests under Valgrind a lot. However, when using `async-std` my programs and tests very often seem to not correctly join threads before exiting, resulting in leaks that make Valgrind sad and my automated tests fail. I experience this on Linux and have not tried it elsewhere.
A simple failing test. Placed under `tests/all_tests.rs`:
```rust
#[async_std::test]
async fn just_sleep() {
async_std::task::sleep(std::time::Duration::from_millis(1)).await;
}
```
You can run it under vanilla Valgrind with `valgrind --leak-check=full ./target/debug/whatever_the_filename_is`. But `cargo valgrind` has prettier output. Due to the racy nature of this it might not always fail, but for me it does ~90% of the runs:
```
$ cargo valgrind --test all_tests
....
Error Leaked 24 B
Info at malloc (vg_replace_malloc.c:309)
at alloc::alloc::alloc (alloc.rs:84)
at alloc::alloc::exchange_malloc (alloc.rs:206)
at alloc::sync::Arc::new (sync.rs:302)
at futures_timer::global::current_thread_waker (global.rs:104)
at futures_timer::global::run (global.rs:59)
at futures_timer::global::HelperThread::new::{{closure}} (global.rs:28)
at std::sys_common::backtrace::__rust_begin_short_backtrace (backtrace.rs:126)
at std::thread::Builder::spawn_unchecked::{{closure}}::{{closure}} (mod.rs:470)
at as core::ops::function::FnOnce<()>>::call_once (panic.rs:315)
at std::panicking::try::do_call (panicking.rs:292)
at __rust_maybe_catch_panic (lib.rs:80)
Error Leaked 288 B
Info at calloc (vg_replace_malloc.c:762)
at allocate_dtv
at _dl_allocate_tls
at pthread_create@@GLIBC_2.2.5
at std::sys::unix::thread::Thread::new (thread.rs:67)
at std::thread::Builder::spawn_unchecked (mod.rs:489)
at std::thread::Builder::spawn (mod.rs:382)
at futures_timer::global::HelperThread::new (global.rs:26)
at ::default (timer.rs:281)
at futures_timer::delay::Delay::new (delay.rs:39)
at async_std::io::timeout::timeout::{{closure}} (timeout.rs:40)
at as core::future::future::Future>::poll::{{closure}} (future.rs:43)
Summary Leaked 312 B total
```
A very similar leak stack trace can be obtained from the following test:
```rust
#[test]
fn just_sync_sleep() {
std::thread::spawn(move || {
std::thread::sleep(std::time::Duration::from_millis(1));
});
}
```
Which is fixed by joining all threads before exiting:
```rust
#[test]
fn just_sync_sleep() {
let t = std::thread::spawn(move || {
std::thread::sleep(std::time::Duration::from_millis(1));
});
t.join()
}
```
This leads me to suspect the `#[async_std::main]` and `#[async_std::test]` macros don't properly join all threads before exiting.
Contributor guide
Assessment
This issue has not been assessed yet.