linux/fs
Dave Chinner d62016b1a2 xfs: avoid dquot buffer pin deadlock
On shutdown when quotas are enabled, the shutdown can deadlock
trying to unpin the dquot buffer buf_log_item like so:

[ 3319.483590] task:kworker/20:0H   state:D stack:14360 pid:1962230 tgid:1962230 ppid:2      task_flags:0x4208060 flags:0x00004000
[ 3319.493966] Workqueue: xfs-log/dm-6 xlog_ioend_work
[ 3319.498458] Call Trace:
[ 3319.500800]  <TASK>
[ 3319.502809]  __schedule+0x699/0xb70
[ 3319.512672]  schedule+0x64/0xd0
[ 3319.515573]  schedule_timeout+0x30/0xf0
[ 3319.528125]  __down_common+0xc3/0x200
[ 3319.531488]  __down+0x1d/0x30
[ 3319.534186]  down+0x48/0x50
[ 3319.540501]  xfs_buf_lock+0x3d/0xe0
[ 3319.543609]  xfs_buf_item_unpin+0x85/0x1b0
[ 3319.547248]  xlog_cil_committed+0x289/0x570
[ 3319.571411]  xlog_cil_process_committed+0x6d/0x90
[ 3319.575590]  xlog_state_shutdown_callbacks+0x52/0x110
[ 3319.580017]  xlog_force_shutdown+0x169/0x1a0
[ 3319.583780]  xlog_ioend_work+0x7c/0xb0
[ 3319.587049]  process_scheduled_works+0x1d6/0x400
[ 3319.591127]  worker_thread+0x202/0x2e0
[ 3319.594452]  kthread+0x20c/0x240

The CIL push has seen the deadlock, so it has aborted the push and
is running CIL checkpoint completion to abort all the items in the
checkpoint. This calls ->iop_unpin(remove = true) to clean up the
log items in the checkpoint.

When a buffer log item is unpined like this, it needs to lock the
buffer to run io completion to correctly fail the buffer and run all
the required completions to fail attached log items as well. In this
case, the attempt to lock the buffer on unpin is hanging because the
buffer is already locked.

I suspected a leaked XFS_BLI_HOLD state because of XFS_BLI_STALE
handling changes I was testing, so I went looking for
pin events on HOLD buffers and unpin events on locked buffer. That
isolated this one buffer with these two events:

xfs_buf_item_pin:     dev 251:6 daddr 0xa910 bbcount 0x2 hold 2 pincount 0 lock 0 flags DONE|KMEM recur 0 refcount 1 bliflags HOLD|DIRTY|LOGGED liflags DIRTY
....
xfs_buf_item_unpin:   dev 251:6 daddr 0xa910 bbcount 0x2 hold 4 pincount 1 lock 0 flags DONE|KMEM recur 0 refcount 1 bliflags DIRTY liflags ABORTED

Firstly, bbcount = 0x2, which means it is not a single sector
structure. That rules out every xfs_trans_bhold() case except one:
dquot buffers.

Then hung task dumping gave this trace:

