OverviewFilesCommitsIssuesDocsReleasesPeers

hypercore-kb commits / ae70f85

scripts: diagnose a wedged FUSE mount, and clear it only when asked
Blender began hanging on startup — before any output, ignoring `timeout` — after running fine dozens of times in the same session. The cause was not Blender and not the code being tested: a GVFS/MTP mount (a phone, with adb running) stopped answering, and every process that enumerates mounted volumes blocked in UNINTERRUPTIBLE I/O on it. State D, wchan `request_wait_answer`, deaf to SIGTERM and SIGKILL alike, which is exactly why `timeout` appeared to do nothing. Two symptoms worth recognising next time, because both misdirect: * the process produces NO output at all, since it blocks before reaching any print — so it looks like the code under test hangs immediately; * it survives being killed, so a test harness reports a timeout rather than a crash and the natural conclusion is an infinite loop in the new code. I spent several rounds isolating a compute shader that was never involved. The tell I should have read: a two-texel probe and a full test suite hung IDENTICALLY, which points at the environment, not the code. The script reports by default and touches nothing. Aborting a connection makes pending I/O fail with EIO and frees the blocked processes; it destroys no data but does break that mount, so whatever was using it needs reconnecting — hence --abort being explicit, and --force being required to abort a connection that looks idle. scripts/unwedge-fuse.sh # diagnose scripts/unwedge-fuse.sh --abort # clear everything with requests waiting scripts/unwedge-fuse.sh --abort 54 # clear one Connection numbers map to mounts through the minor device number in mountinfo, so the report names paths rather than bare integers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ae70f85f4e6f4da5d99b22180d666a53012e5883
by plankatron <fleeky@gmail.com> · 2026-08-18T19:40:44.000Z

Changed files (1) +143 -0

