Problem with multithreading in gtk-rs

I am writing an app using gtk-rs and I have encountered a problem when trying to call glib::idle_add from another thread.

When the callback passed to glib::idle_add contains a GObject, the compilation fails.

This is a simplified example:

extern crate glib;
extern crate gtk;

use std::thread;
use glib::prelude::*;
use gtk::prelude::*;

fn perform_some_long_computation() {}

fn main() {
    if gtk::init().is_err() {
        println!("Failed to initialize GTK.");
        return;
    }

    let window = gtk::Window::new(gtk::WindowType::Toplevel);
    let button = gtk::Button::new_with_label("Click me!");

    let window_clone = window.clone();

    button.connect_clicked(move |_| {
        thread::spawn(move || {
            perform_some_long_computation();
            glib::idle_add(move || {
                window_clone.set_title("Finished");  // cannot use any GObject inside this callback
                glib::Continue(false)
            });
        });
    });

    window.add(&button);

    window.show_all();
    gtk::main();
}

These are the compilation errors:

error[E0277]: the trait bound `*mut glib::object::GObject: std::marker::Send` is not satisfied in `[closure@src/main.rs:24:28: 27:14 window_clone:gtk::Window]`
  --> src/main.rs:24:13
   |
24 |             glib::idle_add(move || {
   |             ^^^^^^^^^^^^^^ within `[closure@src/main.rs:24:28: 27:14 window_clone:gtk::Window]`, the trait `std::marker::Send` is not implemented for `*mut glib::object::GObject`
   |
   = note: `*mut glib::object::GObject` cannot be sent between threads safely
   = note: required because it appears within the type `glib::shared::Shared<glib::object::GObject, glib::object::MemoryManager>`
   = note: required because it appears within the type `glib::object::ObjectRef`
   = note: required because it appears within the type `gtk::Window`
   = note: required because it appears within the type `[closure@src/main.rs:24:28: 27:14 window_clone:gtk::Window]`
   = note: required by `glib::idle_add`

error[E0277]: the trait bound `*mut gtk_sys::GtkWindowPrivate: std::marker::Send` is not satisfied in `[closure@src/main.rs:24:28: 27:14 window_clone:gtk::Window]`
  --> src/main.rs:24:13
   |
24 |             glib::idle_add(move || {
   |             ^^^^^^^^^^^^^^ within `[closure@src/main.rs:24:28: 27:14 window_clone:gtk::Window]`, the trait `std::marker::Send` is not implemented for `*mut gtk_sys::GtkWindowPrivate`
   |
   = note: `*mut gtk_sys::GtkWindowPrivate` cannot be sent between threads safely
   = note: required because it appears within the type `gtk_sys::GtkWindow`
   = note: required because it appears within the type `std::marker::PhantomData<gtk_sys::GtkWindow>`
   = note: required because it appears within the type `gtk::Window`
   = note: required because it appears within the type `[closure@src/main.rs:24:28: 27:14 window_clone:gtk::Window]`
   = note: required by `glib::idle_add`

error[E0277]: the trait bound `*const glib::object::MemoryManager: std::marker::Send` is not satisfied in `[closure@src/main.rs:24:28: 27:14 window_clone:gtk::Window]`
  --> src/main.rs:24:13
   |
24 |             glib::idle_add(move || {
   |             ^^^^^^^^^^^^^^ within `[closure@src/main.rs:24:28: 27:14 window_clone:gtk::Window]`, the trait `std::marker::Send` is not implemented for `*const glib::object::MemoryManager`
   |
   = note: `*const glib::object::MemoryManager` cannot be sent between threads safely
   = note: required because it appears within the type `std::marker::PhantomData<*const glib::object::MemoryManager>`
   = note: required because it appears within the type `glib::shared::Shared<glib::object::GObject, glib::object::MemoryManager>`
   = note: required because it appears within the type `glib::object::ObjectRef`
   = note: required because it appears within the type `gtk::Window`
   = note: required because it appears within the type `[closure@src/main.rs:24:28: 27:14 window_clone:gtk::Window]`
   = note: required by `glib::idle_add`

error[E0277]: the trait bound `*mut gtk_sys::GtkBinPrivate: std::marker::Send` is not satisfied in `[closure@src/main.rs:24:28: 27:14 window_clone:gtk::Window]`
  --> src/main.rs:24:13
   |
24 |             glib::idle_add(move || {
   |             ^^^^^^^^^^^^^^ within `[closure@src/main.rs:24:28: 27:14 window_clone:gtk::Window]`, the trait `std::marker::Send` is not implemented for `*mut gtk_sys::GtkBinPrivate`
   |
   = note: `*mut gtk_sys::GtkBinPrivate` cannot be sent between threads safely
   = note: required because it appears within the type `gtk_sys::GtkBin`
   = note: required because it appears within the type `gtk_sys::GtkWindow`
   = note: required because it appears within the type `std::marker::PhantomData<gtk_sys::GtkWindow>`
   = note: required because it appears within the type `gtk::Window`
   = note: required because it appears within the type `[closure@src/main.rs:24:28: 27:14 window_clone:gtk::Window]`
   = note: required by `glib::idle_add`

error[E0277]: the trait bound `*mut gtk_sys::GtkContainerPrivate: std::marker::Send` is not satisfied in `[closure@src/main.rs:24:28: 27:14 window_clone:gtk::Window]`
  --> src/main.rs:24:13
   |
24 |             glib::idle_add(move || {
   |             ^^^^^^^^^^^^^^ within `[closure@src/main.rs:24:28: 27:14 window_clone:gtk::Window]`, the trait `std::marker::Send` is not implemented for `*mut gtk_sys::GtkContainerPrivate`
   |
   = note: `*mut gtk_sys::GtkContainerPrivate` cannot be sent between threads safely
   = note: required because it appears within the type `gtk_sys::GtkContainer`
   = note: required because it appears within the type `gtk_sys::GtkBin`
   = note: required because it appears within the type `gtk_sys::GtkWindow`
   = note: required because it appears within the type `std::marker::PhantomData<gtk_sys::GtkWindow>`
   = note: required because it appears within the type `gtk::Window`
   = note: required because it appears within the type `[closure@src/main.rs:24:28: 27:14 window_clone:gtk::Window]`
   = note: required by `glib::idle_add`

error[E0277]: the trait bound `*mut gtk_sys::GtkWidgetPrivate: std::marker::Send` is not satisfied in `[closure@src/main.rs:24:28: 27:14 window_clone:gtk::Window]`
  --> src/main.rs:24:13
   |
24 |             glib::idle_add(move || {
   |             ^^^^^^^^^^^^^^ within `[closure@src/main.rs:24:28: 27:14 window_clone:gtk::Window]`, the trait `std::marker::Send` is not implemented for `*mut gtk_sys::GtkWidgetPrivate`
   |
   = note: `*mut gtk_sys::GtkWidgetPrivate` cannot be sent between threads safely
   = note: required because it appears within the type `gtk_sys::GtkWidget`
   = note: required because it appears within the type `gtk_sys::GtkContainer`
   = note: required because it appears within the type `gtk_sys::GtkBin`
   = note: required because it appears within the type `gtk_sys::GtkWindow`
   = note: required because it appears within the type `std::marker::PhantomData<gtk_sys::GtkWindow>`
   = note: required because it appears within the type `gtk::Window`
   = note: required because it appears within the type `[closure@src/main.rs:24:28: 27:14 window_clone:gtk::Window]`
   = note: required by `glib::idle_add`

error[E0277]: the trait bound `*mut glib_sys::GData: std::marker::Send` is not satisfied in `[closure@src/main.rs:24:28: 27:14 window_clone:gtk::Window]`
  --> src/main.rs:24:13
   |
24 |             glib::idle_add(move || {
   |             ^^^^^^^^^^^^^^ within `[closure@src/main.rs:24:28: 27:14 window_clone:gtk::Window]`, the trait `std::marker::Send` is not implemented for `*mut glib_sys::GData`
   |
   = note: `*mut glib_sys::GData` cannot be sent between threads safely
   = note: required because it appears within the type `gobject_sys::GInitiallyUnowned`
   = note: required because it appears within the type `gtk_sys::GtkWidget`
   = note: required because it appears within the type `gtk_sys::GtkContainer`
   = note: required because it appears within the type `gtk_sys::GtkBin`
   = note: required because it appears within the type `gtk_sys::GtkWindow`
   = note: required because it appears within the type `std::marker::PhantomData<gtk_sys::GtkWindow>`
   = note: required because it appears within the type `gtk::Window`
   = note: required because it appears within the type `[closure@src/main.rs:24:28: 27:14 window_clone:gtk::Window]`
   = note: required by `glib::idle_add`

error[E0277]: the trait bound `*mut gobject_sys::GTypeClass: std::marker::Send` is not satisfied in `[closure@src/main.rs:24:28: 27:14 window_clone:gtk::Window]`
  --> src/main.rs:24:13
   |
24 |             glib::idle_add(move || {
   |             ^^^^^^^^^^^^^^ within `[closure@src/main.rs:24:28: 27:14 window_clone:gtk::Window]`, the trait `std::marker::Send` is not implemented for `*mut gobject_sys::GTypeClass`
   |
   = note: `*mut gobject_sys::GTypeClass` cannot be sent between threads safely
   = note: required because it appears within the type `gobject_sys::GTypeInstance`
   = note: required because it appears within the type `gobject_sys::GInitiallyUnowned`
   = note: required because it appears within the type `gtk_sys::GtkWidget`
   = note: required because it appears within the type `gtk_sys::GtkContainer`
   = note: required because it appears within the type `gtk_sys::GtkBin`
   = note: required because it appears within the type `gtk_sys::GtkWindow`
   = note: required because it appears within the type `std::marker::PhantomData<gtk_sys::GtkWindow>`
   = note: required because it appears within the type `gtk::Window`
   = note: required because it appears within the type `[closure@src/main.rs:24:28: 27:14 window_clone:gtk::Window]`
   = note: required by `glib::idle_add`

error: aborting due to 8 previous errors

Thank you for any advice!

2 Likes

GTK is not thread-safe:
http://gtk-rs.org/docs/gtk/#threads

1 Like

I know that GTK is not thread-safe, but the callback inside the call to glib::idle_add is scheduled to run on the main thread, so I think it is safe to call glib::idle_add from any thread?

2 Likes

The problem here, which you can see stated in the errors, is that the objects you're trying to send across the threads aren't safe to do so. No GTKObject is safe to send between threads. Basically all GUI stuff has to happen in a single thread.

What you should be doing instead is sending an enum from the child thread to the main thread indicating when the computation is done, and then matching on that enum and doing the window.set_title() call there. You you need to define an API between your main GUI thread and any spawned children thread instead of trying to send GtkObjects across the thread boundaries.

I have a pretty fleshed-out example in my gattii crate if you'd like to see a fully-featured GTK app with that kind of configuration. I have a background thread that works with the serialport and it needs to trigger changes in the GUI, but those changes only occur in the main thread but are triggered by responses from the child thread.

2 Likes

You use a thread-local storage key - that's quite a nice hack!

But, when using dynamically created and destroyed GObjects (e.g. multiple dialog windows), TLS probably cannot be used as it uses only static global variables.

For now the only universal solution that I can think of is polling using gtk::timeout_add (from main thread).

1 Like

Using TLS isn't a hack. I use it because I want a global variable essentially, but those are bad. TLS at least prevents variables from leaking across thread boundaries.

Instead of polling you can do what I did in my program. I pass a callback to my child thread and then have the child queue up that callback when it's sent a message. This way polling isn't necessary. See src/bin/gattii.rs:521.

2 Likes

I just hit this issue. Luckily for me now gtk-rust has mpsc. I also learned about fragile as a bonus.

https://coaxion.net/blog/2019/02/mpsc-channel-api-for-painless-usage-of-threads-with-gtk-in-rust/

1 Like