[ 3197.312078] task:fsync-tester    state:D stack:12080 pid:2051125 tgid:2051125 ppid:1643233 task_flags:0x400000 flags:0x00004002
[ 3197.323007] Call Trace:
[ 3197.325581]  <TASK>
[ 3197.327727]  __schedule+0x699/0xb70
[ 3197.334582]  schedule+0x64/0xd0
[ 3197.337672]  schedule_timeout+0x30/0xf0
[ 3197.350139]  wait_for_completion+0xbd/0x180
[ 3197.354235]  __flush_workqueue+0xef/0x4e0
[ 3197.362229]  xlog_cil_force_seq+0xa0/0x300
[ 3197.374447]  xfs_log_force+0x77/0x230
[ 3197.378015]  xfs_qm_dqunpin_wait+0x49/0xf0
[ 3197.382010]  xfs_qm_dqflush+0x55/0x460
[ 3197.385663]  xfs_qm_dquot_isolate+0x29e/0x4d0
[ 3197.389977]  __list_lru_walk_one+0x141/0x220
[ 3197.398867]  list_lru_walk_one+0x10/0x20
[ 3197.402713]  xfs_qm_shrink_scan+0x6a/0x100
[ 3197.406699]  do_shrink_slab+0x18a/0x350
[ 3197.410512]  shrink_slab+0xf7/0x430
[ 3197.413967]  drop_slab+0x97/0xf0
[ 3197.417121]  drop_caches_sysctl_handler+0x59/0xc0
[ 3197.421654]  proc_sys_call_handler+0x18b/0x280
[ 3197.426050]  proc_sys_write+0x13/0x20
[ 3197.429750]  vfs_write+0x2b8/0x3e0
[ 3197.438532]  ksys_write+0x7e/0xf0
[ 3197.441742]  __x64_sys_write+0x1b/0x30
[ 3197.445363]  x64_sys_call+0x2c72/0x2f60
[ 3197.449044]  do_syscall_64+0x6c/0x140
[ 3197.456341]  entry_SYSCALL_64_after_hwframe+0x76/0x7e

Yup, another test run by check-parallel is running drop_caches
concurrently and the dquot shrinker for the hung filesystem is
running. That's trying to flush a dirty dquot from reclaim context,
and it waiting on a log force to complete. xfs_qm_dqflush is called
with the dquot buffer held locked, and so we've called
xfs_log_force() with that buffer locked.

Now the log force is waiting for a workqueue flush to complete, and
that workqueue flush is waiting of CIL checkpoint processing to
finish.

The CIL checkpoint processing is aborting all the log items it has,
and that requires locking aborted buffers to cancel them.

Now, normally this isn't a problem if we are issuing a log force
to unpin an object, because the ->iop_unpin() method wakes pin
waiters first. That results in the pin waiter finishing off whatever
it was doing, dropping the lock and then xfs_buf_item_unpin() can
lock the buffer and fail it.

However, xfs_qm_dqflush() is waiting on the -dquot- unpin event, not
the dquot buffer unpin event, and so it never gets woken and so does
not drop the buffer lock.

Inodes do not have this problem, as they can only be written from
one spot (->iop_push) whilst dquots can be written from multiple
places (memory reclaim, ->iop_push, xfs_dq_dqpurge, and quotacheck).

The reason that the dquot buffer has an attached buffer log item is
that it has been recently allocated. Initialisation of the dquot
buffer logs the buffer directly, thereby pinning it in memory. We
then modify the dquot in a separate operation, and have memory
reclaim racing with a shutdown and we trigger this deadlock.

check-parallel reproduces this reliably on 1kB FSB filesystems with
quota enabled because it does all of these things concurrently
without having to explicitly write tests to exercise these corner
case conditions.

xfs_qm_dquot_logitem_push() doesn't have this deadlock because it
checks if the dquot is pinned before locking the dquot buffer and
skipping it if it is pinned. This means the xfs_qm_dqunpin_wait()
log force in xfs_qm_dqflush() never triggers and we unlock the
buffer safely allowing a concurrent shutdown to fail the buffer
appropriately.

xfs_qm_dqpurge() could have this problem as it is called from
quotacheck and we might have allocated dquot buffers when recording
the quota updates. This can be fixed by calling
xfs_qm_dqunpin_wait() before we lock the dquot buffer. Because we
hold the dquot locked, nothing will be able to add to the pin count
between the unpin_wait and the dqflush callout, so this now makes
xfs_qm_dqpurge() safe against this race.

xfs_qm_dquot_isolate() can also be fixed this same way but, quite
frankly, we shouldn't be doing IO in memory reclaim context. If the
dquot is pinned or dirty, simply rotate it and let memory reclaim
come back to it later, same as we do for inodes.

This then gets rid of the nasty issue in xfs_qm_flush_one() where
quotacheck writeback races with memory reclaim flushing the dquots.
We can lift xfs_qm_dqunpin_wait() up into this code, then get rid of
the "can't get the dqflush lock" buffer write to cycle the dqlfush
lock and enable it to be flushed again.  checking if the dquot is
pinned and returning -EAGAIN so that the dquot walk will revisit the
dquot again later.

