gtk-rs / gtk-rs-core

Rust bindings for GNOME libraries
https://gtk-rs.org/gtk-rs-core
MIT License
272 stars 105 forks source link

[BUG] Memory leak when aborting tasks #1326

Closed AaronErhardt closed 4 months ago

AaronErhardt commented 4 months ago

The bug first appeared in https://github.com/Relm4/Relm4/issues/619, but seems to be located in the glib crate rather than Relm4.

Bug description

Spawning and then aborting an uncompleted task leaks memory (presumably the future of the task).

use std::time::Duration;
use glib::timeout_future;

fn main() {
    let c = glib::MainContext::default();
    let l = glib::MainLoop::new(Some(&c), false);

    let l_clone = l.clone();
    c.spawn_local(async move {
        for _ in 0..1000 {
            // Spawn a task
            let handle = glib::MainContext::ref_thread_default().spawn_local(async {
                let test: u128 = std::future::pending().await;
                println!("{test}");
            });

            // Abort task => LEAK!
            handle.abort();
        }

        // Wait 1s and then quit
        timeout_future(Duration::from_secs(1)).await;
        l_clone.quit();
    });

    l.run();
}

Backtrace

I'm using valgrind --suppressions=/usr/share/glib-2.0/valgrind/glib.supp --suppressions=/usr/share/gtk-4.0/valgrind/gtk.supp --leak-check=full BINARY_NAME to detect the leaks.

❯ valgrind   --suppressions=/usr/share/glib-2.0/valgrind/glib.supp   --suppressions=/usr/share/gtk-4.0/valgrind/gtk.supp --leak-check=full ../target/debug/memory_leak 
==71348== Memcheck, a memory error detector
==71348== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==71348== Using Valgrind-3.22.0 and LibVEX; rerun with -h for copyright info
==71348== Command: ../target/debug/memory_leak
==71348== 
==71348== 
==71348== HEAP SUMMARY:
==71348==     in use at exit: 142,440 bytes in 1,271 blocks
==71348==   total heap usage: 7,683 allocs, 6,412 frees, 519,309 bytes allocated
==71348== 
==71348== 104,000 bytes in 1,000 blocks are definitely lost in loss record 251 of 251
==71348==    at 0x484280F: malloc (vg_replace_malloc.c:442)
==71348==    by 0x116BDF: alloc (alloc.rs:98)
==71348==    by 0x116BDF: alloc::alloc::Global::alloc_impl (alloc.rs:181)
==71348==    by 0x116956: allocate (alloc.rs:241)
==71348==    by 0x116956: alloc::alloc::exchange_malloc (alloc.rs:330)
==71348==    by 0x11EE91: new<alloc::sync::ArcInner<futures_channel::oneshot::Inner<core::result::Result<alloc::boxed::Box<dyn core::any::Any, alloc::alloc::Global>, alloc::boxed::Box<(dyn core::any::Any + core::marker::Send), alloc::alloc::Global>>>>> (boxed.rs:217)
==71348==    by 0x11EE91: alloc::sync::Arc<T>::new (sync.rs:389)
==71348==    by 0x119417: futures_channel::oneshot::channel (oneshot.rs:105)
==71348==    by 0x115366: glib::main_context_futures::<impl glib::auto::main_context::MainContext>::spawn_local_with_priority (main_context_futures.rs:566)
==71348==    by 0x11508A: glib::main_context_futures::<impl glib::auto::main_context::MainContext>::spawn_local (main_context_futures.rs:526)
==71348==    by 0x113230: memory_leak::main::{{closure}} (main.rs:13)
==71348==    by 0x113CE5: glib::main_context_futures::<impl glib::auto::main_context::MainContext>::spawn_local_with_priority::{{closure}} (main_context_futures.rs:564)
==71348==    by 0x11636F: <futures_task::future_obj::LocalFutureObj<T> as core::future::future::Future>::poll (future_obj.rs:84)
==71348==    by 0x119E0C: <glib::main_context_futures::FutureWrapper as core::future::future::Future>::poll (main_context_futures.rs:38)
==71348==    by 0x11CAB9: glib::main_context_futures::TaskSource::poll::{{closure}}::{{closure}} (main_context_futures.rs:245)
==71348== 
==71348== LEAK SUMMARY:
==71348==    definitely lost: 104,000 bytes in 1,000 blocks
==71348==    indirectly lost: 0 bytes in 0 blocks
==71348==      possibly lost: 0 bytes in 0 blocks
==71348==    still reachable: 19,176 bytes in 41 blocks
==71348==         suppressed: 17,248 bytes in 209 blocks
==71348== Reachable blocks (those to which a pointer was found) are not shown.
==71348== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==71348== 
==71348== For lists of detected and suppressed errors, rerun with: -s
==71348== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
sdroege commented 4 months ago

Doesn't look like the future that is leaked but the oneshot channel to get the result of the future into the join handle. Shouldn't leak of course :)

sdroege commented 4 months ago

Should also add a test for this when fixing it.

sdroege commented 4 months ago

See https://github.com/gtk-rs/gtk-rs-core/pull/1328