-1

Hy, i have a gtkmm application, which does some async network-requests, to ask the server for additional properties of the gtk-widgets. This means for example, that the application should be able to change the label of a widget.

In this example I have created a new widget based on Gtk::ToggleButton.

But I found out that sometimes the gtkmm-application crashes with a segfault. When debuging with gdb I always get the line where i set the label.

For better understanding, I have created a MWE which does the label-changes in a loop, to simulate lots of async-calls:

#include <boost/asio.hpp>
#include <boost/asio/steady_timer.hpp>

#include <iostream>
#include <thread>
#include <mutex>
#include <gtkmm/application.h>
#include <gtkmm/window.h>
#include <gtkmm/togglebutton.h>

class led_label_t : public Gtk::ToggleButton {
public:
    using value_list_t = std::vector<Glib::ustring>;
    using lock_t = std::lock_guard<std::mutex>;

    led_label_t(Glib::ustring label = "<no data>", bool mnemonic = false)
        : Gtk::ToggleButton(std::move(label), std::move(mnemonic)),
          _values{"SEL1", "SEL2"} {}

protected:
    virtual void on_toggled(void) override {
        std::cout << "Clicked Button." << std::endl;
        lock_t lock(_mtx);
        value_changed(_values[get_active()]);
    }

    virtual void value_changed(Glib::ustring& value) {
        std::string path;
        if (get_active()) {
            path =
                "/usr/share/icons/Adwaita/16x16/emblems/emblem-important.png";
        } else {
            path = "/usr/share/icons/Adwaita/16x16/emblems/emblem-default.png";
        }
        remove();  // remove previous label
        std::cout << "Changed Label of led_label: "
                  << ", value: " << value << std::endl;
        add_pixlabel(path, value);
    }

private:
    mutable std::mutex _mtx;
    value_list_t _values;
};

int main(void) {
    auto app = Gtk::Application::create();
    Gtk::Window window;
    window.set_default_size(200, 200);

    led_label_t inst{};
    inst.show();
    window.add(inst);

    auto f = [&inst, &window]() {
        using namespace std::chrono_literals;
        boost::asio::io_service io;
        {   //wait for startup
            boost::asio::steady_timer t{io, 100ms};
            t.wait();
        }

        bool toggle = true;
        for (auto i = 0; i < 2000; i++) {
            std::cout << "i=" << i << std::endl;
            //wait until next simulated button click
            boost::asio::steady_timer t{io, 1ms};
            t.wait();
            inst.set_active(toggle);
            toggle = !toggle;
        }
    };

    std::thread c1(f);
    std::thread w([&app, &window]() { app->run(window); });
    c1.join();
    window.hide();
    w.join();
    return EXIT_SUCCESS;
}

To compile this example, I use following command:

g++ main.cpp -o main `pkg-config --cflags --libs gtkmm-3.0` -Wall -pedantic -Wextra -Werror -Wcast-qual -Wcast-align -Wconversion -fdiagnostics-color=auto -g -O0 -std=c++14 -lboost_system -pthread

I am using GCC 4.9.2 and libgtkmm-3.14 (both standard debian jessie)

The segfault I get is the following:

Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7fffe7fff700 (LWP 7888)]
0x00007ffff6288743 in ?? () from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
(gdb) bt
#0  0x00007ffff6288743 in ?? () from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#1  0x00007ffff6288838 in ?? () from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#2  0x00007ffff6267ce9 in ?? () from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#3  0x00007ffff627241b in ?? () from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#4  0x00007ffff63a1601 in ?? () from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#5  0x00007ffff63a154c in ?? () from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#6  0x00007ffff63a26b8 in ?? () from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#7  0x00007ffff644d5ff in ?? () from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#8  0x00007ffff644d9b7 in gtk_widget_realize ()
   from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#9  0x00007ffff644dbe8 in gtk_widget_map ()
   from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#10 0x00007ffff621c387 in ?? () from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#11 0x00007ffff626270f in ?? () from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#12 0x00007ffff46bf474 in ?? ()
   from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
#13 0x00007ffff46d9087 in g_signal_emit_valist ()
   from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
#14 0x00007ffff46d99df in g_signal_emit ()
   from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