Finally, with xfs_qm_dqunpin_wait() lifted into all the callers,
we can remove it from the xfs_qm_dqflush() code.

Signed-off-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Carlos Maiolino <cmaiolino@redhat.com>
Signed-off-by: Carlos Maiolino <cem@kernel.org>
2025-06-27 14:14:37 +02:00
..
9p vfs-6.16-rc1.netfs 2025-06-02 15:04:06 -07:00
adfs Merge patch series "adfs, affs, befs, hfs, hfsplus: convert to new mount api" 2024-10-08 14:41:53 +02:00
affs vfs-6.15-rc1.async.dir 2025-03-24 10:47:14 -07:00
afs vfs-6.16-rc1.netfs 2025-06-02 15:04:06 -07:00
autofs VFS: rename lookup_one_len family to lookup_noperm and remove permission check 2025-04-08 11:24:36 +02:00
bcachefs bcachefs: Don't trace should_be_locked unless changing 2025-06-11 23:25:41 -04:00
befs befs: convert befs to use the new mount api 2024-09-18 11:44:43 +02:00
bfs bfs: convert bfs to use the new mount api 2025-04-07 09:36:20 +02:00
btrfs for-6.16-tag 2025-05-28 11:59:25 -07:00
cachefiles vfs-6.16-rc1.netfs 2025-06-02 15:04:06 -07:00
ceph A one-liner that leads to a startling (but also very much rational) 2025-06-06 17:56:19 -07:00
coda Change inode_operations.mkdir to return struct dentry * 2025-02-27 20:00:17 +01:00
configfs - The 3 patch series "hung_task: extend blocking task stacktrace dump to 2025-05-31 19:12:53 -07:00
cramfs
crypto fscrypt: add support for hardware-wrapped keys 2025-04-08 19:32:11 -07:00
debugfs VFS: rename lookup_one_len family to lookup_noperm and remove permission check 2025-04-08 11:24:36 +02:00
devpts devpts: Fix type for uid and gid params 2025-04-07 15:22:12 +02:00
dlm treewide, timers: Rename from_timer() to timer_container_of() 2025-06-08 09:07:37 +02:00
ecryptfs VFS: rename lookup_one_len family to lookup_noperm and remove permission check 2025-04-08 11:24:36 +02:00
efivarfs vfs-6.16-rc1.super 2025-05-26 09:33:44 -07:00
efs efs: fix the efs new mount api implementation 2024-10-15 15:58:36 +02:00
erofs vfs-6.16-rc1.netfs 2025-06-02 15:04:06 -07:00
exfat exfat: do not clear volume dirty flag during sync 2025-05-26 20:25:23 +09:00
exportfs readdir: supply dir_context.count as readdir buffer size hint 2025-05-29 12:31:23 +02:00
ext2 ext2: Deprecate DAX 2025-04-29 13:08:20 +02:00
ext4 treewide, timers: Rename from_timer() to timer_container_of() 2025-06-08 09:07:37 +02:00
f2fs f2fs-for-6.16-rc1 2025-05-30 08:40:25 -07:00
fat Change inode_operations.mkdir to return struct dentry * 2025-02-27 20:00:17 +01:00
freevxfs freevxfs: Replace one-element array with flexible array member 2024-11-06 10:42:06 +01:00
fuse fuse update for 6.16 2025-06-02 15:31:05 -07:00
gfs2 gfs2: Don't clear sb->s_fs_info in gfs2_sys_fs_add 2025-05-30 19:20:20 +02:00
hfs Revert "hfs{plus}: add deprecation warning" 2025-04-19 22:48:59 +02:00
hfsplus hfsplus: use bdev_rw_virt in hfsplus_submit_bio 2025-05-07 07:31:08 -06:00
hostfs Updates for UML for this cycle, notably: 2025-04-02 12:25:03 -07:00
hpfs Change inode_operations.mkdir to return struct dentry * 2025-02-27 20:00:17 +01:00
hugetlbfs - The 6 patch series "Enable strict percpu address space checks" from 2025-04-01 09:29:18 -07:00
iomap iomap: don't lose folio dropbehind state for overwrites 2025-05-28 09:26:07 +02:00
isofs isofs: fix Y2038 and Y2156 issues in Rock Ridge TF entry 2025-04-15 11:56:57 +02:00
jbd2 treewide, timers: Rename from_timer() to timer_container_of() 2025-06-08 09:07:37 +02:00
jffs2 jffs2: check jffs2_prealloc_raw_node_refs() result in few other places 2025-05-22 20:54:38 +02:00
jfs - The 11 patch series "Add folio_mk_pte()" from Matthew Wilcox 2025-05-31 15:44:16 -07:00
kernfs Driver core changes for 6.16-rc1 2025-05-29 09:11:39 -07:00
lockd sysctl: Fixes nsm_local_state bounds 2025-03-10 09:11:13 -04:00
minix Change inode_operations.mkdir to return struct dentry * 2025-02-27 20:00:17 +01:00
netfs vfs-6.16-rc1.netfs 2025-06-02 15:04:06 -07:00
nfs NFS Clent Updates for Linux 6.16 2025-06-03 16:13:32 -07:00
nfs_common nfs_localio: change nfsd_file_put_local() to take a pointer to __rcu pointer 2025-05-28 17:17:14 -04:00
nfsd NFS Clent Updates for Linux 6.16 2025-06-03 16:13:32 -07:00
nilfs2 treewide, timers: Rename from_timer() to timer_container_of() 2025-06-08 09:07:37 +02:00
nls move asm/unaligned.h to linux/unaligned.h 2024-10-02 17:23:23 -04:00
notify \n 2025-05-29 10:34:26 -07:00
ntfs3 - The 2 patch series "zram: support algorithm-specific parameters" from 2025-06-02 16:00:26 -07:00
ocfs2 treewide, timers: Rename from_timer() to timer_container_of() 2025-06-08 09:07:37 +02:00
omfs omfs: convert to new mount API 2025-04-28 10:54:39 +02:00
openpromfs
orangefs orangefs: Convert to use the new mount API 2025-05-28 12:05:30 -07:00
overlayfs overlayfs update for 6.16 2025-06-06 17:54:09 -07:00
proc - The 3 patch series "hung_task: extend blocking task stacktrace dump to 2025-05-31 19:12:53 -07:00
pstore treewide: Switch/rename to timer_delete[_sync]() 2025-04-05 10:30:12 +02:00
qnx4
qnx6 fs/qnx6: Fix building with GCC 15 2024-12-03 10:40:36 +01:00
quota VFS: rename lookup_one_len family to lookup_noperm and remove permission check 2025-04-08 11:24:36 +02:00
ramfs Change inode_operations.mkdir to return struct dentry * 2025-02-27 20:00:17 +01:00
resctrl fs/resctrl: Restore the rdt_last_cmd_clear() calls after acquiring rdtgroup_mutex 2025-06-04 20:32:55 +02:00
romfs
smb smb: improve directory cache reuse for readdir operations 2025-06-12 13:38:21 -05:00
squashfs squashfs: add optional full compressed block caching 2025-05-27 19:40:33 -07:00
sysfs sysfs: constify attribute_group::bin_attrs 2025-04-15 18:46:10 +02:00
tests
tracefs VFS: rename lookup_one_len family to lookup_noperm and remove permission check 2025-04-08 11:24:36 +02:00
ubifs This pull request contains the following fixes for JFFS2 and UBIFS: 2025-06-07 07:24:07 -07:00
udf udf: Make sure i_lenExtents is uptodate on inode eviction 2025-05-07 12:04:07 +02:00
ufs ufs: convert ufs to the new mount API 2025-05-14 22:40:55 -04:00
unicode unicode: kunit: change tests filename and path 2025-02-12 14:00:11 -08:00
vboxsf vboxsf: Convert to writepages 2025-04-07 09:36:48 +02:00
verity Revert "fsverity: relax build time dependency on CRYPTO_SHA256" 2025-02-17 11:34:15 -08:00
xfs xfs: avoid dquot buffer pin deadlock 2025-06-27 14:14:37 +02:00
zonefs zonefs: use bdev_rw_virt in zonefs_read_super 2025-05-07 07:31:07 -06:00
aio.c fs: aio: initialize .ki_write_stream of read-write request 2025-05-07 08:00:11 -06:00
anon_inodes.c anon_inode: raise SB_I_NODEV and SB_I_NOEXEC 2025-04-07 16:19:04 +02:00
attr.c fs: handle delegated timestamps in setattr_copy_mgtime 2024-10-10 10:20:51 +02:00
backing-file.c tree-wide: s/revert_creds_light()/revert_creds()/g 2024-12-02 11:25:09 +01:00
bad_inode.c Change inode_operations.mkdir to return struct dentry * 2025-02-27 20:00:17 +01:00
binfmt_elf_fdpic.c binfmt_elf_fdpic: fix variable set but not used warning 2025-03-07 20:07:33 -08:00
binfmt_elf.c vfs-6.16-rc1.misc 2025-05-26 09:02:39 -07:00
binfmt_flat.c binfmt_flat: Fix integer overflow bug on 32 bit systems 2025-01-10 08:49:05 -08:00
binfmt_misc.c VFS: rename lookup_one_len family to lookup_noperm and remove permission check 2025-04-08 11:24:36 +02:00
binfmt_script.c
bpf_fs_kfuncs.c bpf: fs/xattr: Add BPF kfuncs to set and remove xattrs 2025-02-13 19:35:32 -08:00
buffer.c vfs-6.16-rc1.writepage 2025-05-26 08:23:09 -07:00
char_dev.c fs: Reorganize kerneldoc parameter names 2024-10-22 11:16:57 +02:00
compat_binfmt_elf.c binfmt_elf: Wire up AT_HWCAP3 at AT_HWCAP4 2024-10-17 18:38:49 +01:00
coredump.c coredump: validate socket name as it is written 2025-05-21 13:59:12 +02:00
d_path.c
dax.c vfs-6.16-rc2.fixes 2025-06-02 12:49:16 -07:00
dcache.c vfs-6.16-rc1.misc 2025-05-26 09:02:39 -07:00
direct-io.c
drop_caches.c fs: drop_caches: move sysctl to fs/drop_caches.c 2025-02-07 16:53:04 +01:00
eventfd.c make use of anon_inode_getfile_fmode() 2025-02-21 10:25:31 +01:00
eventpoll.c fs/eventpoll: fix endless busy loop after timeout has expired 2025-05-02 14:21:26 +02:00
exec.c - The 11 patch series "Add folio_mk_pte()" from Matthew Wilcox 2025-05-31 15:44:16 -07:00
fcntl.c fs: get rid of __FMODE_NONOTIFY kludge 2024-12-09 11:34:29 +01:00
fhandle.c exportfs: add permission method 2024-12-17 09:16:11 +01:00
file_table.c fs: Make file-nr output the total allocated file handles 2025-04-21 10:27:58 +02:00
file.c fs: fall back to file_ref_put() for non-last reference 2025-04-22 18:16:09 +02:00
filesystems.c fs/filesystems: Fix potential unsigned integer underflow in fs_name() 2025-04-14 13:05:59 +02:00
fs_context.c fs/fs_context: Mark an unlikely if condition with unlikely() in vfs_parse_monolithic_sep() 2025-04-14 13:05:59 +02:00
fs_parser.c fs/fs_parse: Remove unused and problematic validate_constant_table() 2025-04-21 10:27:59 +02:00
fs_pin.c
fs_struct.c
fs_types.c
fs-writeback.c fs: fs-writeback: move sysctl to fs/fs-writeback.c 2025-02-07 16:53:04 +01:00
fsopen.c fs: support O_PATH fds with FSCONFIG_SET_FD 2025-02-12 10:02:10 +01:00
init.c VFS: Change vfs_mkdir() to return the dentry. 2025-03-05 11:52:50 +01:00
inode.c fs: call inode_sb_list_add() outside of inode hash lock 2025-03-20 13:06:51 +01:00
internal.h vfs-6.16-rc1.super 2025-05-26 09:33:44 -07:00
ioctl.c vfs-6.16-rc1.super 2025-05-26 09:33:44 -07:00
Kconfig fs/resctrl: Add boiler plate for external resctrl code 2025-05-16 11:05:40 +02:00
Kconfig.binfmt
kernel_read_file.c fdget(), trivial conversions 2024-11-03 01:28:06 -05:00
libfs.c vfs-6.16-rc1.super 2025-05-26 09:33:44 -07:00
locks.c treewide: const qualify ctl_tables where applicable 2025-01-28 13:48:37 +01:00
Makefile fs/resctrl: Add boiler plate for external resctrl code 2025-05-16 11:05:40 +02:00
mbcache.c
mnt_idmapping.c statmount: allow to retrieve idmappings 2025-02-12 12:12:27 +01:00
mount.h Don't propagate mounts into detached trees 2025-05-26 17:35:32 -04:00
mpage.c fs: use writeback_iter directly in mpage_writepages 2025-05-09 12:37:48 +02:00
namei.c vfs-6.16-rc1.misc 2025-05-26 09:02:39 -07:00
namespace.c mount-related bugfixes 2025-06-08 10:35:12 -07:00
nsfs.c vfs-6.15-rc1.nsfs 2025-03-24 11:38:12 -07:00
open.c fs/open: make do_truncate() killable 2025-05-15 12:03:12 +02:00
pidfs.c vfs-6.16-rc1.coredump 2025-05-26 11:17:01 -07:00
pipe.c sort.h: hoist cmp_int() into generic header file 2025-05-11 17:54:12 -07:00
pnode.c Don't propagate mounts into detached trees 2025-05-26 17:35:32 -04:00
pnode.h fix IS_MNT_PROPAGATING uses 2025-05-09 18:06:27 -04:00
posix_acl.c acl: Annotate struct posix_acl with __counted_by() 2024-10-22 11:16:59 +02:00
proc_namespace.c ->mnt_devname is never NULL 2025-05-23 14:20:44 +02:00
read_write.c fs/read_write: make default_llseek() killable 2025-05-15 12:03:12 +02:00
readdir.c readdir: supply dir_context.count as readdir buffer size hint 2025-05-29 12:31:23 +02:00
remap_range.c convert vfs_dedupe_file_range(). 2024-11-03 01:28:07 -05:00
select.c select: core_sys_select add unlikely branch hint on return path 2025-04-21 10:27:58 +02:00
seq_file.c fs: Reorganize kerneldoc parameter names 2024-10-22 11:16:57 +02:00
signalfd.c make use of anon_inode_getfile_fmode() 2025-02-21 10:25:31 +01:00
splice.c splice: remove duplicate noinline from pipe_clear_nowait 2025-04-25 12:11:56 +02:00
stack.c
stat.c xfs: New code for 6.16 2025-05-26 12:56:01 -07:00
statfs.c fdget_raw() users: switch to CLASS(fd_raw) 2024-11-03 01:28:06 -05:00
super.c automount wart removal 2025-05-30 15:38:29 -07:00
sync.c fdget(), trivial conversions 2024-11-03 01:28:06 -05:00
sysctls.c treewide: const qualify ctl_tables where applicable 2025-01-28 13:48:37 +01:00
timerfd.c A treewide hrtimer timer cleanup 2025-03-25 10:54:15 -07:00
userfaultfd.c mm/userfaultfd: fix uninitialized output field for -EAGAIN race 2025-05-07 23:39:39 -07:00
utimes.c fdget(), more trivial conversions 2024-11-03 01:28:06 -05:00
xattr.c fs/xattr.c: fix simple_xattr_list to always include security.* xattrs 2025-04-28 10:53:22 +02:00