18

I'm building a fairly simple C application using GTK, but have to perform some blocking IO which will trigger updates to the GUI. In order to do this, I start a new pthread right before gtk_main() as such:

/* global variables */
GMainContext *mainc;

/* local variables */
FILE *fifo;
pthread_t reader;

/* main() */
mainc = g_main_context_default();
pthread_create(&reader, NULL, watch_fifo, argv[argc-1]);
gtk_main();

When the pthread reads some data, it updates the GUI like so:

g_main_context_invoke(mainc, set_icon, param);

Where set_icon is

gboolean set_icon(gpointer data)
{
    char *p = (char*)data;
    gtk_status_icon_set_from_icon_name(icon, p);
    return FALSE;
}

This all works most of the time, but every now and again I get this curious error message:

[xcb] Unknown sequence number while processing queue
[xcb] Most likely this is a multi-threaded client and XInitThreads has not been called
[xcb] Aborting, sorry about that.
mktrayicon: xcb_io.c:274: poll_for_event: Assertion `!xcb_xlib_threads_sequence_lost' failed.

I thought the whole point of using g_main_context_invoke was to avoid issues with threads? Doing a bit of Googling, I came across gdk_threads_init, gdk_threads_enter and friends, but they all seem to be deprecated? I know the GTK documentation says that all GUI updaes should be performed on the main thread, but this does not combine all that well with blocking IO, and I'd prefer not to have to construct some complex communication mechanism between the threads.

And so, my question is, how should I correctly deal with this?

EDIT: The full code can be seen here EDIT2: As an update based on @ptomato's answer, I've moved to GThreads and using gdk_threads_add_idle() as seen in this commit, but the problem is still present.

Jon Gjengset
  • 4,078
  • 3
  • 30
  • 43
  • Do you have some code availiable, I've been using gtk+ for quite some time and _never_ stumbled upon this issue.. – drahnr Sep 10 '13 at 17:43
  • The entire code is available at [GitHub](https://github.com/jonhoo/mktrayicon) as linked to in the post. – Jon Gjengset Sep 10 '13 at 18:09
  • Ah, missed that thanks! – drahnr Sep 10 '13 at 18:28
  • Note that you'll want to use [this](https://github.com/Jonhoo/mktrayicon/blob/fd9051b6cf374a189ee43af1248aba678c637127/mktrayicon.c) link to see the original code, and [this](https://github.com/Jonhoo/mktrayicon/blob/a438e2d39d43966cde38c9b64bba327bb0715bce/mktrayicon.c) link to see the code after implementing the suggestions by @ptomato. [This](https://github.com/Jonhoo/mktrayicon/blob/77a869fd4612c0cdffdee2f19ae5db3dda77adb6/mktrayicon.c) link will point you to the fixed version of the file. – Jon Gjengset Sep 10 '13 at 18:35

4 Answers4

19

Call XInitThreads(). This should be done before gtk_init, that will stop the messages!

Something like this:

    #include <X11/Xlib.h>
    ...  
    XInitThreads();
    ...
    gtk_init(&argc, &argv);

I don't remember seeing these messages before GLIB 2.32, when g_thread_init()/gdk_threads_init() were used.

You might want to check out g_thread_pool_new and g_thread_pool_push. From thread, use g_main_context_invoke to execute in main loop or just wrap thread between gdk_threads_enter()/gdk_threads_leave()

I do not use a tray so I can not easily check this. I think you are correct about gdk_threads_add_idle using locks to protect GTK/GDK API. There is nothing obvious to me that would cause these messages to appear. The function description for gtk_status_icon_new_from_icon_name states that "If the current icon theme is changed, the icon will be updated appropriately. Which to me, implies your code is not the only code that will access the X display, which could potentially be the problem.

There is also some related info regarding XInitThreads() at

What is the downside of XInitThreads()?

Note that while GDK uses locks for the display, GTK/GDK do not ever call XInitThreads.

On a side note: What's protecting the global variable "onclick", which is passed to execl after a fork(), The child will not inherit the parent's memory locks, and GLib mainloop is incompatible with fork(). Maybe you could copy the string to local variable.

Community
  • 1
  • 1
Wiley
  • 1,156
  • 7
  • 4
  • `gdk_threads_enter` and `gdk_threads_leave` have been deprecated, so using them is not really an option. Considering I only have one additional thread, using a thread pool seems a bit overkill, but thanks for the tip! Seems strange to have to call `XInitThreads` manually considering the manual says "The GLib threading system is automatically initialized at the start of your program", but I'll give it a go later today and report back. – Jon Gjengset Sep 09 '13 at 10:07
  • 1
    Also, isn't it a bit strange to have to call `XInitThreads` when the documentation for `gdk_threads_add_idle_full` says that it "calls `function` with the GDK lock held", and the `XInitThreads` docs say "If all calls to Xlib functions are protected by some other access mechanism (for example, a mutual exclusion lock in a toolkit or through explicit client programming), Xlib thread initialization is not required"? – Jon Gjengset Sep 09 '13 at 10:13
  • [This](https://github.com/Jonhoo/mktrayicon/commit/77a869fd4612c0cdffdee2f19ae5db3dda77adb6) fixed the problem, thank you! Would still like some more information on **why** this is happening for the bounty. – Jon Gjengset Sep 09 '13 at 17:16
  • After reading the link in your answer on `XInitThreads`, I'm not liking having to call it even though it works. It seems as though using `gdk_threads_add_idle`, which already acquires the GTK lock, should be sufficient... In terms of your side note, this shouldn't be a problem as the memory space is copied by `fork` and then "wiped" by `execl`. The parent's `onclick` var is freed when it is changed further down. If you're thinking about X locks, the newly `fork`ed process will be entirely independent, and shouldn't have to worry about GDK/GTK starting it at all. – Jon Gjengset Sep 11 '13 at 00:19
  • 1
    I think this may be a bug in GTK or GDK. If you can reduce it to a minimal example (admittedly, hard to do with threading bugs) it definitely bears reporting at bugzilla.gnome.org. – ptomato Sep 11 '13 at 06:42
  • Don't forget to add `pkg-config --cflags --libs x11` – est.tenorio Feb 05 '20 at 18:35
1

I'm not sure if bare pthreads are guaranteed to work with GTK. You should use the GThread wrappers.

I think what the problem may be is that g_main_context_invoke() is adding set_icon() as an idle function. (It seems that that is what goes on behind the scenes, but I'm not sure.) Idle functions added using GLib's API, despite being executed on the main thread, need to hold the GDK lock. If you use the gdk_threads_add_idle() API (which is not deprecated) to invoke set_icon(), then everything should work properly with threading.

(Although this is just a wild guess.)

ptomato
  • 56,175
  • 13
  • 112
  • 165
  • I've changed to GThreads and `gdk_threads_add_idle` as seen [in this commit](https://github.com/Jonhoo/mktrayicon/commit/e62ede1cf0e863e0e9173d15f46a01bd6536fa11), but I am still seeing the error (or minor variations of it) occasionally... – Jon Gjengset Sep 06 '13 at 09:56
0

As a work around, if you just want to avoid blocking the UI while waiting for some IO you could use the asynchronous IO from GIO. That would avoid you having to manage threads yourself.

Edit: Thinking about it you could just mark your file descriptors as non-blocking and add them as a source to the glib main loop and it will poll them for you in the main event loop without having to mess about with threads.

Phillip Wood
  • 831
  • 4
  • 5
  • Polling it doesn't seem like a particularly clean solution though. I'd rather block on it as that would mean the program will react pretty much immediately to commands. GIO seems interesting though. Could you give an example of how I might use it for this? – Jon Gjengset Sep 09 '13 at 19:35
  • Polling shouldn't be any slower than a blocking read, it's what GTK uses to react to mouse and keyboard events. To do this create a `GIOChannel` with `g_io_channel_new_file` and add it to the main loop with `g_io_add_watch`. For GIO create a `GFile` with `g_file_new_for_commandline_arg` or `g_file_new_for_path` then call `g_file_read_async` with a callback that calls `g_file_read_finish` followed by `g_input_stream_read_async` with a callback that calls `g_input_stream_read_finish` and then processes the data read and then calls `g_input_stream_read_async` again. – Phillip Wood Sep 10 '13 at 13:15
0

You could avoid using threads by using gio_add_watch() which will invoke your callback function when there is data available on the channel.

barry day
  • 26
  • 1
  • Not entirely trivial with FIFO files, because the way I'm using them, they will need to be reopened (which blocks) every time EOF is encountered as writers come and go. `g_io_add_watch` does not seem to support this? Correct me if I'm wrong. – Jon Gjengset Sep 10 '13 at 11:44