#15 0x00007ffff644db99 in gtk_widget_map ()
   from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#16 0x00007ffff64506d8 in gtk_widget_set_parent ()
   from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#17 0x00007ffff6217a9b in ?? () from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#18 0x00007ffff79a44eb in Gtk::Container_Class::add_callback(_GtkContainer*, _GtkWidget*) () from /usr/lib/x86_64-linux-gnu/libgtkmm-3.0.so.1
#19 0x00007ffff46c253b in g_cclosure_marshal_VOID__OBJECTv ()
   from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
#20 0x00007ffff46bf474 in ?? ()
   from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
#21 0x00007ffff46d9087 in g_signal_emit_valist ()
   from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
#22 0x00007ffff46d99df in g_signal_emit ()
   from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
#23 0x00007ffff6261aa5 in gtk_container_add ()
   from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#24 0x000000000040b0b5 in led_label_t::value_changed (this=0x7fffffffe2a0, 
    value=...) at main.cpp:38
#25 0x000000000040afb1 in led_label_t::on_toggled (this=0x7fffffffe2a0)
    at main.cpp:24
#26 0x00007ffff7a18af0 in Gtk::ToggleButton_Class::toggled_callback(_GtkToggleButton*) () from /usr/lib/x86_64-linux-gnu/libgtkmm-3.0.so.1
#27 0x00007ffff46bf245 in g_closure_invoke ()
   from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
#28 0x00007ffff46d083b in ?? ()
   from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
#29 0x00007ffff46d9778 in g_signal_emit_valist ()
   from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
#30 0x00007ffff46d99df in g_signal_emit ()
   from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
#31 0x00007ffff63ecb4d in ?? () from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#32 0x00007ffff798a4a0 in Gtk::Button_Class::clicked_callback(_GtkButton*) ()
   from /usr/lib/x86_64-linux-gnu/libgtkmm-3.0.so.1
#33 0x00007ffff46bf474 in ?? ()
   from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
#34 0x00007ffff46d9087 in g_signal_emit_valist ()
   from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
#35 0x00007ffff46d99df in g_signal_emit ()
   from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
#36 0x00007ffff63ec936 in gtk_toggle_button_set_active ()
   from /usr/lib/x86_64-linux-gnu/libgtk-3.so.0
#37 0x0000000000405e12 in <lambda()>::operator()(void) const (
    __closure=0x74f4f8) at main.cpp:73
#38 0x000000000040811a in std::_Bind_simple<main()::<lambda()>()>::_M_invoke<>(std::_Index_tuple<>) (this=0x74f4f8) at /usr/include/c++/4.9/functional:1700
#39 0x0000000000407fa9 in std::_Bind_simple<main()::<lambda()>()>::operator()(void) (this=0x74f4f8) at /usr/include/c++/4.9/functional:1688
#40 0x0000000000407e9e in std::thread::_Impl<std::_Bind_simple<main()::<lambda()>()> >::_M_run(void) (this=0x74f4e0) at /usr/include/c++/4.9/thread:115
#41 0x00007ffff3f47970 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#42 0x00007ffff37650a4 in start_thread (arg=0x7fffe7fff700)
    at pthread_create.c:309
#43 0x00007ffff349a04d in clone ()
    at ../sysdeps/unix/sysv/linux/x86_64/clone.S:111

Maybe the Interesting line of this is

#24: 0x000000000040b0b5 in led_label_t::value_changed (this=0x7fffffffe2a0, 
    value=...) at main.cpp:38)

this is the line where add_pixlabel(path, value); is called.

What am I doing wrong here?

Attention: This segfault doesn't come always, I found out, that on my desktop-machine I get the error once every 10 calls. (Intel i7-3xxx) And on my laptop I get the error nearly every call (Intel i5-3xxx)

byteunit
  • 991
  • 4
  • 15

2 Answers2

2

Now I have found a solution, based on the answer of @user4581301. He was right, that gtkmm doesn't support multithreading. (To be more precise, libsigc++ and sigc::trackable are not thread-safe)

However, care is required when writing programs based on gtkmm using multiple threads of execution, arising from the fact that libsigc++, and in particular sigc::trackable, are not thread-safe.

Quote from gtkmm documentation.

Therefore I have used Glib::Dispatcher, to execute the set_label() - method in the context of the gtkmm-Main-Loop of the window.

Here is the code, that did not segfault anymore on my machine(s) (even with many retries)

#include <boost/asio.hpp>
#include <boost/asio/steady_timer.hpp>

