Skip to content
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

FactoryVecDeque will eventually crash due to memory leaks #619

Closed
sumibi-yakitori opened this issue Mar 14, 2024 · 7 comments
Closed

FactoryVecDeque will eventually crash due to memory leaks #619

sumibi-yakitori opened this issue Mar 14, 2024 · 7 comments

Comments

@sumibi-yakitori
Copy link

sumibi-yakitori commented Mar 14, 2024

Hello. Thanks for the great library.

I am building a viewer that handles a large number of images(<10000) and after rewriting the contents of FactoryVecDeque several times, the performance of the application slows down significantly.

While investigating this, I found a memory leak which I am not sure if it is related to the performance drop, but I have confirmed that it will eventually crash.
(I am a GTK4 contributor on macOS and believe that additional research is still needed to determine if Relm4 is related to the performance degradation)

When I created code with similar behavior using only gtk4, the program did not have any memory leaks.

This also occurs in relm4 version 0.6

Relm4:
Screenshot 2024-03-14 at 19 53 15

GTK4 Only:
Screenshot 2024-03-14 at 19 55 32

@sumibi-yakitori
Copy link
Author

sumibi-yakitori commented Mar 14, 2024

Relm4:
use gtk::prelude::*;
use item::Item;
use relm4::{
  factory::{FactoryComponent, FactoryVecDeque, FactoryView},
  gtk,
  prelude::*,
  FactorySender,
};
use std::time::Duration;

fn main() {
  gtk::init().unwrap();
  let app = relm4::main_application();

  RelmApp::from_app(app).run::<List>(());
}

pub struct List {
  items: FactoryVecDeque<Item>,
}

#[derive(Debug)]
pub enum ListInput {
  Update,
}

impl SimpleComponent for List {
  type Init = ();
  type Input = ListInput;
  type Output = ();
  type Root = gtk::ApplicationWindow;
  type Widgets = ();

  fn init_root() -> Self::Root {
    gtk::ApplicationWindow::builder().build()
  }

  fn init(
    _init: Self::Init,
    root: Self::Root,
    sender: ComponentSender<Self>,
  ) -> ComponentParts<Self> {
    let list = gtk::Box::builder().build();
    root.set_child(Some(&{
      let container = gtk::ScrolledWindow::builder().build();
      container.set_child(Some(&list));
      container
    }));
    let items = FactoryVecDeque::builder().launch(list).detach();
    let model = Self { items };
    std::thread::spawn({
      let sender = sender.clone();
      move || loop {
        sender.input(ListInput::Update);
        std::thread::sleep(Duration::from_secs(1));
      }
    });
    ComponentParts { widgets: (), model }
  }

  fn update(&mut self, message: Self::Input, _sender: ComponentSender<Self>) {
    match message {
      ListInput::Update => {
        let mut guard = self.items.guard();
        guard.clear();
        for _ in 0..10000 {
          guard.push_back(Item {});
        }
      }
    }
  }
}

mod item {
  use super::*;

  #[derive(Clone)]
  pub struct Item;

  type RootWidget = gtk::Box;

  #[derive(Debug)]
  pub struct FactoryWidgets {}

  impl FactoryComponent for Item {
    type Init = Self;
    type Input = ();
    type Output = ();
    type CommandOutput = ();
    type ParentWidget = gtk::Box;
    type Root = RootWidget;
    type Widgets = FactoryWidgets;
    type Index = DynamicIndex;

    fn init_root(&self) -> Self::Root {
      RootWidget::builder()
        .hexpand(false)
        .vexpand(true)
        .halign(gtk::Align::Start)
        .build()
    }

    fn init_widgets(
      &mut self,
      _index: &DynamicIndex,
      _root_widget: Self::Root,
      _returned_widget: &<Self::ParentWidget as FactoryView>::ReturnedWidget,
      _sender: FactorySender<Self>,
    ) -> Self::Widgets {
      Self::Widgets {}
    }

    fn init_model(value: Self::Init, _index: &DynamicIndex, _sender: FactorySender<Self>) -> Self {
      value
    }
  }
}
GTK4:
use gtk::{prelude::*, Application, ApplicationWindow};
use relm4::{prelude::gtk, RelmRemoveAllExt};
use std::time::Duration;

fn main() {
  let application = Application::builder()
    .application_id("com.example.list")
    .build();

  application.connect_activate(|app| {
    let window = ApplicationWindow::builder()
      .application(app)
      .default_width(400)
      .default_height(400)
      .build();

    window.set_child(Some(&{
      let container = gtk::ScrolledWindow::builder().build();
      container.set_child(Some(&{
        let list = gtk::Box::builder().build();

        gtk::glib::timeout_add_local(Duration::from_secs(1), {
          let list = gtk::glib::clone::Downgrade::downgrade(&list);
          move || {
            let Some(list) = list.upgrade()
            else {
              return false.into();
            };

            list.remove_all();
            for _ in 0..10000 {
              list.append(&{ gtk::Box::builder().build() });
            }

            true.into()
          }
        });

        list
      }));
      container
    }));

    window.show();
  });

  application.run();
}

@AaronErhardt
Copy link
Member

Thanks for the report. This is actually not quite unexpected because FactoryVecDeque is using a little bit of unsafe to make it possible to implement the Index trait. Maybe something in the logic is wrong so that the destructor isn't run which would free the allocation.

Since we just published version 0.7 and 0.8, would you mind checking whether the problem exists there as well?

@sumibi-yakitori
Copy link
Author

sumibi-yakitori commented Mar 14, 2024

@AaronErhardt
Thanks for the quick reply. This is the result of running on Relm 0.8.

I first found this issue with Relm 0.6 and was going to report the issue, but I just learned that Relm 0.7/0.8 has just been released (Congratulations!🎉)

So I upgraded my crate in hopes that this issue had been resolved, but alas, it was not.

@AaronErhardt
Copy link
Member

The leak seems to be caused by gtk-rs-core and is simply triggered very often by Relm4s factories. Let's see what the gtk-rs devs say about this (you can follow the referenced issue).

@sumibi-yakitori
Copy link
Author

sumibi-yakitori commented Mar 15, 2024

Thank you!

@AaronErhardt
Copy link
Member

The memory leak should be fixed in gtk-rs in their latest versions. Can you confirm whether it works now?

@sumibi-yakitori
Copy link
Author

sumibi-yakitori commented May 19, 2024

Oh, I have confirmed that it has been fixed. Thanks a lot!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants