GNOME Bugzilla – Bug 524128
Gtk::TextView widget doesn't like being updated from other threads
Last modified: 2008-03-25 15:57:08 UTC
As the title said, if I create a separate thread and update a TextBuffer, which has been associated with a TextView. The program crashes sooner rather than later. The TextView widget often got messed up when that happened. It seems the stack or internal state of Gtk(mm) was corrupted. The behavior is random: Some times it crashes right away, sometimes a few seconds later. Error messages spitted out by Gtk+ also suggested this: I got entirely unrelated messages in different runs. Here are two out of many: ==================== (whiteboard:11964): Gtk-WARNING **: Invalid text buffer iterator: either the iterator is uninitialized, or the characters/pixbufs/widgets in the buffer have been modified since the iterator was created. You must use marks, character numbers, or line numbers to preserve a position across buffer modifications. You can apply tags and insert marks without invalidating your iterators, but any mutation that affects 'indexable' buffer contents (contents that can be referred to by character offset) will invalidate all outstanding iterators ===================== ===================== ** ** Pango:ERROR:(/build/buildd/pango1.0-1.20.0/pango/pango-layout.c:5910):pango_layout_iter_next_line: assertion failed: (iter->line_extents_link != NULL) ===================== I am the developer of Linux Whiteboard. The behavior was observed when I ran my program. But I tested it on several test apps and they exposed the same behavior. ********************* Steps to reproduce: - Build a simple GUI using Glade with a TextView and a Button. - Associate a TextBuffer with the TextView and update the TextBuffer using Gtk::TextBuffer::insert_at_cursor() . - Write code to spawn a thread when the Button is clicked, the thread function just updates the TextBuffer once per second. - Load and run the GUI in the app and watch it crash. User may have to select the text inside or otherwise move the mouse around the TextView.
I can provide a minimal test case when needed. It is quite easy to reproduce though.
It is true that gtk widgets don't like to be modified from other threads. That is because GTK+ is not threadsafe. One option is to use a Glib::Dispatcher for communication between different threads (see http://www.gtkmm.org/docs/glibmm-2.4/docs/reference/html/classGlib_1_1Dispatcher.html). Alternately, use the gdk thread-related functions to protect *all* gtk function calls made from other threads: http://library.gnome.org/devel/gdk/stable/gdk-Threads.html If you already are doing one of these things and it still has problems, feel free to re-open this bug and somebody can help investigate the issue further.
Thank you for the reply, I tried g_thread_* functions since that seems to easily integrate with existing applications. The program barely crashes anymore (although perhaps I was just lucky), but the TextView still got messed up when being updated from other threads, despite the fact that I've surrounded Gtk::Main within a enter() and leave() pair like this: ========= gdk_threads_enter(); m_gtk_kit.run(); gdk_threads_leave(); ========= g_thread_init() and gdk_threads_init() were called before those above. It also didn't work when I surrounded all Gtk::TextBuffer::insert_at_cursor() calls with enter()/leave() pairs. I've got lots of other threads other than this one (although only one of them could be running at a time), and some timeout callbacks. Do I need to surround the pair with every single one of them or do it like the illustrated example of gdk-Threads is enough?.
Actually I can make it crash by scrolling the text view up and down a bit harder :s . The errors were still random.
according to the documentation, you need to protect every use of gtk-related functions from a timeout or idle callback: "Idles, timeouts, and input functions from GLib, such as g_idle_add(), are executed outside of the main GTK+ lock. So, if you need to call GTK+ inside of such a callback, you must surround the callback with a gdk_threads_enter()/gdk_threads_leave() pair or use gdk_threads_add_idle_full() which does this for you. However, event dispatching from the mainloop is still executed within the main GTK+ lock, so callback functions connected to event signals like GtkWidget::button-press-event, do not need thread protection. In particular, this means, if you are writing widgets that might be used in threaded programs, you must surround timeouts and idle functions in this matter. As always, you must also surround any calls to GTK+ not made within a signal handler with a gdk_threads_enter()/gdk_threads_leave() pair." So basically, you'll need to protect a lot of calls with enter/leave pairs. This is why it's generally not recommended to use gtk+ from multiple threads, it becomes a pain. Usually it's recommended to have one thread to *all* of the gtk-related stuff and have the others just send messages to the UI thread (with e.g. Glib::Dispatcher) when it needs to do ui-related things. But I actually don't have a lot of experience doing threaded UI programming, I'm just passing on things I've heard regularly from others.
I was ready to use Glib::Dispatcher but noticed something odd: My TextView is wrapped inside a ScrolledWindow and whenever a new message is printed, the ScrolledWindow scrolls to the bottom using this code: =================== Gtk::Adjustment *const vadj = m_gtk_output_scroll->get_vadjustment(); vadj->set_value( vadj->get_upper() ); =================== Commenting out this code and the crashy behavior went away. So the problem is probably not one of my threads (there is only one thread running at any given time, aside from the main thread, and it worked properly when I replaced the TextView with a Label). - Is the above code to scroll the view to the bottom correct?. - Glib::Dispatcher doesn't allow passing data between threads, is there a documented way to do it (I've been searching around a lot but the only question about this one went unanswered)?.
You should not call GTK+ functions from multiple threads. I suggest that you ask on gtkmm-list for advice. The "Is gtkmm thread-safe" question here might also be useful: http://www.gtkmm.org/docs/gtkmm-2.4/docs/FAQ/html/index.html#id2552475 > So the problem is probably not one of my threads Then please provide a simple test case that does not use threads, in a new bug report, please.
I wasn't able to reproduce the behavior of ScrolledWindow without a thread, you are right. Using Glib::Dispatcher hasn't resulted in any crash so far, but the ScrolledWindow still didn't behave properly (I could visually see it being over-scrolled to the bottom, so nothing was displayed, until I tried to select the text inside did the text appear properly. Whiteboard uses pthread for all of its works, figured I had to use Glib::Thread for it to work. But Glib::Thread only supports sigc::signal<void>. In other words, it makes things a lot more complicated to pass data between thread and the main code in comparison to good 'ol pthread :/ . Searching the mailing list archive with "glib::dispatcher pass data thread" returned a thread from 2004: "The problem is that I'm still not convinced that cross-thread signalling with arguments can be implemented correctly as a generic template." I am not an expert at designing multi-threaded applications, so I'd take his words for granted. The thing is, there has been little documentation on how to use Glib::Dispatcher: The class documentation is some what lacking, and its example doesn't show a way to pass data between threads. Searching for "pass data thread" didn't give any useful info either, isn't it a common problem?. I'm not complaining though. Guess I'll have to leave it aside and move on. Thank you for the answers.
I switched the bulk of pthread and plain sigc::signal code to Glib::Thread and Glib::Dispatcher, the problem went away. Proceeding to move the rest of the code to Glib. The lack of data passing between threads were alleviated by member functions, it is actually more manageable than I thought. Thanks.
glad to hear it. have fun! In the future, feel free to ask questions on gtkmm-list as well.