scripts/unwedge-fuse.sh +143 -0
……@@ -0,0 +1,143 @@
1#!/usr/bin/env bash
2#
3# Diagnose (and optionally clear) a wedged FUSE mount.
4#
5# WHY THIS EXISTS
6#
7# A FUSE filesystem whose userspace daemon stops answering leaves every process
8# that touches it blocked in UNINTERRUPTIBLE I/O — state D, wchan
9# `request_wait_answer`. Such a process cannot be killed: SIGTERM and SIGKILL are
10# both ignored until the I/O completes, which it never will. `timeout` appears to
11# do nothing, and the process shows no output at all because it blocked before it
12# got anywhere.
13#
14# The symptom that sent us here: Blender hung on startup, before even printing,
15# after running fine dozens of times in the same session. Blender enumerates
16# mounted volumes at startup, so it touches /run/user/$UID/gvfs — which is where
17# GVFS puts an MTP phone. With adb running and a phone attached, one unanswered
18# request wedges every subsequent Blender launch.
19#
20# It is not specific to Blender. Anything that enumerates mounts — file dialogs,
21# `df`, backup tools, an editor's file browser — will hang the same way.
22#
23# WHAT ABORTING DOES
24#
25# Writing to /sys/fs/fuse/connections/<N>/abort makes all pending and future I/O
26# on that connection fail with EIO, which releases the blocked processes. It
27# destroys no data on disk. It DOES break that mount: anything using it (the
28# phone) must be remounted, usually by unplugging and replugging, or by
29# restarting the gvfs daemon.
30#
31# Because that is disruptive to something the user may be mid-way through, this
32# script REPORTS by default and only aborts when explicitly asked.
33#
34# USAGE
35# scripts/unwedge-fuse.sh # diagnose only; touches nothing
36# scripts/unwedge-fuse.sh --abort # abort every connection with waiting > 0
37# scripts/unwedge-fuse.sh --abort 54 # abort exactly connection 54
38# scripts/unwedge-fuse.sh --abort 54 --force # even if it looks idle
39#
40# Aborting needs root, so it re-invokes itself through sudo for that step only.
41
42set -uo pipefail
43
44CONNECTIONS=/sys/fs/fuse/connections
45DO_ABORT=0
46FORCE=0
47WANT=""
48
49while [ $# -gt 0 ]; do
50 case "$1" in
51 --abort) DO_ABORT=1; shift
52 # An optional bare number after --abort selects one connection.
53 if [ $# -gt 0 ] && printf '%s' "$1" | grep -qE '^[0-9]+$'; then WANT="$1"; shift; fi ;;
54 --force) FORCE=1; shift ;;
55 -h|--help) sed -n '2,40p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
56 *) echo "unknown argument: $1" >&2; exit 2 ;;
57 esac
58done
59
60if [ ! -d "$CONNECTIONS" ]; then
61 echo "no $CONNECTIONS — fusectl is not mounted, so there is nothing to inspect"
62 exit 0
63fi
64
65# ---------------------------------------------------------------- diagnose
66
67# A fuse mount's connection number IS the minor device number in mountinfo, which
68# is how a bare number under /sys becomes a path a human recognises.
69mount_for() {
70 awk -v want="0:$1" '$3 == want { print $9 " on " $5 }' /proc/self/mountinfo 2>/dev/null | head -1
71}
72
73echo "FUSE connections:"
74wedged=""
75for dir in "$CONNECTIONS"/*/; do
76 [ -d "$dir" ] || continue
77 n=$(basename "$dir")
78 waiting=$(cat "$dir/waiting" 2>/dev/null || echo "?")
79 where=$(mount_for "$n")
80 [ -n "$where" ] || where="(no matching mount — already unmounted?)"
81 mark=" "
82 if [ "$waiting" != "0" ] && [ "$waiting" != "?" ]; then
83 mark=" *"
84 wedged="$wedged $n"
85 fi
86 printf "%s %-4s waiting=%-4s %s\n" "$mark" "$n" "$waiting" "$where"
87done
88
89echo
90echo "Processes blocked in uninterruptible I/O (state D):"
91blocked=$(ps -eo pid,stat,wchan:22,comm --no-headers 2>/dev/null | awk '$2 ~ /^D/')
92if [ -z "$blocked" ]; then
93 echo " none"
94else
95 echo "$blocked" | sed 's/^/ /'
96 echo
97 echo " These ignore SIGTERM and SIGKILL. Aborting the connection is what frees"
98 echo " them; killing is not an option while the I/O is outstanding."
99fi
100
101if [ -z "$wedged" ]; then
102 echo
103 echo "No connection has requests waiting. If something is still hung, it is not"
104 echo "this — look elsewhere before aborting anything."
105 [ "$DO_ABORT" = "1" ] && [ "$FORCE" = "0" ] && exit 0
106fi
107
108# ------------------------------------------------------------------- abort
109
110if [ "$DO_ABORT" = "0" ]; then
111 echo
112 echo "Diagnosis only; nothing was touched."
113 [ -n "$wedged" ] && echo "To clear: $0 --abort${WANT:+ $WANT}"
114 exit 0
115fi
116
117targets="${WANT:-$wedged}"
118if [ -z "$targets" ]; then
119 echo "nothing to abort" >&2
120 exit 1
121fi
122
123for n in $targets; do
124 if [ ! -w "$CONNECTIONS/$n/abort" ] && [ ! -e "$CONNECTIONS/$n/abort" ]; then
125 echo "connection $n does not exist" >&2
126 continue
127 fi
128 waiting=$(cat "$CONNECTIONS/$n/waiting" 2>/dev/null || echo "?")
129 if [ "$waiting" = "0" ] && [ "$FORCE" = "0" ]; then
130 echo "connection $n has nothing waiting; skipping (use --force to abort anyway)"
131 continue
132 fi
133 echo "aborting connection $n ($(mount_for "$n"))"
134 if [ -w "$CONNECTIONS/$n/abort" ]; then
135 echo 1 > "$CONNECTIONS/$n/abort"
136 else
137 echo 1 | sudo tee "$CONNECTIONS/$n/abort" >/dev/null
138 fi
139done
140
141echo
142echo "Done. Blocked processes should exit within a moment; anything that was using"
143echo "the mount (a phone over MTP, for instance) needs reconnecting."