Skip to content

Valgrind shows memory leak with possibly lost when running tests with cargo test #141084

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
nablaa opened this issue May 16, 2025 · 4 comments
Closed
Labels
A-valgrind-full-leak Area: memory leaks detected by valgrind leak-check=full C-discussion Category: Discussion or questions that doesn't represent real issues. T-libs Relevant to the library team, which will review and decide on the PR/issue.

Comments

@nablaa
Copy link

nablaa commented May 16, 2025

When running cargo test under valgrind will full leak check, valgrind shows a memory leak backtrace with "possibly lost" bytes.

This happens with the default template code generated by cargo new / cargo init and affects stable rust versions starting from 1.86.0. No memory leak is observed with older 1.85.0.

Repro:

cargo init --lib

echo No memleak with 1.85
valgrind --error-exitcode=42 --tool=memcheck --leak-check=full $(cargo +1.85.0 test --no-run --message-format=json | jq  -r 'select(.reason=="compiler-artifact") | .executable | select(. != null)')

echo Memleak with 1.86
valgrind --error-exitcode=42 --tool=memcheck --leak-check=full $(cargo +1.86.0 test --no-run --message-format=json | jq  -r 'select(.reason=="compiler-artifact") | .executable | select(. != null)')

echo Memleak with latest nightly
valgrind --error-exitcode=42 --tool=memcheck --leak-check=full $(cargo +nightly test --no-run --message-format=json | jq  -r 'select(.reason=="compiler-artifact") | .executable | select(. != null)')

I expected to see this happen: No memory leaks reported.

Instead, this happened: Memory leaks reported for 1.86.0 as well as the latest nightly:

No memleak with 1.85:

    Creating library package
note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

   Compiling foo v0.1.0 (/tmp/foo)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.15s
==866796== Memcheck, a memory error detector
==866796== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==866796== Using Valgrind-3.22.0 and LibVEX; rerun with -h for copyright info
==866796== Command: /tmp/foo/target/debug/deps/foo-60a4b72f22d3bfbc
==866796==

running 1 test
test tests::it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.07s

==866796==
==866796== HEAP SUMMARY:
==866796==     in use at exit: 0 bytes in 0 blocks
==866796==   total heap usage: 638 allocs, 638 frees, 77,348 bytes allocated
==866796==
==866796== All heap blocks were freed -- no leaks are possible
==866796==
==866796== For lists of detected and suppressed errors, rerun with: -s
==866796== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Memleak with 1.86:

Memleak with 1.86
   Compiling foo v0.1.0 (/tmp/foo)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.15s
==866870== Memcheck, a memory error detector
==866870== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==866870== Using Valgrind-3.22.0 and LibVEX; rerun with -h for copyright info
==866870== Command: /tmp/foo/target/debug/deps/foo-c47a447f412d7bfc
==866870==

running 1 test
test tests::it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.07s

