1

I'm trying to write a basic multithreaded application using gtk3-rs, where the main thread sends a message to a child thread when a button is clicked, and the child thread sends a message back in response, after doing some calculations, the results of which are displayed by the main thread in a dialog box.

This seems simple enough conceptually, but I'm running into a problem where the channels that I'm creating in the callback that is used by gtk::Application::connect_activate to build the user interface are getting closed before the child thread (also created in that callback, and then detached) can even use them once, let alone how I intended, which is continually throughout the life of the application.

These are glib channels on the MainContext, not MSPC channels, so instead of busy-waiting for input like for normal channels, I was able to attach a listener on both receivers. I have one listening in the main thread (attached in the UI builder callback) and one listening in the spawned thread, but apparently that's not enough to keep the channels alive, because when I try to send a message to the thread's channel, it errors out saying that the thread is closed.

So the basic structure of my code is like this:

fn connect_events(/* event box, channel a */) {
    event_box.connect_button_release_event(move |_, _| {
        a.send("foo").unwrap();
    });
}

fn build_ui(app: &gtk::Application) {
    let (a, b) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
    let (c, d) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
    let event_box = /* GTK event box to capture events */;
    connect_events(&event_box, a.clone());
    thread::spawn(move || {
        b.attach(/* handle receiving a message from the main thread by sending a message back on c */);
    });

    d.attach(/* pop up a dialog box with whatever was sent back */);
}

fn main() {
    let application = gtk::Application::new(
        Some("com.example.aaaaaaaa"),
        Default::default(),
    );

    application.connect_activate(build_ui);

    application.run();
}

So, how do I convince Rust to keep the channels alive? I tried doing some lazy_static magic and using .leak(), but neither of those seemed to work, and moving all of this code out of the UI builder is unfortunately not an option.

Alexis Dumas
  • 1,299
  • 11
  • 30
  • Can you show exactly how your code uses `a` and `c`? – user4815162342 Sep 17 '22 at 16:14
  • It's doing `a.send()` inside a callback connected to the button press event on the button, and the `c.send()` is just directly inside the callback given to `b.attach`. I'm not sure what more you want? – Alexis Dumas Sep 17 '22 at 16:25
  • Examining the code that does that might help understand how the sending side of the channel gets dropped. Some subtlety may be involved, and showing (a minimal version of) the code can't hurt. – user4815162342 Sep 17 '22 at 17:36
  • Alright, I'll add a stripped down version to my question, but I'm not sure it'll actually help, since, well, it does basically exactly what I said – Alexis Dumas Sep 17 '22 at 18:09
  • Please add code that actually compiles and reproduces your problem. Don't just do `/* imagine code here */`; actually make it functional. For example, currently `c` isn't used anywhere, and a comment about a pop up does not help us when we are attempting to fix your code, because the comment will not create an actual popup for us to verify that it works now. For more info, read [how to create a minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). – Finomnis Sep 17 '22 at 21:08
  • Okay. I don't think that's really possible in this case, I spent a few hours code golfing in the rust playground trying to reproduce this and couldn't come up with anything, but I'll try to come up with something later. – Alexis Dumas Sep 17 '22 at 21:11

1 Answers1

1

My pragmatic answer is: Don't use glib channels.

I'm using async rust channels for things like this. In your case, a oneshot channel could be useful. But many crates provide async channels, async-std or tokio for example.

You can spawn a function via glib::MainContext::default().spawn_local() that .awaits the message(s) from the channel and show the dialog there.

Sophie
  • 1,374
  • 1
  • 8
  • 12