Search code examples
c++c++14gtkmm

sporadic segfaults when changing label of gtkmm widget


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)


Solution

  • 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;
    }