You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
GTK/X11 crashes from GTK calls inside @async_function worker threads #409
Hypnotix 4.3 has GTK thread-safety violations that can crash on X11.
Environment:
Linux Mint Cinnamon X11
Hypnotix 4.3
Python 3.10.12
GTK 3.24.33
Symptoms:
Channel selection can crash with:
Gdk-ERROR: received X Window System error
BadRequest (invalid request code or no such operation)
request_code 0 minor_code 0
Launching from desktop icon can abort with SIGABRT/SIGTRAP.
coredumps show crashes in libgdk/libX11/cairo while Python worker threads are active.
Root causes found:
LiveMainWindow.play_async is decorated with @async_function but calls GTK/GDK-sensitive code:
self.info_menu_item.set_sensitive(False)
self.before_play(channel)
self.reinit_mpv()
self.mpv_drawing_area.get_window().get_xid()
LiveMainWindow.reload is decorated with @async_function; in the Xtream provider path it directly calls GTK/GDK window cursor APIs from the worker thread:
self.window.get_window().get_cursor()
self.window.get_window().set_cursor(...)
Gdk.Cursor.new_from_name(...)
GTK must only be touched from the GTK main loop.
Proposed fixes:
Keep GTK setup and mpv window/XID creation on the main thread.
Move only blocking/network/wait operations to worker threads.
Marshal cursor changes through GLib.idle_add / the existing @idle_function.
Patch direction:
Remove @async_function from play_async.
Move only mpv.wait_until_playing() into a worker helper.
Add idle-thread helpers for Xtream wait cursor set/restore.
The violations this issue lists are still unmarshalled in master
before_play and after_play are @idle_function, but these are not:
L885 self.info_menu_item.set_sensitive(False) — called directly from the @async_function worker in play_async (L877).
L887 self.reinit_mpv() — also from that worker. reinit_mpv (L1612) then calls Gtk.events_pending() (L1630) and self.mpv_drawing_area.get_window().get_xid()
(L1646) off the main thread. Gtk.events_pending() inspects main-loop state from
a thread that does not own it.
L1549/L1551 self.window.get_window().get_cursor() / set_cursor(Gdk.Cursor.new_from_name(...)) — from the @async_functionreload
(L1500).
What the crash looks like on Wayland: heap corruption, not a BadRequest
SIGSEGV, logged by the kernel as a general protection fault (a non-canonical
pointer, not a null dereference):
The faulting instruction is the inlined GTK_LABEL() cast in gtk_label_set_label: mov (%rbx),%rax ; cmp %rsi,(%rax), with %rbx = priv->title_label. Its class pointer read back as 0x69f1699a694368ec, which is
not an address at all.
Inspecting the core: the GtkHeaderBar itself is intact and live — priv->title = the provider name, priv->subtitle = "Movies > <category>" — but all three of
its internal widgets (title_label, subtitle_label, label_box) have been freed while priv still points at them. A search of the whole 56 MB heap finds
exactly one surviving pointer to the dead title label: the header bar's own private
struct. The freed label's memory had already been reissued to the image loader —
it now holds a 16-bit monotonic gamma curve (fitted exponent ≈ 2.4), i.e. an lcms2
tone-response table from glycin decoding channel artwork.
So the widget was destroyed out from under a live parent, and the crash surfaced on
the next headerbar.set_title() from navigate_to.
What was happening at the time
Browsing an Xtream provider, Movies → a category, with 188 channel-logo JPEGs
downloading concurrently (download_channel_logos worker threads) while navigating
between pages and starting playback. 29 threads live at the moment of the crash:
the GTK main thread, ~5 glycin image threads, several mpv threads, several requests/SSL download threads.
Caveat, stated plainly
The core proves the corruption and localises it precisely. It does not prove which
pair of racing calls caused it — I could not pin the freeing thread from a single
dump. I am reporting it here rather than as a new issue because the mechanism
(GObject state mutated from @async_function workers) is the one this issue
already identifies, and because it shows the failure is not limited to X11.
Authored by Claude Opus 5 via Claude Code, from a systemd-coredump core dump.
Activity
jebjarrell1974-debug commentedon Sep 15, 2026
Still present in master (0e0fa1c), and not X11-specific — here is a Wayland
reproduction with a core dump that shows the damage at the memory level.
Environment
hypnotix 5.6 · GTK 3.24.52 · Python 3.14.7 · PyGObject 3.56.3 · glib 2.88.3
Arch Linux, Wayland (Hyprland), no X11 session — mpv runs through XWayland.
The violations this issue lists are still unmarshalled in master
before_playandafter_playare@idle_function, but these are not:self.info_menu_item.set_sensitive(False)— called directly from the@async_functionworker inplay_async(L877).self.reinit_mpv()— also from that worker.reinit_mpv(L1612) then callsGtk.events_pending()(L1630) andself.mpv_drawing_area.get_window().get_xid()(L1646) off the main thread.
Gtk.events_pending()inspects main-loop state froma thread that does not own it.
self.window.get_window().get_cursor()/set_cursor(Gdk.Cursor.new_from_name(...))— from the@async_functionreload(L1500).
What the crash looks like on Wayland: heap corruption, not a BadRequest
SIGSEGV, logged by the kernel as a general protection fault (a non-canonical
pointer, not a null dereference):
The faulting instruction is the inlined
GTK_LABEL()cast ingtk_label_set_label:mov (%rbx),%rax ; cmp %rsi,(%rax), with%rbx=priv->title_label. Its class pointer read back as0x69f1699a694368ec, which isnot an address at all.
Inspecting the core: the
GtkHeaderBaritself is intact and live —priv->title= the provider name,priv->subtitle="Movies > <category>"— but all three ofits internal widgets (
title_label,subtitle_label,label_box) have beenfreed while priv still points at them. A search of the whole 56 MB heap finds
exactly one surviving pointer to the dead title label: the header bar's own private
struct. The freed label's memory had already been reissued to the image loader —
it now holds a 16-bit monotonic gamma curve (fitted exponent ≈ 2.4), i.e. an lcms2
tone-response table from glycin decoding channel artwork.
So the widget was destroyed out from under a live parent, and the crash surfaced on
the next
headerbar.set_title()fromnavigate_to.What was happening at the time
Browsing an Xtream provider, Movies → a category, with 188 channel-logo JPEGs
downloading concurrently (
download_channel_logosworker threads) while navigatingbetween pages and starting playback. 29 threads live at the moment of the crash:
the GTK main thread, ~5 glycin image threads, several mpv threads, several
requests/SSL download threads.Caveat, stated plainly
The core proves the corruption and localises it precisely. It does not prove which
pair of racing calls caused it — I could not pin the freeing thread from a single
dump. I am reporting it here rather than as a new issue because the mechanism
(GObject state mutated from
@async_functionworkers) is the one this issuealready identifies, and because it shows the failure is not limited to X11.
Authored by Claude Opus 5 via Claude Code, from a systemd-coredump core dump.