Fix potential login freeze: skip blocking GStreamer registry rescan

Gst.init() has no async variant. If the plugin registry cache is stale
(e.g. right after a system update), it synchronously rescans every
plugin, spawning gst-plugin-scanner per plugin and probing hardware
for codecs. Since gnome-shell is single-threaded and is the
compositor, that can freeze the whole screen for several seconds,
same as the blocking UPower proxy lookup fixed earlier.

Set GST_REGISTRY_UPDATE=no when a cache already exists so Gst.init()
trusts it and returns immediately, then refresh the cache in a
throwaway subprocess afterward so it doesn't go stale forever.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-06 12:11:11 +02:00
co-authored by Claude Sonnet 5
parent d7278ee56d
commit 40af1dad26
+72
View File
@@ -1,3 +1,67 @@
import Gio from 'gi://Gio';
import GLib from 'gi://GLib';
/**
* True if a GStreamer plugin registry cache already exists for this user.
* A plain local directory listing — fast, unlike the scan below.
*/
function hasGstRegistryCache() {
const cacheDir = Gio.File.new_for_path(
GLib.build_filenamev([GLib.get_user_cache_dir(), 'gstreamer-1.0']));
try {
const enumerator = cacheDir.enumerate_children('standard::name', Gio.FileQueryInfoFlags.NONE, null);
const hasEntry = enumerator.next_file(null) !== null;
enumerator.close(null);
return hasEntry;
} catch (e) {
return false;
}
}
/**
* Gst.init() has no async variant. If its plugin registry cache is stale
* (e.g. right after a system update touched GStreamer packages), it
* synchronously rescans every plugin — spawning gst-plugin-scanner per
* plugin, and probing hardware for codec plugins — which can block
* gnome-shell's single main thread (it *is* the compositor) for many
* seconds, exactly the conditions right after login. That froze the entire
* screen the same way the blocking UPower proxy lookup did.
*
* GST_REGISTRY_UPDATE=no makes Gst.init() trust the existing cache
* unconditionally instead of validating it, so it returns immediately. Only
* set when a cache already exists — an extension-lifetime-first-ever
* GStreamer run on this machine still needs one real scan to have anything
* to trust.
*/
function keepGstInitFast() {
if (hasGstRegistryCache())
GLib.setenv('GST_REGISTRY_UPDATE', 'no', true);
}
/**
* Refreshes the on-disk registry cache in a throwaway subprocess, so a
* stale cache (skipped above) doesn't stay stale forever and newly
* installed plugins eventually become visible. Runs fully out-of-process:
* even if a hardware probe inside it hangs, gnome-shell is unaffected.
*/
function refreshGstRegistryInBackground() {
try {
const launcher = new Gio.SubprocessLauncher(
Gio.SubprocessFlags.STDOUT_SILENCE | Gio.SubprocessFlags.STDERR_SILENCE);
launcher.setenv('GST_REGISTRY_UPDATE', 'yes', true);
const proc = launcher.spawnv(['gst-inspect-1.0', '--exists', 'playbin']);
proc.wait_check_async(null, (source, result) => {
try {
source.wait_check_finish(result);
} catch (e) {
// Best-effort refresh; a missing binary or nonzero exit just leaves the cache as-is.
}
});
} catch (e) {
// Best-effort refresh; a missing gst-inspect-1.0 just leaves the cache as-is.
}
}
/**
* Loads the GObject-Introspection bindings the live wallpaper feature needs
* (Gst, GstApp for the appsink signals, Cogl for uploading decoded frames)
@@ -13,6 +77,8 @@
* checkGstreamerBaseAvailable() there instead.
*/
export async function loadGstreamerModules() {
keepGstInitFast();
const [{default: Gst}, , {default: Cogl}] = await Promise.all([
import('gi://Gst?version=1.0'),
import('gi://GstApp?version=1.0'),
@@ -22,6 +88,8 @@ export async function loadGstreamerModules() {
if (!Gst.is_initialized())
Gst.init(null);
refreshGstRegistryInBackground();
return {Gst, Cogl};
}
@@ -33,6 +101,8 @@ export async function loadGstreamerModules() {
* failure here is a genuine, actionable problem, unlike a Cogl probe.
*/
export async function checkGstreamerBaseAvailable() {
keepGstInitFast();
const [{default: Gst}] = await Promise.all([
import('gi://Gst?version=1.0'),
import('gi://GstApp?version=1.0'),
@@ -40,6 +110,8 @@ export async function checkGstreamerBaseAvailable() {
if (!Gst.is_initialized())
Gst.init(null);
refreshGstRegistryInBackground();
}
export const GSTREAMER_INSTALL_HINT =