Skip to content

Debugging I/O Hangs

When I/O is not just slow but completely stuck — how to identify, triage, and recover from I/O hangs in Linux

What is an I/O hang?

A process in D state (uninterruptible sleep) is waiting for I/O to complete. Normally this lasts microseconds to milliseconds. An I/O hang occurs when a process stays in D state for seconds, minutes, or indefinitely — typically because:

  • A storage device stopped responding (hardware failure, firmware hang, cable issue)
  • A network filesystem (NFS, CIFS) lost its server connection
  • The block layer detected an error and is retrying without limit
  • A kernel lock is held by a D-state process, causing a chain of dependents

The kernel logs a "hung task" warning after 120 seconds by default:

[12345.678901] INFO: task kworker/u8:2:1234 blocked for more than 120 seconds.
[12345.678902]       Not tainted 6.8.0 #1
[12345.678903] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
[12345.678904] task:kworker/u8:2    state:D stack:    0 pid: 1234 ppid:     2 flags:0x00004000
[12345.678905] Call Trace:
[12345.678906]  <TASK>
[12345.678907]  __schedule+0x2d4/0x8a0
[12345.678908]  schedule+0x46/0xb0
[12345.678909]  io_schedule+0x42/0x70
[12345.678910]  wait_for_completion_io+0x6e/0x110

Step 1: Identify which processes are hung

# Find all processes in D (uninterruptible sleep)
ps aux | awk '$8 ~ /^D/ { print $0 }'

# Or with more detail
for pid in $(ls /proc | grep '^[0-9]'); do
    state=$(cat /proc/$pid/status 2>/dev/null | grep '^State' | awk '{print $2}')
    if [ "$state" = "D" ]; then
        comm=$(cat /proc/$pid/comm 2>/dev/null)
        wchan=$(cat /proc/$pid/wchan 2>/dev/null)
        echo "PID $pid ($comm): blocked in $wchan"
    fi
done

Reading /proc/<pid>/wchan:

wchan value What the process is waiting for
io_schedule Generic block I/O
jbd2_log_wait_commit ext4 journal commit
nfs_wait_bit_killable NFS operation
wait_on_page_writeback Waiting for a specific page to be flushed
balance_dirty_pages_ratelimited Dirty throttling (usually resolves quickly)
blk_execute_rq Synchronous block request in progress
md_flush_request md/RAID flush

Step 2: Get the full kernel stack

# Requires CAP_SYS_ADMIN (root)
cat /proc/<pid>/stack

# Example output for a stuck ext4 write:
# [<0>] jbd2_log_wait_commit+0xb4/0x110
# [<0>] jbd2__journal_start+0x18c/0x340
# [<0>] __ext4_journal_start_sb+0x6c/0xf0
# [<0>] ext4_dirty_inode+0x34/0x60
# [<0>] __mark_inode_dirty+0x1cc/0x4b0
# [<0>] generic_write_end+0xb4/0x100
# [<0>] ext4_write_end+0x68/0x1c0
# [<0>] generic_perform_write+0x124/0x1c0
# [<0>] ext4_buffered_write_iter+0x58/0x100
# [<0>] vfs_write+0x298/0x3e0
# [<0>] __x64_sys_write+0x5c/0x100

The stack trace tells you exactly where in the kernel the process is blocked. Work upward from the innermost frame.

Getting stacks for all D-state processes at once (useful during an incident):

# Write a script to dump all D-state stacks
for pid in $(ls /proc | grep '^[0-9]'); do
    state=$(awk '/^State/{print $2}' /proc/$pid/status 2>/dev/null)
    if [ "$state" = "D" ]; then
        echo "=== PID $pid ($(cat /proc/$pid/comm 2>/dev/null)) ==="
        cat /proc/$pid/stack 2>/dev/null
        echo ""
    fi
done

# Alternatively, use sysrq to dump all stacks to dmesg
echo l > /proc/sysrq-trigger  # dumps all CPU stacks
# Then: dmesg | tail -200

Step 3: Check device health

If processes are stuck in block I/O, check whether the device itself has stopped responding.

# Check for device errors in kernel log
dmesg | grep -E '(error|timeout|reset|offline|failed|EIO|SCSI|ata|nvme)' | tail -30

# NVMe health
nvme smart-log /dev/nvme0

# SATA/SAS health
smartctl -a /dev/sda

# Check if device is still responding to I/O (will block if device is hung)
# Use timeout to avoid hanging your shell
timeout 5 dd if=/dev/sda of=/dev/null bs=512 count=1 iflag=direct 2>&1
# If this hangs: device is not responding to I/O

# Check block device queue state
cat /sys/block/sda/queue/in_flight    # I/Os currently in flight to the device
cat /sys/block/sda/queue/nr_requests  # maximum queue depth

Common device hang signatures in dmesg:

# NVMe timeout:
nvme nvme0: I/O 23 QID 1 timeout, aborting

# SCSI timeout:
sd 0:0:0:0: [sda] tag#0 FAILED Result: hostbyte=DID_TIMEOUT driverbyte=DRIVER_OK

# ATA timeout:
ata1.00: exception Emask 0x10 SAct 0x0 SErr 0x4010000 action 0x6
ata1.00: hard resetting link

