diff --git a/0001-util-Add-way-to-print-backtraces.patch b/0001-util-Add-way-to-print-backtraces.patch new file mode 100644 index 0000000..89ce285 --- /dev/null +++ b/0001-util-Add-way-to-print-backtraces.patch @@ -0,0 +1,724 @@ +From 574f2cf39e37ac6ecdb6d9b8b38626b4d6502822 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Mon, 25 Sep 2023 22:22:08 +0200 +Subject: [PATCH 1/3] util: Add way to print backtraces + +Now that mutter can use a realtime thread its very important that +it doesn't stall for too long, since that can result in the kernel +killing it. Ironically, a main reason mutter could stall is +kernel bugs. + +When a stall happens, we need a way to see why. This commit adds +a new function, "meta_print_backtrace" to print backtrace of the +current process and the kernel (if possible). + +A future commit will use this new function. +--- + meson.build | 8 ++++ + meson_options.txt | 6 +++ + src/50-mutter.rules | 9 ++++ + src/core/util-private.h | 2 + + src/core/util.c | 57 ++++++++++++++++++++++++ + src/meson.build | 27 +++++++++++ + src/mutter-backtrace | 17 +++++++ + src/org.gnome.mutter.backtrace.policy.in | 17 +++++++ + 8 files changed, 143 insertions(+) + create mode 100644 src/50-mutter.rules + create mode 100755 src/mutter-backtrace + create mode 100644 src/org.gnome.mutter.backtrace.policy.in + +diff --git a/meson.build b/meson.build +index d08401b3c..5482dfb9e 100644 +--- a/meson.build ++++ b/meson.build +@@ -270,60 +270,67 @@ if have_wayland or have_native_backend + libdrm_dep = dependency('libdrm') + endif + + have_egl_device = get_option('egl_device') + + have_wayland_eglstream = get_option('wayland_eglstream') + if have_wayland_eglstream + wayland_eglstream_protocols_dep = dependency('wayland-eglstream-protocols') + dl_dep = cc.find_library('dl', required: true) + + if not have_wayland + error('Wayland EGLStream support requires Wayland to be enabled') + endif + endif + + have_sm = get_option('sm') + if have_sm + sm_dep = dependency('sm') + endif + + have_libwacom = get_option('libwacom') + if have_libwacom + libwacom_dep = dependency('libwacom', version: libwacom_req) + endif + + have_pango_ft2 = get_option('pango_ft2') + if have_pango_ft2 + pangoft2_dep = dependency('pangoft2') + endif + ++have_polkit = get_option('polkit') ++if have_polkit ++ polkit_dep = dependency('polkit-gobject-1') ++ polkit_policy_dir = polkit_dep.get_variable('policydir') ++ polkit_action_dir = polkit_dep.get_variable('actiondir') ++endif ++ + have_startup_notification = get_option('startup_notification') + if have_startup_notification + if have_x11_client + libstartup_notification_dep = dependency('libstartup-notification-1.0', + version: libstartup_notification_req) + else + error('startup_notification requires X11 or Xwayland to be enabled') + endif + endif + + have_remote_desktop = get_option('remote_desktop') + if have_remote_desktop + libpipewire_dep = dependency('libpipewire-0.3', version: libpipewire_req) + endif + + have_introspection = get_option('introspection') + if have_introspection + gobject_introspection_dep = dependency('gobject-introspection-1.0') + + introspection_args = [ + '--quiet', + '-U_GNU_SOURCE', + ] + endif + + have_documentation = get_option('docs') + if have_documentation + gidocgen_dep = dependency('gi-docgen', version: '>= 2021.1', + fallback: ['gi-docgen', 'dummy_dep']) + endif +@@ -671,50 +678,51 @@ if have_documentation + subdir('doc/reference') + endif + + gnome.post_install( + glib_compile_schemas: true, + ) + + meson.add_dist_script('meson/check-version.py', meson.project_version(), 'NEWS') + + summary('prefix', prefix, section: 'Directories') + summary('libexecdir', libexecdir, section: 'Directories') + summary('pkgdatadir', pkgdatadir, section: 'Directories') + + summary('buildtype', get_option('buildtype'), section: 'Build Configuration') + summary('debug', get_option('debug'), section: 'Build Configuration') + + summary('OpenGL', have_gl, section: 'Rendering APIs') + summary('GLES2', have_gles2, section: 'Rendering APIs') + summary('EGL', have_egl, section: 'Rendering APIs') + summary('GLX', have_glx, section: 'Rendering APIs') + + summary('Wayland', have_wayland, section: 'Options') + summary('Wayland EGLStream', have_wayland_eglstream, section: 'Options') + summary('X11', have_x11, section: 'Options') + summary('XWayland', have_xwayland, section: 'Options') + summary('Native Backend', have_native_backend, section: 'Options') + summary('EGL Device', have_egl_device, section: 'Options') + summary('Remote desktop', have_remote_desktop, section: 'Options') + summary('libgnome-desktop', have_gnome_desktop, section: 'Options') + summary('libdisplay-info', have_libdisplay_info, section: 'Options') ++summary('Polkit enhanced backtrace support', have_polkit, section: 'Options') + summary('Sound player', have_sound_player, section: 'Options') + summary('gudev', have_libgudev, section: 'Options') + summary('Wacom', have_libwacom, section: 'Options') + summary('SM', have_sm, section: 'Options') + summary('Startup notification', have_startup_notification, section: 'Options') + summary('Introspection', have_introspection, section: 'Options') + summary('Documentation', have_documentation, section: 'Options') + summary('Profiler', have_profiler, section: 'Options') + summary('Xwayland initfd', have_xwayland_initfd, section: 'Options') + summary('Xwayland listenfd', have_xwayland_listenfd, section: 'Options') + summary('Xwayland terminate delay', have_xwayland_terminate_delay, section: 'Options') + summary('Xwayland byte-swapped clients', have_xwayland_byte_swapped_clients, section: 'Options') + + summary('Enabled', have_tests, section: 'Tests') + summary('Core tests', have_core_tests, section: 'Tests') + summary('Cogl tests', have_cogl_tests, section: 'Tests') + summary('Clutter tests', have_clutter_tests, section: 'Tests') + summary('KVM tests', get_option('kvm_tests'), section: 'Tests') + summary('Installed tests', have_installed_tests, section: 'Tests') + summary('Coverage', get_option('b_coverage'), section: 'Tests') +diff --git a/meson_options.txt b/meson_options.txt +index b5d215b24..b6b18ebdf 100644 +--- a/meson_options.txt ++++ b/meson_options.txt +@@ -84,60 +84,66 @@ option('wayland_eglstream', + option('udev', + type: 'boolean', + value: true, + description: 'Enable udev support when using the X11 backend' + ) + + option('udev_dir', + type: 'string', + value: '', + description: 'Absolute path of the udev base directory' + ) + + option('libwacom', + type: 'boolean', + value: true, + description: 'Enable libwacom support' + ) + + option('sound_player', + type: 'boolean', + value: true, + description: 'Enable sound player support using libcanberra', + ) + + option('pango_ft2', + type: 'boolean', + value: true, + description: 'Enable PangoFt2 support' + ) + ++option('polkit', ++ type: 'boolean', ++ value: true, ++ description: 'Enable Polkit enhanced backtrace support' ++) ++ + option('startup_notification', + type: 'boolean', + value: true, + description: 'Enable startup notification support' + ) + + option('sm', + type: 'boolean', + value: true, + description: 'Enable X11 session management support' + ) + + option('introspection', + type: 'boolean', + value: true, + description: 'Enable GObject introspection' + ) + + option('docs', + type: 'boolean', + value: false, + description: 'Enable gi-docgen documentation' + ) + + option('cogl_tests', + type: 'boolean', + value: true, + description: 'Enable cogl tests' + ) + +diff --git a/src/50-mutter.rules b/src/50-mutter.rules +new file mode 100644 +index 000000000..d14735fe6 +--- /dev/null ++++ b/src/50-mutter.rules +@@ -0,0 +1,9 @@ ++polkit.addRule(function(action, subject) { ++ if (subject.isInGroup("wheel") && ++ subject.active && ++ subject.local && ++ action.id == "org.gnome.mutter.backtrace") { ++ return polkit.Result.YES; ++ } ++}); ++ +diff --git a/src/core/util-private.h b/src/core/util-private.h +index 18ae68de7..32798ca4e 100644 +--- a/src/core/util-private.h ++++ b/src/core/util-private.h +@@ -13,46 +13,48 @@ + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + + #pragma once + + #include + #include + + #include "meta/util.h" + #include "meta/common.h" + + /* META_EXPORT_TEST should be used to export symbols that are exported only + * for testability purposes */ + #define META_EXPORT_TEST META_EXPORT + + void meta_set_verbose (gboolean setting); + void meta_set_debugging (gboolean setting); + + void meta_set_is_wayland_compositor (gboolean setting); + + char * meta_generate_random_id (GRand *rand, + int length); + ++void meta_print_backtrace (void); ++ + void meta_init_debug_utils (void); + + static inline int64_t + meta_timeval_to_microseconds (const struct timeval *tv) + { + return ((int64_t) tv->tv_sec) * G_USEC_PER_SEC + tv->tv_usec; + } + + #define META_POINT_IN_RECT(xcoord, ycoord, rect) \ + ((xcoord) >= (rect).x && \ + (xcoord) < ((rect).x + (rect).width) && \ + (ycoord) >= (rect).y && \ + (ycoord) < ((rect).y + (rect).height)) + + #define META_CONTAINER_OF(ptr, type, member) \ + (type *) ((uint8_t *) (ptr) - G_STRUCT_OFFSET (type, member)) +diff --git a/src/core/util.c b/src/core/util.c +index 05a0dea39..dc547713c 100644 +--- a/src/core/util.c ++++ b/src/core/util.c +@@ -1,107 +1,110 @@ + /* + * Copyright (C) 2001 Havoc Pennington + * Copyright (C) 2005 Elijah Newren + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + + + #define _POSIX_C_SOURCE 200112L /* for fdopen() */ + + #include "config.h" + + #include "core/display-private.h" + #include "core/util-private.h" + ++#include + #include + #include + #include + #include + #include ++#include + + #ifdef HAVE_SYS_PRCTL + #include + #endif + + #include "clutter/clutter-mutter.h" + #include "cogl/cogl.h" + #include "meta/common.h" + #include "meta/main.h" + + static const GDebugKey meta_debug_keys[] = { + { "focus", META_DEBUG_FOCUS }, + { "workarea", META_DEBUG_WORKAREA }, + { "stack", META_DEBUG_STACK }, + { "sm", META_DEBUG_SM }, + { "events", META_DEBUG_EVENTS }, + { "window-state", META_DEBUG_WINDOW_STATE }, + { "window-ops", META_DEBUG_WINDOW_OPS }, + { "geometry", META_DEBUG_GEOMETRY }, + { "placement", META_DEBUG_PLACEMENT }, + { "ping", META_DEBUG_PING }, + { "keybindings", META_DEBUG_KEYBINDINGS }, + { "sync", META_DEBUG_SYNC }, + { "startup", META_DEBUG_STARTUP }, + { "prefs", META_DEBUG_PREFS }, + { "groups", META_DEBUG_GROUPS }, + { "resizing", META_DEBUG_RESIZING }, + { "shapes", META_DEBUG_SHAPES }, + { "edge-resistance", META_DEBUG_EDGE_RESISTANCE }, + { "dbus", META_DEBUG_DBUS }, + { "input", META_DEBUG_INPUT }, + { "wayland", META_DEBUG_WAYLAND }, + { "kms", META_DEBUG_KMS }, + { "screen-cast", META_DEBUG_SCREEN_CAST }, + { "remote-desktop", META_DEBUG_REMOTE_DESKTOP }, + { "backend", META_DEBUG_BACKEND }, + { "render", META_DEBUG_RENDER }, + { "color", META_DEBUG_COLOR }, + { "input-events", META_DEBUG_INPUT_EVENTS }, + { "eis", META_DEBUG_EIS }, + }; + + static gint verbose_topics = 0; + static gboolean is_wayland_compositor = FALSE; + static int debug_paint_flags = 0; + static GLogLevelFlags mutter_log_level = G_LOG_LEVEL_MESSAGE; ++static char *backtrace_command = NULL; + + #ifdef WITH_VERBOSE_MODE + static FILE* logfile = NULL; + + static void + ensure_logfile (void) + { + if (logfile == NULL && g_getenv ("MUTTER_USE_LOGFILE")) + { + char *filename = NULL; + char *tmpl; + int fd; + GError *err; + + tmpl = g_strdup_printf ("mutter-%d-debug-log-XXXXXX", + (int) getpid ()); + + err = NULL; + fd = g_file_open_tmp (tmpl, + &filename, + &err); + + g_free (tmpl); + + if (err != NULL) + { + meta_warning ("Failed to open debug log: %s", + err->message); + g_error_free (err); + return; +@@ -186,60 +189,70 @@ meta_remove_verbose_topic (MetaDebugTopic topic) + verbose_topics = 0; + else + verbose_topics &= ~topic; + } + + void + meta_init_debug_utils (void) + { + const char *debug_env; + + #ifdef HAVE_SYS_PRCTL + prctl (PR_SET_DUMPABLE, 1); + #endif + + if (g_getenv ("MUTTER_VERBOSE")) + meta_set_verbose (TRUE); + + debug_env = g_getenv ("MUTTER_DEBUG"); + if (debug_env) + { + MetaDebugTopic topics; + + topics = g_parse_debug_string (debug_env, + meta_debug_keys, + G_N_ELEMENTS (meta_debug_keys)); + meta_add_verbose_topic (topics); + } + + if (g_test_initialized ()) + mutter_log_level = G_LOG_LEVEL_DEBUG; ++ ++ /* If pkexec works we'll get a kernel backtrace too, ++ * but it may not work if the user isn't in the wheel group. ++ * If it fails, we fall back to running unprivileged to at ++ * least get a backtrace of the process. ++ */ ++ if (backtrace_command == NULL) ++ backtrace_command = g_strdup_printf ("pkexec %1$s %2$d || %1$s %2$d", ++ MUTTER_LIBEXECDIR "/mutter-backtrace", ++ (int) getpid ()); + } + + gboolean + meta_is_wayland_compositor (void) + { + return is_wayland_compositor; + } + + void + meta_set_is_wayland_compositor (gboolean value) + { + is_wayland_compositor = value; + } + + char * + meta_g_utf8_strndup (const gchar *src, + gsize n) + { + const gchar *s = src; + while (n && *s) + { + s = g_utf8_next_char (s); + n--; + } + + return g_strndup (src, s - src); + } + + static int + utf8_fputs (const char *str, +@@ -513,60 +526,104 @@ MetaLocaleDirection + meta_get_locale_direction (void) + { + switch (clutter_get_text_direction ()) + { + case CLUTTER_TEXT_DIRECTION_LTR: + return META_LOCALE_DIRECTION_LTR; + case CLUTTER_TEXT_DIRECTION_RTL: + return META_LOCALE_DIRECTION_RTL; + default: + g_assert_not_reached (); + return 0; + } + } + + char * + meta_generate_random_id (GRand *rand, + int length) + { + char *id; + int i; + + /* Generate a random string of printable ASCII characters. */ + + id = g_new0 (char, length + 1); + for (i = 0; i < length; i++) + id[i] = (char) g_rand_int_range (rand, 32, 127); + + return id; + } + ++static gboolean ++unix_signal_safe_run_command (const char *command) ++{ ++ pid_t pid; ++ int status; ++ int ret; ++ ++ pid = fork (); ++ if (pid == -1) ++ return FALSE; ++ ++ if (pid == 0) ++ { ++ int stdin_fd; ++ ++ const char *argv[] = { ++ "/bin/sh", ++ "-c", ++ command, ++ NULL ++ }; ++ ++ stdin_fd = open ("/dev/null", O_RDWR); ++ dup2 (stdin_fd, STDIN_FILENO); ++ ++ execve (argv[0], (char **) argv, environ); ++ ++ _exit (1); ++ } ++ ++ do ++ { ++ ret = waitpid (pid, &status, 0); ++ } ++ while (ret != 0 && errno == EINTR); ++ ++ return WIFEXITED (status) && WEXITSTATUS (status) == 0; ++} ++ ++void ++meta_print_backtrace (void) ++{ ++ unix_signal_safe_run_command (backtrace_command); ++} + + void + meta_add_clutter_debug_flags (ClutterDebugFlag debug_flags, + ClutterDrawDebugFlag draw_flags, + ClutterPickDebugFlag pick_flags) + { + clutter_add_debug_flags (debug_flags, draw_flags, pick_flags); + } + + void + meta_remove_clutter_debug_flags (ClutterDebugFlag debug_flags, + ClutterDrawDebugFlag draw_flags, + ClutterPickDebugFlag pick_flags) + { + clutter_remove_debug_flags (debug_flags, draw_flags, pick_flags); + } + + /** + * meta_get_clutter_debug_flags: + * @debug_flags: (out) (optional): return location for debug flags + * @draw_flags: (out) (optional): return location for draw debug flags + * @pick_flags: (out) (optional): return location for pick debug flags + */ + void + meta_get_clutter_debug_flags (ClutterDebugFlag *debug_flags, + ClutterDrawDebugFlag *draw_flags, + ClutterPickDebugFlag *pick_flags) + { + clutter_get_debug_flags (debug_flags, draw_flags, pick_flags); + } +diff --git a/src/meson.build b/src/meson.build +index ca2ef166c..4aabb6a14 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -1197,60 +1197,87 @@ libmutter = shared_library(libmutter_name, + ], + install_rpath: pkglibdir, + install_dir: libdir, + install: true, + ) + + libmutter_dep = declare_dependency( + link_with: libmutter, + include_directories: mutter_includes, + sources: mutter_built_headers, + dependencies: [ + libmutter_cogl_dep, + libmutter_clutter_dep, + mutter_deps, + ], + ) + + mutter = executable('mutter', + sources: [ + files('core/mutter.c'), + ], + include_directories: mutter_includes, + c_args: [ + mutter_c_args, + '-DG_LOG_DOMAIN="mutter"', + ], + dependencies: [libmutter_dep], + install_dir: bindir, + install: true, + ) ++ ++install_data( ++ 'mutter-backtrace', ++ install_dir : libexecdir, ++ install_mode : 'rwxr-xr-x' ++) ++ ++if have_polkit ++ backtrace_polkit_policy = configure_file( ++ input : 'org.gnome.mutter.backtrace.policy.in', ++ output : 'org.gnome.mutter.backtrace.policy', ++ configuration : {'LIBEXECDIR': libexecdir} ++ ) ++ ++ install_data( ++ backtrace_polkit_policy, ++ install_dir : polkit_policy_dir, ++ install_mode : 'rw-r--r--' ++ ) ++ ++ install_data( ++ '50-mutter.rules', ++ install_dir : polkit_action_dir, ++ install_mode : 'rw-r--r--' ++ ) ++endif ++ + if have_x11 + executable('mutter-restart-helper', + sources: [ + files('core/restart-helper.c'), + ], + include_directories: [ + top_includepath, + ], + c_args: [ + mutter_c_args, + '-DG_LOG_DOMAIN="mutter-restart-helper"', + ], + dependencies: [ + x11_dep, + xcomposite_dep, + ], + install_dir: libexecdir, + install: true, + ) + endif + + if have_introspection + mutter_introspected_sources = [] + foreach source : mutter_sources + if source.endswith('.c') + mutter_introspected_sources += source + endif + endforeach + + libmutter_gir = gnome.generate_gir(libmutter, +diff --git a/src/mutter-backtrace b/src/mutter-backtrace +new file mode 100755 +index 000000000..20c9d4e75 +--- /dev/null ++++ b/src/mutter-backtrace +@@ -0,0 +1,17 @@ ++#!/bin/bash ++ ++[ $# -ne 1 ] && exit 1 ++ ++gdb_command=(gdb -p "$1" -ex 'thread apply all bt full' -batch) ++ ++if [ -n $PKEXEC_UID -a $(id -u) -eq 0 ]; then ++ cd /proc/$1/task ++ for thread in *; do ++ echo "Thread ${thread}:" ++ cat $thread/stack ++ done ++ pkexec --user $(id -u -n $PKEXEC_UID) "${gdb_command[@]}" ++ exit 0 ++fi ++ ++${gdb_command[@]} +diff --git a/src/org.gnome.mutter.backtrace.policy.in b/src/org.gnome.mutter.backtrace.policy.in +new file mode 100644 +index 000000000..39985acb9 +--- /dev/null ++++ b/src/org.gnome.mutter.backtrace.policy.in +@@ -0,0 +1,17 @@ ++ ++ ++ ++ ++ Authentication is required to run mutter-backtrace ++ dialog-password ++ ++ auth_admin ++ auth_admin ++ auth_admin ++ ++ @LIBEXECDIR@/mutter-backtrace ++ ++ ++ +-- +2.41.0 + diff --git a/0002-clutter-Add-ms2ns-helper.patch b/0002-clutter-Add-ms2ns-helper.patch new file mode 100644 index 0000000..0a79a0f --- /dev/null +++ b/0002-clutter-Add-ms2ns-helper.patch @@ -0,0 +1,89 @@ +From 2c78762dfe3dc05febe6be6408e4018be048159e Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Fri, 29 Sep 2023 10:53:50 -0400 +Subject: [PATCH 2/3] clutter: Add ms2ns helper + +Some system apis we're going to need in the future use +nanoseconds, but we're never going to need more timing +precision that milliseconds. + +In preparate for that, this commit adds a new helper ns2ms +to help avoid bugs in unit conversion. +--- + clutter/clutter/clutter-private.h | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/clutter/clutter/clutter-private.h b/clutter/clutter/clutter-private.h +index 9d5e68c8c..007e15eba 100644 +--- a/clutter/clutter/clutter-private.h ++++ b/clutter/clutter/clutter-private.h +@@ -219,60 +219,66 @@ gboolean _clutter_run_progress_function (GType gtype, + const GValue *final, + gdouble progress, + GValue *retval); + + void clutter_timeline_cancel_delay (ClutterTimeline *timeline); + + static inline void + clutter_round_to_256ths (float *f) + { + *f = roundf ((*f) * 256) / 256; + } + + static inline uint64_t + ns (uint64_t ns) + { + return ns; + } + + static inline int64_t + us (int64_t us) + { + return us; + } + + static inline int64_t + ms (int64_t ms) + { + return ms; + } + ++static inline int64_t ++ms2ns (int64_t ms) ++{ ++ return ns (us (ms * 1000) * 1000); ++} ++ + static inline int64_t + ms2us (int64_t ms) + { + return us (ms * 1000); + } + + static inline int64_t + us2ns (int64_t us) + { + return ns (us * 1000); + } + + static inline int64_t + us2ms (int64_t us) + { + return (int64_t) (us / 1000); + } + + static inline int64_t + ns2us (int64_t ns) + { + return us (ns / 1000); + } + + static inline int64_t + s2us (int64_t s) + { + return s * G_USEC_PER_SEC; + } + +-- +2.41.0 + diff --git a/0003-native-Stop-using-real-time-thread-if-it-stalls.patch b/0003-native-Stop-using-real-time-thread-if-it-stalls.patch new file mode 100644 index 0000000..0757158 --- /dev/null +++ b/0003-native-Stop-using-real-time-thread-if-it-stalls.patch @@ -0,0 +1,1074 @@ +From c49ff787cefa5917e8c6601624db414930b9b1d6 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Mon, 25 Sep 2023 22:29:17 +0200 +Subject: [PATCH 3/3] native: Stop using real-time thread if it stalls + +RTKit requires mutter to set a hard rlimit on how long its thread will +use CPU before going back to the kernel. This limit is about 12 frames. + +Unfortunately, amdgpu seems to have a bug at the moment where it will +cause the thread to stall longer than that when the screen is locked. + +It also seemingly stalls during some games. + +Rather than let the display server get slayed, this commit adds a +roundtrip through the kernel to reset the clock when things are stalled, +and tells mutter to switch away from using real-time threads. + +Closes https://gitlab.gnome.org/GNOME/mutter/-/issues/3037 +--- + src/backends/native/meta-thread-watcher.c | 419 ++++++++++++++++++++++ + src/backends/native/meta-thread-watcher.h | 36 ++ + src/backends/native/meta-thread.c | 65 +++- + src/meson.build | 3 +- + 4 files changed, 520 insertions(+), 3 deletions(-) + create mode 100644 src/backends/native/meta-thread-watcher.c + create mode 100644 src/backends/native/meta-thread-watcher.h + +diff --git a/src/backends/native/meta-thread-watcher.c b/src/backends/native/meta-thread-watcher.c +new file mode 100644 +index 000000000..439966ef2 +--- /dev/null ++++ b/src/backends/native/meta-thread-watcher.c +@@ -0,0 +1,419 @@ ++/* ++ * Copyright (C) 2023 Red Hat ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License as ++ * published by the Free Software Foundation; either version 2 of the ++ * License, or (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, but ++ * WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, see . ++ */ ++ ++#include "config.h" ++ ++#include "backends/native/meta-thread-watcher.h" ++ ++#include "clutter/clutter.h" ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "clutter/clutter-private.h" ++#include "core/util-private.h" ++ ++/* There's a watchdog timer that if left to its own devices will fire after ++ * priv->interval_ms milliseconds. ++ * ++ * There's a GLib main loop timeout that runs just before the watchdog timer fires ++ * to reset the timer to run later. ++ * ++ * If the main loop is ever blocked the main loop timeout won't run and the timer ++ * wont get reset. In this way we can tell if the thread with the main loop is stalled. ++ * ++ * WATCH_INTERVAL_PHASE_OFFSET_MS is how many milliseconds before the watchdog timer ++ * is supposed to fire that the main loop timeout is supposed to reset the timer. ++ * ++ * It just needs to be long enough for timer_settime to be called, but there's ++ * no real disadvantage to making it longer, so long as it's less the thread rlimit. ++ * ++ * It's currently set somewhat arbitrarily at 16ms (approximately one rendererd frame ++ * on most machines) ++ */ ++#define WATCH_INTERVAL_PHASE_OFFSET_MS ms (16) ++ ++enum ++{ ++ THREAD_STALLED, ++ ++ N_SIGNALS ++}; ++ ++static guint signals[N_SIGNALS]; ++ ++struct _MetaThreadWatcher ++{ ++ GObject parent_instance; ++}; ++ ++typedef struct _MetaThreadWatcherPrivate ++{ ++ int fds[2]; ++ int64_t interval_ms; ++ timer_t *timer; ++ guint notification_watch_id; ++ GMainContext *context; ++ GSource *source; ++} MetaThreadWatcherPrivate; ++ ++G_DEFINE_TYPE_WITH_PRIVATE (MetaThreadWatcher, meta_thread_watcher, G_TYPE_OBJECT) ++ ++static void ++meta_thread_watcher_constructed (GObject *object) ++{ ++ MetaThreadWatcher *watcher = META_THREAD_WATCHER (object); ++ MetaThreadWatcherPrivate *priv = meta_thread_watcher_get_instance_private (watcher); ++ ++ priv->timer = NULL; ++ priv->fds[0] = -1; ++ priv->fds[1] = -1; ++ ++ G_OBJECT_CLASS (meta_thread_watcher_parent_class)->constructed (object); ++} ++ ++static void ++close_fds (MetaThreadWatcher *watcher) ++{ ++ MetaThreadWatcherPrivate *priv = meta_thread_watcher_get_instance_private (watcher); ++ ++ g_clear_fd (&priv->fds[0], NULL); ++ g_clear_fd (&priv->fds[1], NULL); ++} ++ ++static void ++notify_watched_thread (int fd) ++{ ++ ssize_t bytes_written = 0; ++ ++ do ++ { ++ bytes_written = write (fd, "", 1); ++ } ++ while (bytes_written < 0 && errno == EINTR); ++} ++ ++static void ++clear_notifications (MetaThreadWatcher *watcher) ++{ ++ MetaThreadWatcherPrivate *priv = meta_thread_watcher_get_instance_private (watcher); ++ ssize_t bytes_read; ++ char buf[64]; ++ ++ do ++ { ++ bytes_read = read (priv->fds[0], buf, sizeof (buf)); ++ } ++ while (bytes_read > 0 || (bytes_read < 0 && errno == EINTR)); ++} ++ ++static void ++yield (void) ++{ ++ struct pollfd sleep_pollfd = { -1, 0, 0 }; ++ poll (&sleep_pollfd, 0, 1); ++} ++ ++static void ++on_xcpu_signal (int signal, ++ siginfo_t *signal_data, ++ void *context) ++{ ++ int fd; ++ static gboolean backtrace_printed = FALSE; ++ ++ /* If we're getting the XCPU signal that means the realtime thread is blocked and ++ * mutter is at risk of being killed by the kernel. We can placate the kernel by ++ * sleeping for a millisecond or so. That should buy us another ~200ms to tear ++ * down the realtime thread and get out of the dangerzone. ++ */ ++ yield (); ++ ++ /* If we're here, there's a bug somewhere, so send backtraces to the journal. ++ */ ++ if (!backtrace_printed) ++ { ++ const char *message = "Hang in realtime thread detected! Backtrace:\n"; ++ write (STDERR_FILENO, message, strlen (message)); ++ meta_print_backtrace (); ++ backtrace_printed = TRUE; ++ } ++ ++ if (signal_data->si_pid != 0 || signal_data->si_code != SI_TIMER) ++ return; ++ ++ fd = signal_data->si_value.sival_int; ++ notify_watched_thread (fd); ++} ++ ++static void ++meta_thread_watcher_finalize (GObject *object) ++{ ++ MetaThreadWatcher *watcher = META_THREAD_WATCHER (object); ++ ++ meta_thread_watcher_stop (watcher); ++ G_OBJECT_CLASS (meta_thread_watcher_parent_class)->finalize (object); ++} ++ ++static void ++meta_thread_watcher_class_init (MetaThreadWatcherClass *klass) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ struct sigaction signal_request; ++ int ret; ++ ++ object_class->constructed = meta_thread_watcher_constructed; ++ object_class->finalize = meta_thread_watcher_finalize; ++ ++ signals[THREAD_STALLED] = ++ g_signal_new ("thread-stalled", ++ G_TYPE_FROM_CLASS (klass), ++ G_SIGNAL_RUN_LAST, ++ 0, ++ NULL, NULL, NULL, ++ G_TYPE_NONE, 0); ++ ++ signal_request.sa_flags = SA_SIGINFO; ++ signal_request.sa_sigaction = on_xcpu_signal; ++ sigemptyset (&signal_request.sa_mask); ++ ret = sigaction (SIGXCPU, &signal_request, NULL); ++ ++ if (ret == -1) ++ g_warning ("Failed to listen for SIGXCPU signal: %m"); ++} ++ ++static void ++meta_thread_watcher_init (MetaThreadWatcher *thread_watcher) ++{ ++} ++ ++MetaThreadWatcher * ++meta_thread_watcher_new (void) ++{ ++ MetaThreadWatcher *watcher; ++ ++ watcher = g_object_new (META_TYPE_THREAD_WATCHER, NULL); ++ ++ return watcher; ++} ++ ++static gboolean ++on_reset_timer (MetaThreadWatcher *watcher) ++{ ++ g_autoptr (GError) error = NULL; ++ gboolean was_reset; ++ ++ if (!meta_thread_watcher_is_started (watcher)) ++ return G_SOURCE_REMOVE; ++ ++ was_reset = meta_thread_watcher_reset (watcher, &error); ++ ++ if (!was_reset) ++ { ++ g_warning ("Failed to reset real-time thread watchdog timer: %s", ++ error->message); ++ return G_SOURCE_REMOVE; ++ } ++ ++ return G_SOURCE_CONTINUE; ++} ++ ++void ++meta_thread_watcher_attach (MetaThreadWatcher *watcher, ++ GMainContext *context) ++{ ++ MetaThreadWatcherPrivate *priv = meta_thread_watcher_get_instance_private (watcher); ++ g_return_if_fail (META_IS_THREAD_WATCHER (watcher)); ++ g_return_if_fail (priv->source == NULL); ++ ++ g_clear_pointer (&priv->context, g_main_context_unref); ++ priv->context = g_main_context_ref (context); ++} ++ ++void ++meta_thread_watcher_detach (MetaThreadWatcher *watcher) ++{ ++ MetaThreadWatcherPrivate *priv = meta_thread_watcher_get_instance_private (watcher); ++ ++ g_return_if_fail (META_IS_THREAD_WATCHER (watcher)); ++ ++ g_clear_pointer (&priv->source, g_source_destroy); ++ g_clear_pointer (&priv->context, g_main_context_unref); ++} ++ ++static gboolean ++on_thread_stalled (int fd, ++ GIOCondition condition, ++ MetaThreadWatcher *watcher) ++{ ++ if (condition & G_IO_IN) ++ clear_notifications (watcher); ++ ++ if (meta_thread_watcher_is_started (watcher)) ++ g_signal_emit (G_OBJECT (watcher), signals[THREAD_STALLED], 0); ++ ++ return G_SOURCE_REMOVE; ++} ++ ++gboolean ++meta_thread_watcher_is_started (MetaThreadWatcher *watcher) ++{ ++ MetaThreadWatcherPrivate *priv = meta_thread_watcher_get_instance_private (watcher); ++ ++ g_return_val_if_fail (META_IS_THREAD_WATCHER (watcher), FALSE); ++ ++ return priv->timer != NULL; ++} ++ ++static void ++meta_thread_watcher_clear_source (MetaThreadWatcher *watcher) ++{ ++ MetaThreadWatcherPrivate *priv = meta_thread_watcher_get_instance_private (watcher); ++ priv->source = NULL; ++} ++ ++gboolean ++meta_thread_watcher_start (MetaThreadWatcher *watcher, ++ int interval_us, ++ GError **error) ++{ ++ MetaThreadWatcherPrivate *priv = meta_thread_watcher_get_instance_private (watcher); ++ GSource *source = NULL; ++ sigevent_t timer_request; ++ int ret; ++ g_autofree timer_t *timer = NULL; ++ ++ g_return_val_if_fail (META_IS_THREAD_WATCHER (watcher), FALSE); ++ g_return_val_if_fail (interval_us > ms2us (WATCH_INTERVAL_PHASE_OFFSET_MS), FALSE); ++ g_return_val_if_fail (priv->context != NULL, FALSE); ++ ++ if (meta_thread_watcher_is_started (watcher)) ++ return TRUE; ++ ++ priv->interval_ms = us2ms (interval_us); ++ ++ if (!g_unix_open_pipe (priv->fds, ++ FD_CLOEXEC | O_NONBLOCK, ++ error)) ++ return FALSE; ++ ++ if (priv->fds[0] == -1 || priv->fds[1] == -1) ++ { ++ g_set_error (error, G_IO_ERROR, g_io_error_from_errno (EBADF), ++ "Thread watcher could not create pipe"); ++ return FALSE; ++ } ++ ++ timer_request.sigev_notify = SIGEV_THREAD_ID; ++ timer_request.sigev_signo = SIGXCPU; ++ timer_request.sigev_value.sival_int = priv->fds[1]; ++ timer_request._sigev_un._tid = gettid (); ++ timer = g_new0 (timer_t, 1); ++ ret = timer_create (CLOCK_THREAD_CPUTIME_ID, &timer_request, timer); ++ ++ if (ret == -1) ++ { ++ g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), ++ "Failed to create unix timer: %s", g_strerror (errno)); ++ return FALSE; ++ } ++ ++ priv->timer = g_steal_pointer (&timer); ++ ++ if (!meta_thread_watcher_reset (watcher, error)) ++ return FALSE; ++ ++ priv->notification_watch_id = g_unix_fd_add (priv->fds[0], ++ G_IO_IN, ++ (GUnixFDSourceFunc) on_thread_stalled, ++ watcher); ++ ++ source = g_timeout_source_new (priv->interval_ms - WATCH_INTERVAL_PHASE_OFFSET_MS); ++ g_source_set_name (source, "[mutter] Thread watcher"); ++ g_source_set_callback (source, ++ (GSourceFunc) on_reset_timer, ++ watcher, ++ (GDestroyNotify) ++ meta_thread_watcher_clear_source); ++ g_source_attach (source, priv->context); ++ g_source_unref (source); ++ ++ priv->source = source; ++ ++ return TRUE; ++} ++ ++static void ++free_timer (timer_t *timer) ++{ ++ if (!timer) ++ return; ++ ++ timer_delete (*timer); ++} ++ ++gboolean ++meta_thread_watcher_reset (MetaThreadWatcher *watcher, ++ GError **error) ++{ ++ MetaThreadWatcherPrivate *priv = meta_thread_watcher_get_instance_private (watcher); ++ struct itimerspec timer_interval; ++ int ret; ++ ++ g_return_val_if_fail (META_IS_THREAD_WATCHER (watcher), FALSE); ++ ++ timer_interval.it_value.tv_sec = 0; ++ timer_interval.it_value.tv_nsec = ms2ns (priv->interval_ms); ++ timer_interval.it_interval.tv_sec = timer_interval.it_value.tv_sec; ++ timer_interval.it_interval.tv_nsec = timer_interval.it_value.tv_nsec; ++ ++ ret = timer_settime (*priv->timer, 0, &timer_interval, NULL); ++ ++ if (ret == -1) ++ { ++ g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), ++ "Failed to arm unix timer: %s", g_strerror (errno)); ++ meta_thread_watcher_stop (watcher); ++ ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ ++void ++meta_thread_watcher_stop (MetaThreadWatcher *watcher) ++{ ++ MetaThreadWatcherPrivate *priv = meta_thread_watcher_get_instance_private (watcher); ++ ++ g_return_if_fail (META_IS_THREAD_WATCHER (watcher)); ++ ++ if (!meta_thread_watcher_is_started (watcher)) ++ return; ++ ++ meta_thread_watcher_detach (watcher); ++ ++ g_clear_pointer (&priv->timer, free_timer); ++ g_clear_handle_id (&priv->notification_watch_id, g_source_remove); ++ close_fds (watcher); ++} +diff --git a/src/backends/native/meta-thread-watcher.h b/src/backends/native/meta-thread-watcher.h +new file mode 100644 +index 000000000..21e48de2d +--- /dev/null ++++ b/src/backends/native/meta-thread-watcher.h +@@ -0,0 +1,36 @@ ++/* ++ * Copyright (C) 2023 Red Hat ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License as ++ * published by the Free Software Foundation; either version 2 of the ++ * License, or (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, but ++ * WITHOUT ANY WARRANTY; without even the watcheried warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, see . ++ */ ++ ++#pragma once ++ ++#include ++ ++#define META_TYPE_THREAD_WATCHER (meta_thread_watcher_get_type ()) ++G_DECLARE_FINAL_TYPE (MetaThreadWatcher, meta_thread_watcher, ++ META, THREAD_WATCHER, GObject) ++ ++MetaThreadWatcher * meta_thread_watcher_new (void); ++void meta_thread_watcher_attach (MetaThreadWatcher *self, ++ GMainContext *context); ++void meta_thread_watcher_detach (MetaThreadWatcher *self); ++gboolean meta_thread_watcher_start (MetaThreadWatcher *watcher, ++ int interval_us, ++ GError **error); ++gboolean meta_thread_watcher_is_started (MetaThreadWatcher *watcher); ++gboolean meta_thread_watcher_reset (MetaThreadWatcher *watcher, ++ GError **error); ++void meta_thread_watcher_stop (MetaThreadWatcher *watcher); +diff --git a/src/backends/native/meta-thread.c b/src/backends/native/meta-thread.c +index 08d01144d..a4188ee56 100644 +--- a/src/backends/native/meta-thread.c ++++ b/src/backends/native/meta-thread.c +@@ -1,116 +1,121 @@ + /* + * Copyright (C) 2018-2021 Red Hat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + + #include "config.h" + + #include "backends/native/meta-thread-private.h" + + #include ++#include ++#include + #include ++#include + + #include "backends/meta-backend-private.h" + #include "backends/meta-backend-types.h" + #include "backends/native/meta-thread-impl.h" ++#include "backends/native/meta-thread-watcher.h" + + #include "meta-dbus-rtkit1.h" + #include "meta-private-enum-types.h" + + enum + { + PROP_0, + + PROP_BACKEND, + PROP_NAME, + PROP_THREAD_TYPE, + PROP_WANTS_REALTIME, + + N_PROPS + }; + + static GParamSpec *obj_props[N_PROPS]; + + typedef struct _MetaThreadCallbackData + { + MetaThreadCallback callback; + gpointer user_data; + GDestroyNotify user_data_destroy; + } MetaThreadCallbackData; + + typedef struct _MetaThreadCallbackSource + { + GSource base; + + GMutex mutex; + GCond cond; + + MetaThread *thread; + GMainContext *main_context; + GList *callbacks; + gboolean needs_flush; + } MetaThreadCallbackSource; + + typedef struct _MetaThreadPrivate + { + MetaBackend *backend; + char *name; + + GMainContext *main_context; + + MetaThreadImpl *impl; + gboolean wants_realtime; + gboolean waiting_for_impl_task; + GSource *wrapper_source; + + GMutex callbacks_mutex; + GHashTable *callback_sources; + + MetaThreadType thread_type; + + GThread *main_thread; + + struct { + GThread *thread; ++ MetaThreadWatcher *thread_watcher; + GMutex init_mutex; + } kernel; + } MetaThreadPrivate; + + typedef struct _MetaThreadClassPrivate + { + GType impl_type; + } MetaThreadClassPrivate; + + static void initable_iface_init (GInitableIface *initable_iface); + + G_DEFINE_TYPE_WITH_CODE (MetaThread, meta_thread, G_TYPE_OBJECT, + G_ADD_PRIVATE (MetaThread) + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, + initable_iface_init) + g_type_add_class_private (g_define_type_id, + sizeof (MetaThreadClassPrivate))) + + static void + meta_thread_callback_data_free (MetaThreadCallbackData *callback_data) + { + if (callback_data->user_data_destroy) + callback_data->user_data_destroy (callback_data->user_data); + g_free (callback_data); + } + + static void + meta_thread_get_property (GObject *object, + guint prop_id, + GValue *value, +@@ -181,123 +186,137 @@ get_rtkit_property (MetaDBusRealtimeKit1 *rtkit_proxy, + * org.freedesktop.DBus.Properties.GetAll. See + * . + */ + connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (rtkit_proxy)); + prop_value = + g_dbus_connection_call_sync (connection, + "org.freedesktop.RealtimeKit1", + "/org/freedesktop/RealtimeKit1", + "org.freedesktop.DBus.Properties", + "Get", + g_variant_new ("(ss)", + "org.freedesktop.RealtimeKit1", + property_name), + G_VARIANT_TYPE ("(v)"), + G_DBUS_CALL_FLAGS_NO_AUTO_START, + -1, NULL, error); + if (!prop_value) + return NULL; + + g_variant_get (prop_value, "(v)", &property_variant); + return g_steal_pointer (&property_variant); + } + + static gboolean + request_real_time_scheduling (MetaThread *thread, + GError **error) + { + MetaThreadPrivate *priv = meta_thread_get_instance_private (thread); + g_autoptr (MetaDBusRealtimeKit1) rtkit_proxy = NULL; + g_autoptr (GError) local_error = NULL; +- int64_t rttime; ++ int64_t rttime, watch_interval; + struct rlimit rl; + uint32_t priority; + + rtkit_proxy = + meta_dbus_realtime_kit1_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS | + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, + "org.freedesktop.RealtimeKit1", + "/org/freedesktop/RealtimeKit1", + NULL, + &local_error); + if (!rtkit_proxy) + { + g_dbus_error_strip_remote_error (local_error); + g_propagate_prefixed_error (error, g_steal_pointer (&local_error), + "Failed to acquire RTKit D-Bus proxy: "); + return FALSE; + } + + priority = meta_dbus_realtime_kit1_get_max_realtime_priority (rtkit_proxy); + if (priority == 0) + { + g_autoptr (GVariant) priority_variant = NULL; + + priority_variant = get_rtkit_property (rtkit_proxy, + "MaxRealtimePriority", + error); + if (!priority_variant) + return FALSE; + + priority = g_variant_get_int32 (priority_variant); + } + + if (priority == 0) + g_warning ("Maximum real time scheduling priority is 0"); + + rttime = meta_dbus_realtime_kit1_get_rttime_usec_max (rtkit_proxy); + if (rttime == 0) + { + g_autoptr (GVariant) rttime_variant = NULL; + + rttime_variant = get_rtkit_property (rtkit_proxy, + "RTTimeUSecMax", + error); + if (!rttime_variant) + return FALSE; + + rttime = g_variant_get_int64 (rttime_variant); + } + + meta_topic (META_DEBUG_BACKEND, + "Setting soft and hard RLIMIT_RTTIME limit to %lu", rttime); ++ ++ /* We set the soft-limit and hard-limit to the same value so the ++ * kernel won't send SIGXCPU to random threads. We synthesize our ++ * own SIGXCPU with a timer (See MetaThreadWatcher) that's always ++ * delivered to the approprate thread. ++ */ + rl.rlim_cur = rttime; + rl.rlim_max = rttime; + + if (setrlimit (RLIMIT_RTTIME, &rl) != 0) + { + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), + "Failed to set RLIMIT_RTTIME: %s", g_strerror (errno)); + return FALSE; + } + ++ /* We make sure if a SIGXCPU is synthesized it gets raised before anything ++ * the kernel could throw at us. We do this by setting an interval three quarters ++ * of the soft limit. ++ */ ++ watch_interval = (rl.rlim_cur * 3) / 4; ++ if (!meta_thread_watcher_start (priv->kernel.thread_watcher, watch_interval, error)) ++ return FALSE; ++ + meta_topic (META_DEBUG_BACKEND, "Setting '%s' thread real time priority to %d", + priv->name, priority); + if (!meta_dbus_realtime_kit1_call_make_thread_realtime_sync (rtkit_proxy, + gettid (), + priority, + NULL, + &local_error)) + { + g_dbus_error_strip_remote_error (local_error); + g_propagate_error (error, g_steal_pointer (&local_error)); + return FALSE; + } + + return TRUE; + } + + static gpointer + thread_impl_func (gpointer user_data) + { + MetaThread *thread = META_THREAD (user_data); + MetaThreadPrivate *priv = meta_thread_get_instance_private (thread); + MetaThreadImpl *impl = priv->impl; + MetaThreadImplRunFlags run_flags = META_THREAD_IMPL_RUN_FLAG_NONE; + GMainContext *thread_context = meta_thread_impl_get_main_context (impl); + #ifdef HAVE_PROFILER + MetaContext *context = meta_backend_get_context (priv->backend); + MetaProfiler *profiler = meta_context_get_profiler (context); + #endif + + g_mutex_lock (&priv->kernel.init_mutex); +@@ -443,77 +462,106 @@ static void + wrap_main_context (MetaThread *thread, + GMainContext *thread_main_context) + { + MetaThreadPrivate *priv = meta_thread_get_instance_private (thread); + g_autoptr (GSource) source = NULL; + WrapperSource *wrapper_source; + + if (!g_main_context_acquire (thread_main_context)) + g_return_if_reached (); + + source = g_source_new (&wrapper_source_funcs, + sizeof (WrapperSource)); + wrapper_source = (WrapperSource *) source; + wrapper_source->thread_main_context = thread_main_context; + g_source_set_ready_time (source, -1); + g_source_attach (source, NULL); + + priv->wrapper_source = source; + } + + static void + unwrap_main_context (MetaThread *thread, + GMainContext *thread_main_context) + { + MetaThreadPrivate *priv = meta_thread_get_instance_private (thread); + + g_main_context_release (thread_main_context); + g_clear_pointer (&priv->wrapper_source, g_source_destroy); + } + ++static void ++on_realtime_thread_stalled (MetaThread *thread) ++{ ++ MetaThreadPrivate *priv = meta_thread_get_instance_private (thread); ++ ++ g_warning ("Disabling realtime scheduling"); ++ priv->wants_realtime = FALSE; ++ meta_thread_reset_thread_type (thread, META_THREAD_TYPE_KERNEL); ++} ++ + static void + start_thread (MetaThread *thread) + { + MetaThreadPrivate *priv = meta_thread_get_instance_private (thread); ++ g_autoptr (GError) error = NULL; + + switch (priv->thread_type) + { + case META_THREAD_TYPE_USER: + wrap_main_context (thread, + meta_thread_impl_get_main_context (priv->impl)); + break; + case META_THREAD_TYPE_KERNEL: + g_mutex_init (&priv->kernel.init_mutex); + g_mutex_lock (&priv->kernel.init_mutex); ++ ++ if (priv->wants_realtime) ++ { ++ GMainContext *main_context; ++ ++ priv->kernel.thread_watcher = meta_thread_watcher_new (); ++ main_context = meta_thread_impl_get_main_context (priv->impl); ++ ++ meta_thread_watcher_attach (priv->kernel.thread_watcher, main_context); ++ ++ g_signal_connect_object (priv->kernel.thread_watcher, ++ "thread-stalled", ++ G_CALLBACK (on_realtime_thread_stalled), ++ thread, ++ G_CONNECT_SWAPPED); ++ } ++ + priv->kernel.thread = g_thread_new (priv->name, + thread_impl_func, + thread); ++ + g_mutex_unlock (&priv->kernel.init_mutex); + break; + } + } + + static gboolean + meta_thread_initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) + { + MetaThread *thread = META_THREAD (initable); + MetaThreadPrivate *priv = meta_thread_get_instance_private (thread); + MetaThreadClass *thread_class = META_THREAD_GET_CLASS (thread); + MetaThreadClassPrivate *class_priv = + G_TYPE_CLASS_GET_PRIVATE (thread_class, META_TYPE_THREAD, + MetaThreadClassPrivate); + g_autoptr (GMainContext) thread_context = NULL; + + priv->main_context = g_main_context_default (); + + priv->callback_sources = + g_hash_table_new_full (NULL, NULL, + NULL, (GDestroyNotify) g_source_destroy); + meta_thread_register_callback_context (thread, priv->main_context); + + thread_context = g_main_context_new (); + + g_assert (g_type_is_a (class_priv->impl_type, META_TYPE_THREAD_IMPL)); + priv->impl = g_object_new (class_priv->impl_type, + "thread", thread, +@@ -522,60 +570,63 @@ meta_thread_initable_init (GInitable *initable, + + start_thread (thread); + + return TRUE; + } + + static void + initable_iface_init (GInitableIface *initable_iface) + { + initable_iface->init = meta_thread_initable_init; + } + + static void + finalize_thread_user (MetaThread *thread) + { + MetaThreadPrivate *priv = meta_thread_get_instance_private (thread); + + meta_thread_impl_terminate (priv->impl); + while (meta_thread_impl_dispatch (priv->impl) > 0); + unwrap_main_context (thread, meta_thread_impl_get_main_context (priv->impl)); + } + + static void + finalize_thread_kernel (MetaThread *thread) + { + MetaThreadPrivate *priv = meta_thread_get_instance_private (thread); + + meta_thread_impl_terminate (priv->impl); + g_thread_join (priv->kernel.thread); + priv->kernel.thread = NULL; ++ ++ g_clear_object (&priv->kernel.thread_watcher); ++ + g_mutex_clear (&priv->kernel.init_mutex); + } + + static void + tear_down_thread (MetaThread *thread) + { + MetaThreadPrivate *priv = meta_thread_get_instance_private (thread); + + switch (priv->thread_type) + { + case META_THREAD_TYPE_USER: + finalize_thread_user (thread); + break; + case META_THREAD_TYPE_KERNEL: + finalize_thread_kernel (thread); + break; + } + + meta_thread_flush_callbacks (thread); + } + + static void + meta_thread_finalize (GObject *object) + { + MetaThread *thread = META_THREAD (object); + MetaThreadPrivate *priv = meta_thread_get_instance_private (thread); + + tear_down_thread (thread); + + meta_thread_unregister_callback_context (thread, priv->main_context); +@@ -640,61 +691,71 @@ meta_thread_class_init (MetaThreadClass *klass) + + static void + meta_thread_init (MetaThread *thread) + { + MetaThreadPrivate *priv = meta_thread_get_instance_private (thread); + + g_mutex_init (&priv->callbacks_mutex); + priv->main_thread = g_thread_self (); + } + + void + meta_thread_class_register_impl_type (MetaThreadClass *thread_class, + GType impl_type) + { + MetaThreadClassPrivate *class_priv = + G_TYPE_CLASS_GET_PRIVATE (thread_class, META_TYPE_THREAD, + MetaThreadClassPrivate); + + g_assert (class_priv->impl_type == G_TYPE_INVALID); + class_priv->impl_type = impl_type; + } + + void + meta_thread_reset_thread_type (MetaThread *thread, + MetaThreadType thread_type) + { + MetaThreadPrivate *priv = meta_thread_get_instance_private (thread); + g_autoptr (GMainContext) thread_context = NULL; + + if (priv->thread_type == thread_type) +- return; ++ { ++ gboolean is_realtime; ++ ++ if (thread_type != META_THREAD_TYPE_KERNEL) ++ return; ++ ++ is_realtime = meta_thread_impl_is_realtime (priv->impl); ++ ++ if (is_realtime == priv->wants_realtime) ++ return; ++ } + + tear_down_thread (thread); + g_assert (!priv->wrapper_source); + + priv->thread_type = thread_type; + + start_thread (thread); + + switch (priv->thread_type) + { + case META_THREAD_TYPE_USER: + g_assert (priv->wrapper_source); + break; + case META_THREAD_TYPE_KERNEL: + g_assert (!priv->wrapper_source); + break; + } + } + + static int + dispatch_callbacks (MetaThread *thread, + GList *pending_callbacks) + { + int callback_count = 0; + GList *l; + + for (l = pending_callbacks; l; l = l->next) + { + MetaThreadCallbackData *callback_data = l->data; + +diff --git a/src/meson.build b/src/meson.build +index 4aabb6a14..427cef4d0 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -828,61 +828,62 @@ if have_native_backend + 'backends/native/meta-kms.h', + 'backends/native/meta-onscreen-native.c', + 'backends/native/meta-onscreen-native.h', + 'backends/native/meta-pointer-constraint-native.c', + 'backends/native/meta-pointer-constraint-native.h', + 'backends/native/meta-render-device-gbm.c', + 'backends/native/meta-render-device-gbm.h', + 'backends/native/meta-render-device-private.h', + 'backends/native/meta-render-device-surfaceless.c', + 'backends/native/meta-render-device-surfaceless.h', + 'backends/native/meta-render-device.c', + 'backends/native/meta-render-device.h', + 'backends/native/meta-renderer-native-gles3.c', + 'backends/native/meta-renderer-native-gles3.h', + 'backends/native/meta-renderer-native-private.h', + 'backends/native/meta-renderer-native.c', + 'backends/native/meta-renderer-native.h', + 'backends/native/meta-renderer-view-native.c', + 'backends/native/meta-renderer-view-native.h', + 'backends/native/meta-seat-impl.c', + 'backends/native/meta-seat-impl.h', + 'backends/native/meta-seat-native.c', + 'backends/native/meta-seat-native.h', + 'backends/native/meta-stage-native.c', + 'backends/native/meta-stage-native.h', + 'backends/native/meta-thread-impl.c', + 'backends/native/meta-thread-impl.h', + 'backends/native/meta-thread-private.h', + 'backends/native/meta-thread.c', + 'backends/native/meta-thread.h', +- 'backends/native/meta-thread-private.h', ++ 'backends/native/meta-thread-watcher.h', ++ 'backends/native/meta-thread-watcher.c', + 'backends/native/meta-udev.c', + 'backends/native/meta-udev.h', + 'backends/native/meta-virtual-input-device-native.c', + 'backends/native/meta-virtual-input-device-native.h', + 'backends/native/meta-virtual-monitor-native.c', + 'backends/native/meta-virtual-monitor-native.h', + 'backends/native/meta-xkb-utils.c', + 'backends/native/meta-xkb-utils.h', + 'compositor/meta-compositor-native.c', + 'compositor/meta-compositor-native.h', + 'compositor/meta-compositor-view-native.c', + 'compositor/meta-compositor-view-native.h', + ] + endif + + if have_wayland or have_native_backend + mutter_sources += [ + 'common/meta-cogl-drm-formats.c', + 'common/meta-cogl-drm-formats.h', + ] + endif + + if have_wayland_eglstream + mutter_sources += [ + 'wayland/meta-wayland-egl-stream.c', + 'wayland/meta-wayland-egl-stream.h', + ] + endif + + mutter_private_enum_sources = [ +-- +2.41.0 + diff --git a/mutter.spec b/mutter.spec index 5821c47..ed4559b 100644 --- a/mutter.spec +++ b/mutter.spec @@ -32,6 +32,10 @@ Patch2: 0001-place-Always-center-initial-setup-fedora-welcome.patch Patch4: 0001-gschema-Enable-scale-monitor-framebuffer-experimenta.patch +Patch5: 0001-util-Add-way-to-print-backtraces.patch +Patch6: 0002-clutter-Add-ms2ns-helper.patch +Patch7: 0003-native-Stop-using-real-time-thread-if-it-stalls.patch + BuildRequires: pkgconfig(gobject-introspection-1.0) >= 1.41.0 BuildRequires: pkgconfig(sm) BuildRequires: pkgconfig(libwacom)