#include <cassert>
#include <iostream>
#include <thread>
#include <mutex>
#include <gtkmm/application.h>
#include <gtkmm/window.h>
#include <gtkmm/togglebutton.h>
#include <glibmm/dispatcher.h>

#define LOG()                                                              \
    std::cout << (std::chrono::system_clock::now() - start).count() << " " \
              << std::this_thread::get_id() << ": "

auto start = std::chrono::system_clock::now();

class led_label_t : public Gtk::ToggleButton {
public:
    using value_list_t = std::vector<Glib::ustring>;
    using lock_t = std::lock_guard<std::mutex>;
    using action_queue_t = std::vector<Glib::ustring>;

    led_label_t(Glib::ustring label = "<no data>", bool mnemonic = false)
        : Gtk::ToggleButton(std::move(label), std::move(mnemonic)),
          _values{"SEL1", "SEL2"} {}

    void set_dispatcher(Glib::Dispatcher* dp) {
        _dp = dp;
        _dp->connect([this](void) { dispatcher_task(); });
    }

protected:
    virtual void on_toggled(void) override {
        LOG() << "Clicked Button." << std::endl;
        {
            lock_t lock(_action_mtx);
            auto value = _values[get_active()];
            _action_queue.push_back({value});
            LOG() << "Added label into queue " << value << std::endl;
            if (_action_queue.size() > 1) {
                return;
            }
        }
        _dp->emit();
    }

    void dispatcher_task(void) {
        Glib::ustring label;
        for (;;) {
            {
                lock_t lock(_action_mtx);
                if (_action_queue.size() == 0) {
                    return;
                }
                label = *_action_queue.begin();
                _action_queue.erase(_action_queue.begin());
            }
            set_label(label);
            LOG() << "Set the label " << label << std::endl;
        }
    }

private:
    mutable std::mutex _action_mtx;
    action_queue_t _action_queue;

    value_list_t _values;
    Glib::Dispatcher* _dp;
};

int main(void) {
    auto app = Gtk::Application::create();
    Gtk::Window window;
    window.set_default_size(200, 200);

    led_label_t inst{};
    inst.show();
    window.add(inst);

    auto f = [&inst, &window]() {
        using namespace std::chrono_literals;
        boost::asio::io_service io;
        {  // wait for startup
            boost::asio::steady_timer t{io, 100ms};
            t.wait();
        }

        bool toggle = true;
        for (auto i = 0; i < 200000; i++) {
            // wait until next simulated button click
            boost::asio::steady_timer t{io, 250us};
            t.wait();
            LOG() << "i=" << i << std::endl;
            inst.set_active(toggle);
            toggle = !toggle;
            LOG() << "finished" << std::endl;
        }
    };

    std::thread c1(f);
    std::thread w([&app, &window, &inst]() {
        Glib::Dispatcher dp;
        inst.set_dispatcher(&dp);
        app->run(window);
    });
    c1.join();
    window.hide();
    w.join();
    return EXIT_SUCCESS;
}
byteunit
  • 991
  • 4
  • 15
1

Accessing and changing UI components from multiple threads is always tricky. UIs need to be fast and responsive to user input, so they can't hang around for background tasks to complete. As a result UI components are rarely protected by mutex or other synchronization. You write, it happens. Except when something else gets in the way.

If you write from two threads... Ooops.

You're half way through a write when another thread reads... Ooops.

Say for example Thread 4 is part way through writing a new string into the label when a screen refresh is triggered. If the backend for label is a c-style string, the terminating NULL may have been overwritten and the label write runs off the end into bad RAM.

All sorts of things could go wrong, and some will be survivable or, worse, look like it. You're better off having all of the UI management in one thread and have the other threads queue updates to the UI thread. Start by looking into Model View Controller and then try related patterns if needed.

user4581301
  • 33,082
  • 7
  • 33
  • 54
  • Ok, this means, that I have to syncronize the threads, but how? If I would implement a Queue, when do I know, that current label-update was finished and I can send the next label update? Or is there a possibility, to use the GTK-Thread directly? If I call the Gtk::Application thread with run(), the call is blocking and so I don't get the control of the thread again. – byteunit Sep 15 '15 at 07:27
  • Do a quick google aboot for multithreaded gtk. A quick looks says gdk_threads_enter()` and `gdk_threads_leave()` is the recommended mutex-y solution, but that seems pretty coarse-grained to me. Bets there are better optimized solutions out there. – user4581301 Sep 15 '15 at 15:32