# I/O error returned to filesystem:
EXT4-fs error (device sda1): ext4_find_entry:1455: inode #2: comm bash: reading directory lblock 0

Step 4: Check for NFS hangs

NFS hangs are a common cause of D-state processes. They occur when the NFS server becomes unreachable, or when the client's connection times out.

# Check NFS mount options
mount | grep nfs
# Look for: hard vs soft, timeo=, retrans=, intr

# Check NFS statistics for errors
nfsstat -c  # client-side stats
cat /proc/net/rpc/nfs  # raw NFS client RPC stats

# Check for pending RPC calls
cat /proc/net/rpc/nfsd  # server-side if this is a server

Hard vs soft NFS mounts:

A mount with hard (the default) will retry indefinitely on server failure — processes will hang in D state until the server returns. A soft mount will return EIO after retrans retries × timeo timeout.

# Re-mount with soft and timeo to avoid infinite hangs
mount -o remount,soft,timeo=30,retrans=3 /nfs/mountpoint

# Or force-unmount a stuck NFS mount (lazy unmount)
umount -l /nfs/mountpoint   # lazy: detach immediately, clean up when refs drop
umount -f /nfs/mountpoint   # force: attempt immediate unmount even with active files

Data risk with force unmount

Force-unmounting an NFS filesystem with dirty data may lose writes that have not yet reached the server. Only use -f when you accept potential data loss and need to recover the system.


Step 5: Check for writeback hangs

A writeback hang occurs when dirty pages cannot be flushed: the flusher kworker is stuck, or the device is returning errors that cause retries.

# Check writeback state
grep -E '(nr_dirty|nr_writeback|nr_dirty_threshold|nr_dirty_background_threshold)' /proc/vmstat

# Check if kworker threads are stuck
ps aux | grep kworker
# A kworker in D state doing writeback will show in its stack:
# writeback_sb_inodes / wb_writeback / wb_do_writeback

# Check BDI (Backing Device Info) state per device
ls /sys/class/bdi/
cat /sys/class/bdi/8:0/max_ratio          # max dirty ratio for this device
cat /sys/class/bdi/8:0/read_ahead_kb

# If a device has errors, writeback will retry:
dmesg | grep -E '(writeback|EIO|write error)' | tail -20

Force a writeback stall to resolve:

# Drop all clean cached pages (does NOT help if writeback is stuck on errors)
echo 1 > /proc/sys/vm/drop_caches

# Attempt to sync all filesystems (will block if writeback is hung)
timeout 30 sync

# If sync hangs, check which device is causing problems via blktrace
blktrace -d /dev/sda -o /tmp/btrace &
sleep 5
kill %1
blkparse -i /tmp/btrace.blktrace.0 | tail -50

Step 6: Check for lock contention causing hangs

Sometimes a process appears to be doing I/O but is actually waiting for a kernel lock held by a D-state I/O waiter. This creates a chain of stuck processes.

# lockdep output (if enabled in kernel config)
cat /proc/lockdep_stats

# Check for mutex/rwsem contention in process stacks
# Look for frames like:
#   mutex_lock_slowpath
#   rwsem_down_read_slowpath
#   down_read

Using perf lock to find lock contention:

# Record lock contention events for 10 seconds
perf lock record -a -- sleep 10

# Analyze: show contention by lock
perf lock report --key=wait

# Or use bpftrace to trace mutex contention
bpftrace -e '
kprobe:mutex_lock_slowpath {
    @[kstack] = count();
}
interval:s:5 {
    print(@);
    clear(@);
}'

Step 7: Recover from a device hang without rebooting

If a single device has hung but the system is otherwise functional, you may be able to reset the device without rebooting.

SCSI/SATA device reset:

# Trigger a SCSI device reset
echo 1 > /sys/block/sda/device/delete   # removes the device from the system
# Then rescan to re-add it:
echo "- - -" > /sys/class/scsi_host/host0/scan

# Or use sg_reset to send a bus reset
sg_reset --device /dev/sda
sg_reset --bus /dev/sda

NVMe reset:

# Reset an NVMe controller
nvme reset /dev/nvme0

# Or remove and re-add via sysfs
echo 1 > /sys/bus/pci/devices/<pci-id>/remove
echo 1 > /sys/bus/pci/rescan

md/RAID recovery:

# Check RAID status
cat /proc/mdstat

# Mark a failed drive as faulty and remove it
mdadm /dev/md0 --fail /dev/sdb
mdadm /dev/md0 --remove /dev/sdb

# Re-add after replacement
mdadm /dev/md0 --add /dev/sdb

Preventive configuration

# Reduce hung_task timeout to get faster warnings
echo 30 > /proc/sys/kernel/hung_task_timeout_secs

# Panic on hung tasks (for servers where a hang is worse than a reboot)
echo 1 > /proc/sys/kernel/hung_task_panic

# Enable NMI watchdog to detect hard lockups
echo 1 > /proc/sys/kernel/nmi_watchdog

# Set NFS mounts to soft with reasonable timeout
# Add to /etc/fstab: nfsserver:/export /mnt nfs soft,timeo=30,retrans=3 0 0

# Configure block device error handling
# The `max_sectors_kb` and error policy are device-specific
cat /sys/block/sda/device/timeout    # seconds before SCSI timeout
echo 30 > /sys/block/sda/device/timeout  # reduce from default 30s if needed