Desktop freeze post-mortem: from futex lockups to Cgroups v2 on Linux
Back to blog

Desktop freeze post-mortem: from futex lockups to Cgroups v2 on Linux

10/7/2026 · 4 min · Linux

The frozen desktop: when a futex lockup turns into a Cgroups v2 investigation#

Working daily with dozens of browser tabs, heavy terminal emulators, and local containers often means walking a fine line between high productivity and an unresponsive desktop. Recently, I experienced severe UI stuttering on Linux Mint (Cinnamon): mouse movement lagged, application windows became unresponsive, and the entire workstation ground to a halt.

Instead of forcing a hard reboot, I accessed a virtual tty terminal, captured system metrics, and traced the root cause down to the kernel layer.

Below is the technical post-mortem, the diagnostic toolkit used, and the containment strategy implemented to safeguard the desktop environment.


1. Initial symptoms and htop triage#

Opening htop in a tty terminal revealed critical resource exhaustion:

Two primary bottlenecks were identified:

  1. Memory Pressure & Swap Thrashing: Heavy swap paging caused repeated page faults whenever focus shifted between windows.
  2. Window Manager Crash Loop: Cinnamon was trapped in a render loop waiting for GPU and Xorg synchronization.

2. Kernel-level tracing: strace and futex contention#

To inspect what the main Cinnamon process (cinnamon --replace) was executing, I traced its system calls:

strace -p <PID_CINNAMON> -c -- sleep 10

The summary revealed a severe concurrency lockup:

% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 99,37    0,230989         106      2173       113 futex
  0,18    0,000414           3       120           write
  0,16    0,000372         372         1           execve
...
100,00    0,232457          98      2349       118 total

Over 99.3% of CPU time was spent trapped inside the futex (Fast Userspace Mutex) syscall, accompanied by more than 100 ETIMEDOUT/EAGAIN errors. The window manager was in a Busy Waiting loop, spinning on synchronization locks that were never released.


3. Scheduler diagnostics: vmstat and context switching#

To evaluate the broader impact on the kernel scheduler, I ran vmstat:

vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- -------cpu-------
 r  b   swpd  free    buff  cache   si   so    bi    bo   in   cs us sy id wa st gu
 8  0 1063936 2155216 133344 3756084   38  138   465   688 3614   17 34 12 52  2  0  0
10  1 1063936 2164608 133344 3749040    0    0     0     0 4249 12005 20 38 41  0  0  0
 9  0 1063936 2162456 133344 3746108    0    0     0     0 5491 12703 37 33 29  1  0  0
 8  0 1063936 2155848 133352 3746100    0    0     0   132 5590 13532 29 55 16  1  0  0

Key observations:

When kernel-space CPU time (sy) outpaces user-space execution (us), the operating system is spending more cycles managing process queues and locks than performing actual workload tasks.


4. OOM score auditing and orphaned processes#

To identify which processes were driving memory exhaustion:

printf "PID\tOOM_SCORE\tCOMMAND\n"
for proc in $(ls /proc | grep -E '^[0-9]+'); do
    if [ -f /proc/$proc/oom_score ]; then
        printf "$proc\t$(cat /proc/$proc/oom_score)\t$(cat /proc/$proc/comm)\n"
    fi
done | sort -k2 -nr | head -n 10
PID     OOM_SCORE   COMMAND
3547404 876         brave
3547749 874         brave
3547682 874         brave
3547658 872         brave

Multiple browser renderer processes exhibited oom_score values above 870 while consuming 30% to 40% CPU each. Following the window manager crash, these child processes lost their graphical session connection and fell into unconstrained spinlocks.


5. I/o stalls and background updates#

After killing the unresponsive renderers and resetting swap, memory stabilized (6 GB free), but micro-stutters lingered.

Running iotop in batch mode revealed the underlying cause:

iotop -o -b -n 2
Total DISK WRITE: 29.84 M/s
    TID  PRIO  USER     DISK WRITE  COMMAND
3683027 be/4 user        3.04 M/s  flatpak-update-worker
    640 be/3 root       54.11 K/s  [jbd2/sda7-8]

A background process (flatpak-update-worker) was writing nearly 30 MB/s to disk. Checking PSI (Pressure Stall Information) metrics confirmed I/O contention:

cat /proc/pressure/io
# some avg10=2.25 avg60=3.64 avg300=3.16
# full avg10=1.38 avg60=2.44 avg300=2.17

A full avg10=1.38 metric indicated that for 1.38% of the time, all active CPU threads were entirely stalled waiting for disk I/O completion.


6. Resolving the zramctl --reset pitfall#

When attempting to flush the compressed RAM swap device:

swapoff /dev/zram0
zramctl --reset /dev/zram0
swapon /dev/zram0 -p 100

The system returned: swapon: cannot open /dev/zram0: No such file or directory

Root Cause: The zramctl --reset command not only wipes memory contents, but deletes the virtual block device from the kernel. To recreate it properly:

# 1. Allocate a fresh 4GB zram block device
zramctl --find --size 4G

# 2. Format as swap
mkswap /dev/zram0

# 3. Enable with high priority
swapon /dev/zram0 -p 100

7. Permanent containment: Cgroups v2 slice isolation#

To prevent memory leaks in browsers and Electron clients from crashing the entire desktop session, I implemented cgroups v2 resource limits via Systemd User Slices.

1. Defining the resource slice#

Create ~/.config/systemd/user/apps-limited.slice:

[Unit]
Description=Resource slice for memory-intensive apps
Before=slices.target

[Slice]
MemoryMax=4G
MemoryHigh=3.5G
CPUWeight=50
IOWeight=50

2. Reloading the systemd user daemon#

USER_UID=$(id -u $USER)
XDG_RUNTIME_DIR=/run/user/$USER_UID DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$USER_UID/bus systemctl --user daemon-reload

3. Application launcher wrapper#

To launch the browser inside the constrained slice automatically:

#!/bin/bash
TARGET_USER="$USER"
USER_ID=$(id -u $TARGET_USER)
export XDG_RUNTIME_DIR="/run/user/$USER_ID"
export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$USER_ID/bus"

systemd-run --user --slice=apps-limited.slice --scope /usr/bin/brave-browser "$@"
chmod +x /usr/local/bin/brave

Upon reaching 3.5 GB (MemoryHigh), the kernel applies smooth reclaim throttling. If usage hits 4.0 GB (MemoryMax), the OOM killer terminates only the offending tab or worker inside the slice, keeping the Cinnamon desktop responsive and intact.


Practical stability takeaways#

Workstation performance issues rarely occur in isolation: memory saturation triggered swap thrashing, which induced futex lockups inside the Cinnamon window manager, leaving orphaned worker processes competing for CPU against disk-heavy background updates.

Tackling desktop instability is not a matter of rebooting on every freeze, but of tracing kernel-level bottlenecks, bounding aggressive processes with Cgroups v2, and tuning memory subsystems for predictable resource allocation.

Was this article helpful?

Leave a quick reaction to help prioritize future technical guides:

CC BY-NC

This post is licensed under CC BY-NC.

Comments

Join the discussion below.

0 comments