==866870==
==866870== HEAP SUMMARY:
==866870==     in use at exit: 48 bytes in 1 blocks
==866870==   total heap usage: 639 allocs, 638 frees, 77,374 bytes allocated
==866870==
==866870== 48 bytes in 1 blocks are possibly lost in loss record 1 of 1
==866870==    at 0x4846828: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==866870==    by 0x1830A7: alloc (alloc.rs:96)
==866870==    by 0x1830A7: alloc_impl (alloc.rs:192)
==866870==    by 0x1830A7: allocate (alloc.rs:254)
==866870==    by 0x1830A7: {closure#0}<std::thread::Inner> (sync.rs:484)
==866870==    by 0x1830A7: allocate_for_layout<core::mem::maybe_uninit::MaybeUninit<std::thread::Inner>, alloc::sync::{impl#14}::new_uninit::{closure_env#0}<std::thread::Inner>, fn(*mut u8) -> *mut alloc::sync::ArcInner<core::mem::maybe_uninit::MaybeUninit<std::thread::Inner>>> (sync.rs:1952)
==866870==    by 0x1830A7: new_uninit<std::thread::Inner> (sync.rs:482)
==866870==    by 0x1830A7: std::thread::Thread::new (mod.rs:1429)
==866870==    by 0x11B719: std::thread::current::init_current (current.rs:227)
==866870==    by 0x11BC53: current_or_unnamed (current.rs:184)
==866870==    by 0x11BC53: std::sync::mpmc::context::Context::new (context.rs:72)
==866870==    by 0x11A111: __init (context.rs:43)
==866870==    by 0x11A111: call_once<fn() -> core::cell::Cell<core::option::Option<std::sync::mpmc::context::Context>>, ()> (function.rs:250)
==866870==    by 0x11A111: unwrap_or_else<core::cell::Cell<core::option::Option<std::sync::mpmc::context::Context>>, fn() -> core::cell::Cell<core::option::Option<std::sync::mpmc::context::Context>>> (option.rs:1023)
==866870==    by 0x11A111: std::sys::thread_local::native::lazy::Storage<T,D>::initialize (lazy.rs:64)
==866870==    by 0x120E05: get_or_init<core::cell::Cell<core::option::Option<std::sync::mpmc::context::Context>>, (), fn() -> core::cell::Cell<core::option::Option<std::sync::mpmc::context::Context>>> (lazy.rs:56)
==866870==    by 0x120E05: {closure#0} (mod.rs:94)
==866870==    by 0x120E05: call_once<std::sync::mpmc::context::{impl#0}::with::CONTEXT::{constant#0}::{closure_env#0}, (core::option::Option<&mut core::option::Option<core::cell::Cell<core::option::Option<std::sync::mpmc::context::Context>>>>)> (function.rs:250)
==866870==    by 0x120E05: try_with<core::cell::Cell<core::option::Option<std::sync::mpmc::context::Context>>, std::sync::mpmc::context::{impl#0}::with::{closure_env#1}<std::sync::mpmc::list::{impl#3}::recv::{closure_env#1}<test::event::CompletedTest>, ()>, ()> (local.rs:309)
==866870==    by 0x120E05: with<std::sync::mpmc::list::{impl#3}::recv::{closure_env#1}<test::event::CompletedTest>, ()> (context.rs:52)
==866870==    by 0x120E05: std::sync::mpmc::list::Channel<T>::recv (list.rs:437)
==866870==    by 0x13949F: recv_deadline<test::event::CompletedTest> (mod.rs:1119)
==866870==    by 0x13949F: recv_timeout<test::event::CompletedTest> (mod.rs:1051)
==866870==    by 0x13949F: recv_timeout<test::event::CompletedTest> (mpsc.rs:905)
==866870==    by 0x13949F: run_tests<test::console::run_tests_console::{closure_env#2}> (lib.rs:430)
==866870==    by 0x13949F: test::console::run_tests_console (console.rs:323)
==866870==    by 0x156AD6: test::test_main (lib.rs:150)
==866870==    by 0x15745A: test::test_main_static (lib.rs:172)
==866870==    by 0x11D8D2: foo::main (lib.rs:0)
==866870==    by 0x11D7AA: core::ops::function::FnOnce::call_once (function.rs:250)
==866870==    by 0x11DF1D: std::sys::backtrace::__rust_begin_short_backtrace (backtrace.rs:152)
==866870==
==866870== LEAK SUMMARY:
==866870==    definitely lost: 0 bytes in 0 blocks
==866870==    indirectly lost: 0 bytes in 0 blocks
==866870==      possibly lost: 48 bytes in 1 blocks
==866870==    still reachable: 0 bytes in 0 blocks
==866870==         suppressed: 0 bytes in 0 blocks
==866870==
==866870== For lists of detected and suppressed errors, rerun with: -s
==866870== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

Memleak with latest nightly:

Memleak with latest nightly
   Compiling foo v0.1.0 (/tmp/foo)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.10s
==866946== Memcheck, a memory error detector
==866946== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==866946== Using Valgrind-3.22.0 and LibVEX; rerun with -h for copyright info
==866946== Command: /tmp/foo/target/debug/deps/foo-9e68f113d655f0c4
==866946==

running 1 test
test tests::it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.07s

==866946==
==866946== HEAP SUMMARY:
==866946==     in use at exit: 48 bytes in 1 blocks
==866946==   total heap usage: 644 allocs, 643 frees, 77,544 bytes allocated
==866946==
==866946== 48 bytes in 1 blocks are possibly lost in loss record 1 of 1
==866946==    at 0x4846828: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==866946==    by 0x1A6047: alloc (alloc.rs:93)
==866946==    by 0x1A6047: alloc_impl (alloc.rs:188)
==866946==    by 0x1A6047: allocate (alloc.rs:249)
==866946==    by 0x1A6047: {closure#0}<std::thread::Inner> (sync.rs:505)
==866946==    by 0x1A6047: allocate_for_layout<core::mem::maybe_uninit::MaybeUninit<std::thread::Inner>, alloc::sync::{impl#14}::new_uninit::{closure_env#0}<std::thread::Inner>, fn(*mut u8) -> *mut alloc::sync::ArcInner<core::mem::maybe_uninit::MaybeUninit<std::thread::Inner>>> (sync.rs:1985)
==866946==    by 0x1A6047: new_uninit<std::thread::Inner> (sync.rs:503)
==866946==    by 0x1A6047: std::thread::Thread::new (mod.rs:1429)
==866946==    by 0x1A5609: std::thread::current::init_current (current.rs:227)
==866946==    by 0x1AEA1F: current_or_unnamed (current.rs:184)
==866946==    by 0x1AEA1F: std::sync::mpmc::context::Context::new (context.rs:72)
==866946==    by 0x141E31: __init (context.rs:43)
==866946==    by 0x141E31: call_once<fn() -> core::cell::Cell<core::option::Option<std::sync::mpmc::context::Context>>, ()> (function.rs:250)
==866946==    by 0x141E31: unwrap_or_else<core::cell::Cell<core::option::Option<std::sync::mpmc::context::Context>>, fn() -> core::cell::Cell<core::option::Option<std::sync::mpmc::context::Context>>> (option.rs:1048)
==866946==    by 0x141E31: std::sys::thread_local::native::lazy::Storage<T,D>::initialize (lazy.rs:64)
==866946==    by 0x14348B: get_or_init<core::cell::Cell<core::option::Option<std::sync::mpmc::context::Context>>, (), fn() -> core::cell::Cell<core::option::Option<std::sync::mpmc::context::Context>>> (lazy.rs:56)
==866946==    by 0x14348B: {closure#0} (mod.rs:94)
==866946==    by 0x14348B: call_once<std::sync::mpmc::context::{impl#0}::with::CONTEXT::{constant#0}::{closure_env#0}, (core::option::Option<&mut core::option::Option<core::cell::Cell<core::option::Option<std::sync::mpmc::context::Context>>>>)> (function.rs:250)
==866946==    by 0x14348B: try_with<core::cell::Cell<core::option::Option<std::sync::mpmc::context::Context>>, std::sync::mpmc::context::{impl#0}::with::{closure_env#1}<std::sync::mpmc::list::{impl#3}::recv::{closure_env#1}<test::event::CompletedTest>, ()>, ()> (local.rs:314)
==866946==    by 0x14348B: with<std::sync::mpmc::list::{impl#3}::recv::{closure_env#1}<test::event::CompletedTest>, ()> (context.rs:52)
==866946==    by 0x14348B: std::sync::mpmc::list::Channel<T>::recv (list.rs:442)
==866946==    by 0x15CDF5: recv_deadline<test::event::CompletedTest> (mod.rs:1119)
==866946==    by 0x15CDF5: recv_timeout<test::event::CompletedTest> (mod.rs:1051)
==866946==    by 0x15CDF5: recv_timeout<test::event::CompletedTest> (mpsc.rs:905)
==866946==    by 0x15CDF5: run_tests<test::console::run_tests_console::{closure_env#2}> (lib.rs:441)
==866946==    by 0x15CDF5: test::console::run_tests_console (console.rs:323)
==866946==    by 0x179CA0: test_main_with_exit_callback<test::test_main::{closure_env#0}> (lib.rs:160)
==866946==    by 0x179CA0: test::test_main (lib.rs:101)
==866946==    by 0x17A4FA: test::test_main_static (lib.rs:183)
==866946==    by 0x13F862: foo::main (lib.rs:0)
==866946==    by 0x13F98A: core::ops::function::FnOnce::call_once (function.rs:250)
==866946==    by 0x13F80D: std::sys::backtrace::__rust_begin_short_backtrace (backtrace.rs:152)
==866946==
==866946== LEAK SUMMARY:
==866946==    definitely lost: 0 bytes in 0 blocks
==866946==    indirectly lost: 0 bytes in 0 blocks
==866946==      possibly lost: 48 bytes in 1 blocks
==866946==    still reachable: 0 bytes in 0 blocks
==866946==         suppressed: 0 bytes in 0 blocks
==866946==
==866946== For lists of detected and suppressed errors, rerun with: -s
==866946== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

Meta

rustc +1.86.0 --version --verbosee:

rustc 1.86.0 (05f9846f8 2025-03-31)
binary: rustc
commit-hash: 05f9846f893b09a1be1fc8560e33fc3c815cfecb
commit-date: 2025-03-31
host: x86_64-unknown-linux-gnu
release: 1.86.0
LLVM version: 19.1.7

rustc +nightly --version --verbosee:

rustc 1.89.0-nightly (d97326eab 2025-05-15)
binary: rustc
commit-hash: d97326eabfc3b2c33abcb08d6bc117aefa697cb7
commit-date: 2025-05-15
host: x86_64-unknown-linux-gnu
release: 1.89.0-nightly
LLVM version: 20.1.4
@nablaa nablaa added the C-bug Category: This is a bug. label May 16, 2025
@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label May 16, 2025
@Noratrieb
Copy link
Member

leak-check=full is very overly eager about marking allocations as leaked that are actually fine, and we don't really support it (see #133574).
that said, you could try using cargo-bisect-rustc to figure out what caused it, which could help inform us whether it's just a false positive or a genuine problem

@Noratrieb Noratrieb added T-libs Relevant to the library team, which will review and decide on the PR/issue. C-discussion Category: Discussion or questions that doesn't represent real issues. A-valgrind-full-leak Area: memory leaks detected by valgrind leak-check=full and removed needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. C-bug Category: This is a bug. labels Jun 1, 2025
@nablaa
Copy link
Author

nablaa commented Jun 1, 2025

leak-check=full is very overly eager about marking allocations as leaked that are actually fine, and we don't really support it (see #133574). that said, you could try using cargo-bisect-rustc to figure out what caused it, which could help inform us whether it's just a false positive or a genuine problem

Thank you for the tip! cargo-bisect-rustc seems like a really handy tool!

I ran the bisect and got the following report (full log attached bisect.log):

$ cat test.sh
#!/bin/bash
set -euo pipefail
valgrind --error-exitcode=42 --tool=memcheck --leak-check=full $(cargo test --no-run --message-format=json | jq  -r 'select(.reason=="compiler-artifact") | .executable | select(. != null)')

$ cargo bisect-rustc --start=2025-01-01 --end=2025-05-01 --script ./test.sh

Regression in d0047d3. Note that if it is a legacy rollup build, it might be available in rust-lang-ci@d0047d3.
The PR introducing the regression in this rollup is #132654: std: lazily allocate the main thread handle

= Please file this regression report on the rust-lang/rust GitHub repository =
= New issue: https://github.com/rust-lang/rust/issues/new =
= Known issues: https://github.com/rust-lang/rust/issues =
= Copy and paste the text below into the issue report thread. Thanks! =

searched nightlies: from nightly-2025-01-01 to nightly-2025-05-01
regressed nightly: nightly-2025-01-16
searched commit range: 8361aef...419b3e2
regressed commit: 419b3e2

bisected with cargo-bisect-rustc v0.6.10

Host triple: x86_64-unknown-linux-gnu
Reproduce with:

cargo bisect-rustc --start=2025-01-01 --end=2025-05-01 --script ./test.sh
</details>

@Noratrieb
Copy link
Member

I see, in that case this is definitely a duplicate of #135608. Thank you for the report, but I am going to close this. I recommend not using leak-check=full since that can have false positives like this.

@nablaa
Copy link
Author

nablaa commented Jun 1, 2025

Thank you for the quick reply!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-valgrind-full-leak Area: memory leaks detected by valgrind leak-check=full C-discussion Category: Discussion or questions that doesn't represent real issues. T-libs Relevant to the library team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

3 participants