Skip to content

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

Closed as duplicate

Description

@nablaa

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

Activity

  1. added
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on May 16, 2025
  2. Noratrieb commented on Jun 1, 2025

    @Noratrieb
    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

  3. added
    T-libsRelevant to the library team, which will review and decide on the PR/issue.
    C-discussionCategory: Discussion or questions that doesn't represent real issues.
    A-valgrind-full-leakArea: memory leaks detected by valgrind leak-check=full
    and removed
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    C-bugCategory: This is a bug.
    on Jun 1, 2025
  4. nablaa commented on Jun 1, 2025

    @nablaa
    Author

    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>
  5. Noratrieb commented on Jun 1, 2025

    @Noratrieb
    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.

  6. nablaa commented on Jun 1, 2025

    @nablaa
    Author

    Thank you for the quick reply!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-valgrind-full-leakArea: memory leaks detected by valgrind leak-check=fullC-discussionCategory: Discussion or questions that doesn't represent real issues.T-libsRelevant to the library team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions