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:
- Load Average:
8.96, 6.81, 4.78on a 4-core CPU. - CPU Utilization: ~78% sustained across all cores.
- Memory & Swap: Out of 9.17 GB physical RAM, 4.36 GB was allocated and 1.02 GB / 12.0 GB Swap was actively churning.
- Top Processes: Multiple
cinnamon --replaceinstances consuming 14% to 21% CPU each, alongside Chromium/Brave renderers and Electron terminals (tabby) spawning active--crashpad-handlerthreads.
Two primary bottlenecks were identified:
- Memory Pressure & Swap Thrashing: Heavy swap paging caused repeated page faults whenever focus shifted between windows.
- 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:
- Context Switches (
cs): Spiked past 13,500 switches/s. - Interrupts (
in): Exceeded 5,500/s. - System CPU (
sy): Reached 55%.
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:
This post is licensed under CC BY-NC.



Comments
Join the discussion below.
0 comments