Finally managed to fix the issue with the help of Claude. 01 The symptom Unraid runs as a guest — here on Proxmox VE 9, OVMF + q35, with an Intel Arc A310 passed through for Jellyfin transcoding. Everything works: array healthy, shares serving, containers running, WebGUI up. But the machine will not shut down. shutdown -h now returns exit code 0 and does absolutely nothing. powerdown (deprecated; it's just /sbin/init 0) — same. The WebGUI's Stop array button spins forever. emcmd cmdStop=Stop returns 0 and logs nothing. ACPI shutdown from the hypervisor (qm shutdown <VMID> / virsh shutdown) is ignored. Zero lines appear in /var/log/syslog for any attempt. runlevel stays at N 3; uptime never resets. The only way out is a hard stop from the hypervisor — which means an unclean array stop and a full parity check on the next boot. On a 12 TB array that is most of a day. Most people blame the passed-through GPU. It isn't the GPU. 02 The signature you can check in 5 secondsps hangs, but everything else answers instantly. Command Behaviour ps, pgrep, pkill, top hang forever, unkillable uptime, runlevel, smbstatus, ls, SSH, WebGUI instant, normal ps and pgrep walk /proc and block the moment they read /proc/1, because PID 1 itself is in uninterruptible sleep (D state). Stop after the first hang Every ps/pgrep you run adds another unkillable D-state process. Retrying just digs the hole deeper. 03 Diagnosing it without psYou can enumerate D-state processes straight out of /proc, which does not block. for p in /proc/[0-9]*; do
[ "$(cut -d' ' -f3 "$p/stat" 2>/dev/null)" = D ] && \
echo "$(basename "$p") $(cat "$p/comm") $(cat "$p/wchan")"
doneOn the affected box that printed: pid=1 comm=init wchan=console_lock
pid=128 comm=kworker/10:1+events wchan=virtio_gpu_queue_fenced_ctrl_buffer
pid=375 comm=kworker/14:1+events wchan=modeset_lockRead that bottom-up — it is the entire bug: virtio GPU control queue stalls — never completeskworker in virtio_gpu_queue_fenced_ctrl_buffer…so that kworker never releases the DRM modeset lockmodeset_lock — held indefinitely…which keeps the kernel console lock heldconsole_lock — held indefinitely…and PID 1 waits on it, uninterruptiblyinit — state D, wchan console_lock · cannot process a runlevel change…so every shutdown path deadlocks reading /proc/1shutdown · powerdown · GUI Stop button · emcmd cmdStop=Stopall funnel into an rc script that calls pgrepemhttpd → emhttp_event → stopping_svcs → rc.watcher → pgrep (D) One stalled, invisible device at the top takes init down at the bottom. That is why shutdown "succeeds" and does nothing: it signals init, and init is asleep in the kernel. Small mercy It hangs before anything touches the array, so nothing is torn down half-way. It is the safest possible place to fail. 04 The root causeThe VM config had two display devices: the passed-through GPU (hostpci1: <GPU BDF>), and vga: virtio — Proxmox's default paravirtual display. Unraid's console lives on that virtio display. Nobody ever looks at it — the box is managed over the WebGUI and the real GPU is for transcoding — but the guest kernel still has virtio-gpu bound to it, and its control queue stalls. One unused, invisible device takes init down with it. Why it bites VMs specifically On bare metal there is no virtio-gpu, so this class of hang simply doesn't exist. It is a virtualization-only failure mode — which is exactly why searching for "Unraid won't shut down" turns up GPU-passthrough and Docker advice that doesn't apply. 05 The fix — one lineChange the emulated display away from virtio. On Proxmox, with the VM stopped: qm set <VMID> --vga std Or edit /etc/pve/qemu-server/<VMID>.conf: vga: virtio → vga: std. On libvirt/virt-manager, change the Video device model from virtio to vga/std/qxl. Back the config up first; the diff should be exactly one line. Results, measured before and after before after ps aux hung forever 282 lines, instant PID 1 state D / console_lock S / poll_schedule_timeout D-state processes 19 0 pgrep wedged works shutdown -h now no-op, exit 0 shuts down cleanly, VM powers off No parity check on the next boot. Array auto-started, all disks DISK_OK, mdResync=0. Rollback if you ever need it: qm set <VMID> --vga virtio. 06 What is not the causeRed herrings that cost time. The passed-through GPU. Removing or re-seating it changes nothing. The Arc A310 here was innocent; the fault is in the second, emulated display. Docker / VMs / plugins not stopping. They never get a chance to — the hang is upstream of all of it, in step one of the stop sequence. A broken array or a failing disk. The box is completely healthy while wedged: SSH, SMB, the WebGUI and every container keep serving normally. Nothing looks broken from the outside, which is half of why this is hard to spot. shutdown "not being supported". It's supported; init just can't hear it. 07 Already wedged? Shut down cleanly without a parity checkDon't hard-stop the VM — you'll eat the parity check. The deadlock is only in init and the rc scripts. Userspace daemons are fine, and mdcmd is literally echo $* > /proc/mdcmd — no pgrep anywhere. Easy path (works surprisingly often)emhttp is userspace and is not blocked, so the WebGUI still loads. Try Main → Stop array and watch for mdState=STOPPED in /var/local/emhttp/var.ini. If it completes, stop the VM from the hypervisor and you're done. If it spins forever — it will, once rc.watcher is reached — use the manual path. Manual path — bypass every rc script, over SSHdocker stop -t 30 $(docker ps -q) # clean & DB-safe; the docker API needs no pgrep
sync # verify: grep -E 'Dirty|Writeback' /proc/meminfo → 0 kB
exportfs -ua # drop NFS exports
# SIGTERM smbd / nmbd / winbindd / dockerd BY PID.
# NEVER use /etc/rc.d/rc.samba or rc.docker - they call pgrep and will hang.
umount /mnt/user /mnt/user0 # the shfs layer FIRST - it sits on top of the disks
umount /mnt/disk1 /mnt/disk2 ... # then each array disk
umount /mnt/cache /mnt/<your-pools> # then the pools
/usr/local/sbin/mdcmd stop # safe: writes straight to /proc/mdcmd
# verify BOTH before touching the hypervisor:
grep mdState= /var/local/emhttp/var.ini # want: mdState=STOPPED
mdcmd status | grep -E 'mdState|sbSyncExit' # want: sbSyncExit=0Then, and only then, stop the VM from the host (qm stop <VMID>). Why this is safe Unmount failures are non-destructive — a busy filesystem simply refuses. So there is a natural bail-out at every step, and nothing is committed until mdcmd stop. In the run documented here all 12 unmounts succeeded first try, dirty pages were 0 kB, mdState=STOPPED, and the next boot did no parity check. Two gotchas in the manual pathA killed daemon shows as state Z, not gone Init is wedged, so it cannot reap orphans. If you scan /proc by command name, zombies look identical to live processes and you'll wrongly conclude "samba is still up". Read state from /proc/<pid>/stat — Z means it already exited. Stopping samba strands your CIFS clients Any desktop or VM with shares mounted from this box will get stale handles. On Linux/KDE a dead CIFS mount makes file dialogs hang on every file, including local ones — unmount them first (umount -l, or stop the relevant systemd mount units). 08 TL;DRUnraid guest won't shut down, ps hangs but SSH and the WebGUI are fine? A stalled virtio-gpu control queue is holding console_lock, and PID 1 is stuck behind it. Set the VM's emulated display to std instead of virtio (qm set <VMID> --vga std) and it shuts down normally. Your passed-through GPU was never the problem.