mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* Lockdep circular dependency: btrfs_swap_activate() calls sysfs_notify()
@ 2026-07-31 10:57 Christian Borntraeger
  2026-07-31 11:12 ` Qu Wenruo
  0 siblings, 1 reply; 5+ messages in thread
From: Christian Borntraeger @ 2026-07-31 10:57 UTC (permalink / raw)
  To: Chris Mason, David Sterba, Qu Wenruo; +Cc: linux-btrfs, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 6384 bytes --]

One or more of the following files ( btrfs-swapon-kernfs-repro.sh ) violates IBM policy and all attachment(s) have been removed from the message.

**********************************************************************
We had the calltrace at the bottom of this mail in our CI logs
I also attached an AI generated reproducer that triggers this easily.

For convenience, here is what AI came up with analysing the log, but
I would like your take on it. Let me know if you want to see the
AI proposed fix.

----

btrfs_swap_activate() takes the inode's i_mmap_lock for write very
early (inode.c:10120) and holds it across the entire function; the
comment there explains the intent, which is to keep mmap writes from
racing with the delalloc flush and the extent range lock.

There are three btrfs_exclop_finish() calls inside that window:

   fs/btrfs/inode.c:10181   error path, swapfile on a rw subvolume
                            with an active snapshot
   fs/btrfs/inode.c:10202   error path, could not lock the snapshot drew
                            lock
   fs/btrfs/inode.c:10399   the common "out:" path -- taken on both
                            success and failure

and btrfs_exclop_finish() (fs/btrfs/fs.c:224) ends with an unconditional
sysfs_notify().  So the offending edge is taken on *every* successful
swapon of a btrfs swap file, not only on an error path.

The sysfs_notify() itself does nothing but a kernfs lookup plus a poll
wakeup for userspace watching the "exclusive_operation" attribute.  It
has no dependency whatsoever on i_mmap_lock, or on the inode at all.


Both kernfs_rwsem acquisitions in the cycle are read acquisitions
({++++}), so the two of them alone cannot deadlock.  A real hang needs a
third task waiting to take kernfs_rwsem for write, because rwsem
write-fairness makes a later down_read() block behind a queued writer:

   T_swapon:  holds i_mmap_lock(write)
              blocks in down_read(kernfs_rwsem)   [queued behind T_w]
   T_w:       blocks in down_write(kernfs_rwsem)  [waiting for T_dir]
              e.g. any sysfs node create/remove -- device hotplug,
              module load, cgroup or block-device attribute changes
   T_dir:     holds kernfs_rwsem(read) in kernfs_fop_readdir
              faults on the user dirent buffer -> mmap_lock
              -> btrfs_page_mkwrite -> down_read(i_mmap_lock)
              blocks behind T_swapon's write holder

   -> three-way deadlock.

That is a narrow race, which is consistent with this having gone
unnoticed for years, but every step of it is ordinary system activity.
The dependency is genuine and worth fixing rather than annotating away.

Suggested fix is to get the sysfs_notify() out from under i_mmap_lock


real life log found in our CI:
----------------------------
LOCKDEP_CIRCULAR (suite: tela-distro, case: tests/test_mempig/test_mempig)
WARNING: possible circular locking dependency detected
7.2.0-20260730.rc5.git10.af7a8a7752eb.300.fc44.s390x+debug #1 Not tainted
------------------------------------------------------
swapon/172010 is trying to acquire lock:
000002ea80a485a0 (&root->kernfs_rwsem){++++}-{3:3}, at: kernfs_find_and_get_ns+0x3c/0x80
but task is already holding lock:
000002ebc465d270 (&ei->i_mmap_lock){++++}-{3:3}, at: btrfs_swap_activate+0x9a/0x1240
which lock already depends on the new lock.
the existing dependency chain (in reverse order) is:
-> #3 (&ei->i_mmap_lock){++++}-{3:3}:
        lock_acquire+0x150/0x3f0
        down_read+0x5a/0x280
        btrfs_page_mkwrite+0x258/0x870
        do_page_mkwrite+0x60/0x160
        do_wp_page+0x128/0x750
        __handle_mm_fault+0x1be/0x590
        handle_mm_fault+0xa2/0x370
        do_exception+0x292/0x590
        __do_pgm_check+0x168/0x430
        pgm_check_handler+0x114/0x160
-> #2 (sb_pagefaults){.+.+}-{0:0}:
        lock_acquire+0x150/0x3f0
        percpu_down_read_internal.constprop.0+0x54/0x120
        btrfs_page_mkwrite+0xa6/0x870
        do_page_mkwrite+0x60/0x160
        do_fault+0x132/0x4a0
        __handle_mm_fault+0x1be/0x590
        handle_mm_fault+0xa2/0x370
        do_exception+0x1a0/0x590
        __do_pgm_check+0x168/0x430
        pgm_check_handler+0x114/0x160
-> #1 (&mm->mmap_lock){++++}-{3:3}:
        lock_acquire+0x150/0x3f0
        __might_fault+0x7a/0xa0
        filldir64+0x11c/0x210
        kernfs_fop_readdir+0x150/0x4c0
        iterate_dir+0xcc/0x2d0
        __do_sys_getdents64+0x7a/0x130
        __do_syscall+0x172/0x750
        system_call+0x72/0x90
-> #0 (&root->kernfs_rwsem){++++}-{3:3}:
        check_prev_add+0x160/0xf40
        __lock_acquire+0x12aa/0x15a0
        lock_acquire+0x150/0x3f0
        down_read+0x5a/0x280
        kernfs_find_and_get_ns+0x3c/0x80
        sysfs_notify+0x60/0xc0
        btrfs_swap_activate+0x83c/0x1240
        __do_sys_swapon+0x278/0x9c0
        __do_syscall+0x172/0x750
        system_call+0x72/0x90
other info that might help us debug this:
Chain exists of:
   &root->kernfs_rwsem --> sb_pagefaults --> &ei->i_mmap_lock
  Possible unsafe locking scenario:
        CPU0                    CPU1
        ----                    ----
   lock(&ei->i_mmap_lock);
                                lock(sb_pagefaults);
                                lock(&ei->i_mmap_lock);
   rlock(&root->kernfs_rwsem);
  *** DEADLOCK ***
2 locks held by swapon/172010:
  #0: 000002ebc465d3f0 (&sb->s_type->i_mutex_key#20){++++}-{3:3}, at: __do_sys_swapon+0x5be/0x9c0
  #1: 000002ebc465d270 (&ei->i_mmap_lock){++++}-{3:3}, at: btrfs_swap_activate+0x9a/0x1240
stack backtrace:
CPU: 6 UID: 0 PID: 172010 Comm: swapon Not tainted 7.2.0-20260730.rc5.git10.af7a8a7752eb.300.fc44.s390x+debug #1 PREEMPT
Hardware name: IBM 8561 T01 701 (z/VM 7.4.0)
Call Trace:
  [<000003f7d5ab4e3e>] dump_stack_lvl+0xae/0x108
  [<000003f7d5bbef24>] print_circular_bug+0x1a4/0x230
  [<000003f7d5bbf13c>] check_noncircular+0x18c/0x1b0
  [<000003f7d5bc0510>] check_prev_add+0x160/0xf40
  [<000003f7d5bc408a>] __lock_acquire+0x12aa/0x15a0
  [<000003f7d5bc44d0>] lock_acquire+0x150/0x3f0
  [<000003f7d6c447ca>] down_read+0x5a/0x280
  [<000003f7d60ad61c>] kernfs_find_and_get_ns+0x3c/0x80
  [<000003f7d60b3b70>] sysfs_notify+0x60/0xc0
  [<000003f7d635653c>] btrfs_swap_activate+0x83c/0x1240
  [<000003f7d5f1d268>] __do_sys_swapon+0x278/0x9c0
  [<000003f7d6c369a2>] __do_syscall+0x172/0x750
  [<000003f7d6c4baa2>] system_call+0x72/0x90
INFO: lockdep is turned off.

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: Lockdep circular dependency: btrfs_swap_activate() calls sysfs_notify()
  2026-07-31 10:57 Lockdep circular dependency: btrfs_swap_activate() calls sysfs_notify() Christian Borntraeger
@ 2026-07-31 11:12 ` Qu Wenruo
  2026-07-31 11:14   ` Christian Borntraeger
  0 siblings, 1 reply; 5+ messages in thread
From: Qu Wenruo @ 2026-07-31 11:12 UTC (permalink / raw)
  To: Christian Borntraeger, Chris Mason, David Sterba, Qu Wenruo
  Cc: linux-btrfs, linux-kernel



在 2026/7/31 20:27, Christian Borntraeger 写道:
> One or more of the following files ( btrfs-swapon-kernfs-repro.sh ) 
> violates IBM policy and all attachment(s) have been removed from the 
> message.

If the bash script, as an attachment, violates the policy, can you just 
paste the content as a reply?

As I still have some uncertainty on the LLM explanation.
A concrete reproducer would allow us to do our own experiments and get 
everything more clear.

Otherwise really appreciated all your reports, no matter if it's 
generated by LLM or not, as so far your reports are 100% real bugs.

Thanks,
Qu

> 
> **********************************************************************
> We had the calltrace at the bottom of this mail in our CI logs
> I also attached an AI generated reproducer that triggers this easily.
> 
> For convenience, here is what AI came up with analysing the log, but
> I would like your take on it. Let me know if you want to see the
> AI proposed fix.
> 
> ----
> 
> btrfs_swap_activate() takes the inode's i_mmap_lock for write very
> early (inode.c:10120) and holds it across the entire function; the
> comment there explains the intent, which is to keep mmap writes from
> racing with the delalloc flush and the extent range lock.
> 
> There are three btrfs_exclop_finish() calls inside that window:
> 
>    fs/btrfs/inode.c:10181   error path, swapfile on a rw subvolume
>                             with an active snapshot
>    fs/btrfs/inode.c:10202   error path, could not lock the snapshot drew
>                             lock
>    fs/btrfs/inode.c:10399   the common "out:" path -- taken on both
>                             success and failure
> 
> and btrfs_exclop_finish() (fs/btrfs/fs.c:224) ends with an unconditional
> sysfs_notify().  So the offending edge is taken on *every* successful
> swapon of a btrfs swap file, not only on an error path.
> 
> The sysfs_notify() itself does nothing but a kernfs lookup plus a poll
> wakeup for userspace watching the "exclusive_operation" attribute.  It
> has no dependency whatsoever on i_mmap_lock, or on the inode at all.
> 
> 
> Both kernfs_rwsem acquisitions in the cycle are read acquisitions
> ({++++}), so the two of them alone cannot deadlock.  A real hang needs a
> third task waiting to take kernfs_rwsem for write, because rwsem
> write-fairness makes a later down_read() block behind a queued writer:
> 
>    T_swapon:  holds i_mmap_lock(write)
>               blocks in down_read(kernfs_rwsem)   [queued behind T_w]
>    T_w:       blocks in down_write(kernfs_rwsem)  [waiting for T_dir]
>               e.g. any sysfs node create/remove -- device hotplug,
>               module load, cgroup or block-device attribute changes
>    T_dir:     holds kernfs_rwsem(read) in kernfs_fop_readdir
>               faults on the user dirent buffer -> mmap_lock
>               -> btrfs_page_mkwrite -> down_read(i_mmap_lock)
>               blocks behind T_swapon's write holder
> 
>    -> three-way deadlock.
> 
> That is a narrow race, which is consistent with this having gone
> unnoticed for years, but every step of it is ordinary system activity.
> The dependency is genuine and worth fixing rather than annotating away.
> 
> Suggested fix is to get the sysfs_notify() out from under i_mmap_lock
> 
> 
> real life log found in our CI:
> ----------------------------
> LOCKDEP_CIRCULAR (suite: tela-distro, case: tests/test_mempig/test_mempig)
> WARNING: possible circular locking dependency detected
> 7.2.0-20260730.rc5.git10.af7a8a7752eb.300.fc44.s390x+debug #1 Not tainted
> ------------------------------------------------------
> swapon/172010 is trying to acquire lock:
> 000002ea80a485a0 (&root->kernfs_rwsem){++++}-{3:3}, at: 
> kernfs_find_and_get_ns+0x3c/0x80
> but task is already holding lock:
> 000002ebc465d270 (&ei->i_mmap_lock){++++}-{3:3}, at: 
> btrfs_swap_activate+0x9a/0x1240
> which lock already depends on the new lock.
> the existing dependency chain (in reverse order) is:
> -> #3 (&ei->i_mmap_lock){++++}-{3:3}:
>         lock_acquire+0x150/0x3f0
>         down_read+0x5a/0x280
>         btrfs_page_mkwrite+0x258/0x870
>         do_page_mkwrite+0x60/0x160
>         do_wp_page+0x128/0x750
>         __handle_mm_fault+0x1be/0x590
>         handle_mm_fault+0xa2/0x370
>         do_exception+0x292/0x590
>         __do_pgm_check+0x168/0x430
>         pgm_check_handler+0x114/0x160
> -> #2 (sb_pagefaults){.+.+}-{0:0}:
>         lock_acquire+0x150/0x3f0
>         percpu_down_read_internal.constprop.0+0x54/0x120
>         btrfs_page_mkwrite+0xa6/0x870
>         do_page_mkwrite+0x60/0x160
>         do_fault+0x132/0x4a0
>         __handle_mm_fault+0x1be/0x590
>         handle_mm_fault+0xa2/0x370
>         do_exception+0x1a0/0x590
>         __do_pgm_check+0x168/0x430
>         pgm_check_handler+0x114/0x160
> -> #1 (&mm->mmap_lock){++++}-{3:3}:
>         lock_acquire+0x150/0x3f0
>         __might_fault+0x7a/0xa0
>         filldir64+0x11c/0x210
>         kernfs_fop_readdir+0x150/0x4c0
>         iterate_dir+0xcc/0x2d0
>         __do_sys_getdents64+0x7a/0x130
>         __do_syscall+0x172/0x750
>         system_call+0x72/0x90
> -> #0 (&root->kernfs_rwsem){++++}-{3:3}:
>         check_prev_add+0x160/0xf40
>         __lock_acquire+0x12aa/0x15a0
>         lock_acquire+0x150/0x3f0
>         down_read+0x5a/0x280
>         kernfs_find_and_get_ns+0x3c/0x80
>         sysfs_notify+0x60/0xc0
>         btrfs_swap_activate+0x83c/0x1240
>         __do_sys_swapon+0x278/0x9c0
>         __do_syscall+0x172/0x750
>         system_call+0x72/0x90
> other info that might help us debug this:
> Chain exists of:
>    &root->kernfs_rwsem --> sb_pagefaults --> &ei->i_mmap_lock
>   Possible unsafe locking scenario:
>         CPU0                    CPU1
>         ----                    ----
>    lock(&ei->i_mmap_lock);
>                                 lock(sb_pagefaults);
>                                 lock(&ei->i_mmap_lock);
>    rlock(&root->kernfs_rwsem);
>   *** DEADLOCK ***
> 2 locks held by swapon/172010:
>   #0: 000002ebc465d3f0 (&sb->s_type->i_mutex_key#20){++++}-{3:3}, at: 
> __do_sys_swapon+0x5be/0x9c0
>   #1: 000002ebc465d270 (&ei->i_mmap_lock){++++}-{3:3}, at: 
> btrfs_swap_activate+0x9a/0x1240
> stack backtrace:
> CPU: 6 UID: 0 PID: 172010 Comm: swapon Not tainted 
> 7.2.0-20260730.rc5.git10.af7a8a7752eb.300.fc44.s390x+debug #1 PREEMPT
> Hardware name: IBM 8561 T01 701 (z/VM 7.4.0)
> Call Trace:
>   [<000003f7d5ab4e3e>] dump_stack_lvl+0xae/0x108
>   [<000003f7d5bbef24>] print_circular_bug+0x1a4/0x230
>   [<000003f7d5bbf13c>] check_noncircular+0x18c/0x1b0
>   [<000003f7d5bc0510>] check_prev_add+0x160/0xf40
>   [<000003f7d5bc408a>] __lock_acquire+0x12aa/0x15a0
>   [<000003f7d5bc44d0>] lock_acquire+0x150/0x3f0
>   [<000003f7d6c447ca>] down_read+0x5a/0x280
>   [<000003f7d60ad61c>] kernfs_find_and_get_ns+0x3c/0x80
>   [<000003f7d60b3b70>] sysfs_notify+0x60/0xc0
>   [<000003f7d635653c>] btrfs_swap_activate+0x83c/0x1240
>   [<000003f7d5f1d268>] __do_sys_swapon+0x278/0x9c0
>   [<000003f7d6c369a2>] __do_syscall+0x172/0x750
>   [<000003f7d6c4baa2>] system_call+0x72/0x90
> INFO: lockdep is turned off.


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: Lockdep circular dependency: btrfs_swap_activate() calls sysfs_notify()
  2026-07-31 11:12 ` Qu Wenruo
@ 2026-07-31 11:14   ` Christian Borntraeger
  2026-08-03  9:07     ` Christian Borntraeger
  0 siblings, 1 reply; 5+ messages in thread
From: Christian Borntraeger @ 2026-07-31 11:14 UTC (permalink / raw)
  To: Qu Wenruo, Chris Mason, David Sterba, Qu Wenruo; +Cc: linux-btrfs, linux-kernel



Am 31.07.26 um 13:12 schrieb Qu Wenruo:
> 
> 
> 在 2026/7/31 20:27, Christian Borntraeger 写道:
>> One or more of the following files ( btrfs-swapon-kernfs-repro.sh ) violates IBM policy and all attachment(s) have been removed from the message.

Oh.


Lets try this:
#!/bin/bash
# Reproducer for the lockdep circular dependency
#
#   &root->kernfs_rwsem --> sb_pagefaults --> &ei->i_mmap_lock
#
# btrfs_swap_activate() calls btrfs_exclop_finish() -> sysfs_notify() ->
# kernfs_rwsem while holding the inode's i_mmap_lock for write, while
# kernfs_fop_readdir() holds kernfs_rwsem across filldir64() (-> mmap_lock)
# and a btrfs write fault goes mmap_lock -> sb_pagefaults -> i_mmap_lock.
#
#
# Must be run as root, on a kernel with CONFIG_PROVE_LOCKING and
# CONFIG_DEBUG_ATOMIC_SLEEP.  Run it on an unpatched kernel to see the splat,
# and on a patched one to confirm it is gone.
#
# Usage: ./btrfs-swapon-kernfs-repro.sh [existing-btrfs-mountpoint]
#
# With no argument a 512M loopback btrfs image is created under /var/tmp and
# removed again at the end.  With an argument, that existing single-device
# btrfs mount is used and only the test files inside it are removed.
#
# Exit codes: 0 = no splat (expected with the fix), 1 = reproduced,
#             2 = inconclusive (setup problem, or lockdep already off).

set -u

IMG=""
LOOP=""
MNT=""
OWN_FS=0
SWAPFILE=""
SWAP_ON=0
SWAPON_OK=0

msg()  { printf '[repro] %s\n' "$*"; }
fail() { printf '[repro] ERROR: %s\n' "$*" >&2; exit 2; }

cleanup() {
	set +e
	[ "$SWAP_ON" = 1 ] && { msg "swapoff $SWAPFILE"; swapoff "$SWAPFILE"; }
	if [ "$OWN_FS" = 1 ]; then
		[ -n "$MNT" ]  && umount "$MNT" 2>/dev/null && rmdir "$MNT"
		[ -n "$LOOP" ] && losetup -d "$LOOP" 2>/dev/null
		[ -n "$IMG" ]  && rm -f "$IMG"
	elif [ -n "$MNT" ]; then
		# Only ever the two files this script created, never a bare path.
		[ -n "$SWAPFILE" ] && rm -f "$SWAPFILE"
		rm -f "$MNT/lockdep-mmap.dat"
	fi
}
trap cleanup EXIT

# ---------------------------------------------------------------- checks ---

[ "$(id -u)" = 0 ] || fail "must be run as root"

for t in mkfs.btrfs losetup mkswap swapon swapoff chattr dmesg \
	 truncate stat find dd sync mount umount; do
	command -v "$t" >/dev/null || fail "missing tool: $t"
done

[ -e /proc/lockdep_stats ] || fail \
	"no /proc/lockdep_stats -- kernel lacks CONFIG_PROVE_LOCKING, nothing to see"

# A previous splat disables lockdep for the rest of the boot ("INFO: lockdep
# is turned off").  Nothing will be reported after that, so bail out early
# rather than report a bogus PASS.
if grep -qE '^ *debug_locks: *0' /proc/lockdep_stats; then
	fail "lockdep is already turned off (earlier splat this boot) -- reboot first"
fi

# Edge #1 of the cycle needs the might_fault() annotation in filldir64().
KCONF=""
for c in /proc/config.gz "/boot/config-$(uname -r)"; do
	[ -e "$c" ] && { KCONF="$c"; break; }
done
if [ -n "$KCONF" ]; then
	if [ "${KCONF##*.}" = gz ]; then RDCONF="zcat"; else RDCONF="cat"; fi
	$RDCONF "$KCONF" | grep -q '^CONFIG_DEBUG_ATOMIC_SLEEP=y' || msg \
		"WARNING: CONFIG_DEBUG_ATOMIC_SLEEP is not set -- filldir64() will not
          annotate mmap_lock and the cycle cannot be detected"
else
	msg "note: could not find the kernel config, not checking DEBUG_ATOMIC_SLEEP"
fi

MMAP_HELPER=""
if command -v python3 >/dev/null; then
	MMAP_HELPER=python3
elif command -v cc >/dev/null; then
	MMAP_HELPER=cc
else
	fail "need python3 or cc to do the mmap write fault"
fi

msg "kernel $(uname -r), lockdep active"

# Cursor for the verdict.  Taken before the first step, not just before
# swapon: if some earlier boot activity already registered the
# i_mmap_lock -> kernfs_rwsem edge, the cycle is completed by the mmap or
# the readdir step below instead, and the splat has to be caught there too.
DMESG_LINES=$(dmesg | wc -l)

# ------------------------------------------------------------ test btrfs ---

if [ $# -ge 1 ]; then
	MNT="$1"
	[ -d "$MNT" ] || fail "$MNT is not a directory"
	[ "$(stat -f -c %T "$MNT")" = btrfs ] || fail "$MNT is not btrfs"
	msg "using existing btrfs at $MNT"
else
	OWN_FS=1
	IMG=$(mktemp /var/tmp/btrfs-swapon-repro.XXXXXX.img) || fail "mktemp failed"
	MNT=$(mktemp -d /var/tmp/btrfs-swapon-repro.XXXXXX.mnt) || fail "mktemp failed"
	msg "creating 512M btrfs image $IMG"
	truncate -s 512M "$IMG"                     || fail "truncate failed"
	LOOP=$(losetup --find --show "$IMG")        || fail "losetup failed"
	mkfs.btrfs -q -f "$LOOP"                    || fail "mkfs.btrfs failed"
	# No compression: a compressed swap file is rejected by btrfs.
	mount -o compress=no "$LOOP" "$MNT"         || fail "mount failed"
	msg "mounted $LOOP on $MNT"
fi

# ------------------------------------ edges #2/#3: mmap write fault on btrfs --
# handle_mm_fault -> do_page_mkwrite -> btrfs_page_mkwrite
#   -> sb_start_pagefault()            [sb_pagefaults]
#   -> down_read(&BTRFS_I(inode)->i_mmap_lock)
# Both the do_fault (first touch is a write) and the do_wp_page (read first,
# then write) variants are exercised.

MMAPFILE="$MNT/lockdep-mmap.dat"
dd if=/dev/zero of="$MMAPFILE" bs=4096 count=64 status=none || fail "dd failed"
sync

msg "dirtying a btrfs file mapping (mmap_lock -> sb_pagefaults -> i_mmap_lock)"
if [ "$MMAP_HELPER" = python3 ]; then
	python3 - "$MMAPFILE" <<-'EOF' || fail "mmap helper failed"
		import mmap, os, sys
		fd = os.open(sys.argv[1], os.O_RDWR)
		m = mmap.mmap(fd, 4096 * 64)
		m[0:8] = b"lockdep!"          # write fault on an absent page (do_fault)
		_ = m[8192]                   # populate clean ...
		m[8192:8200] = b"lockdep!"    # ... then dirty it (do_wp_page)
		m.flush()
		m.close()
		os.close(fd)
	EOF
else
	HELPER=$(mktemp -d)
	cat > "$HELPER/mw.c" <<-'EOF'
		#include <fcntl.h>
		#include <stdio.h>
		#include <sys/mman.h>
		#include <unistd.h>
		int main(int argc, char **argv)
		{
			size_t len = 4096 * 64;
			char *p;
			int fd = open(argv[1], O_RDWR);

			if (fd < 0)
				return perror("open"), 1;
			p = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
			if (p == MAP_FAILED)
				return perror("mmap"), 1;
			p[0] = 'x';                  /* do_fault */
			(void)*(volatile char *)(p + 8192);
			p[8192] = 'x';               /* do_wp_page */
			msync(p, len, MS_SYNC);
			munmap(p, len);
			close(fd);
			return 0;
		}
	EOF
	cc -O2 -o "$HELPER/mw" "$HELPER/mw.c" || fail "compiling the mmap helper failed"
	"$HELPER/mw" "$MMAPFILE" || fail "mmap helper failed"
	rm -rf "$HELPER"
fi

# ------------------------------------------- edge #1: kernfs readdir + fault --
# kernfs_fop_readdir() holds kernfs_rwsem for read across the whole emit loop
# and calls filldir64() -> __might_fault() -> mmap_lock.  Any getdents on a
# sysfs directory registers it.

msg "reading sysfs directories (kernfs_rwsem -> mmap_lock)"
for d in /sys/fs/btrfs /sys/class/net /sys/block /sys/devices/system/cpu; do
	[ -d "$d" ] && ls -fU "$d" >/dev/null 2>&1
done
find /sys/fs/btrfs -maxdepth 3 >/dev/null 2>&1

# ------------------------------------------- edge #0: swapon on btrfs (bang) --
# btrfs_swap_activate() holds i_mmap_lock for write across
# btrfs_exclop_finish() -> sysfs_notify() -> kernfs_find_and_get_ns()
#   -> down_read(&root->kernfs_rwsem)
#
# The swap file has to satisfy every btrfs_swap_activate() precondition:
# NODATACOW and NODATASUM (both from chattr +C on a still empty file), not
# compressed, no holes, no inline extent, not shared.  fallocate() is not
# used on purpose -- the extents are written out for real.

SWAPFILE="$MNT/lockdep-swapfile"
msg "creating swap file $SWAPFILE"
rm -f "$SWAPFILE"
touch "$SWAPFILE"                 || fail "touch failed"
chattr +C "$SWAPFILE"             || fail "chattr +C failed (needs NODATACOW)"
chmod 600 "$SWAPFILE"
dd if=/dev/zero of="$SWAPFILE" bs=1M count=64 status=none || fail "dd failed"
sync
mkswap "$SWAPFILE" >/dev/null     || fail "mkswap failed"

msg "swapon -- this is the call that closes the cycle"
if swapon -p 0 "$SWAPFILE"; then
	SWAP_ON=1
	SWAPON_OK=1
	msg "swapon succeeded"
else
	msg "swapon FAILED -- check the btrfs_warn above for which precondition"
	msg "(the lockdep edge is taken on the error paths too, so keep reading)"
fi

sleep 1

# ------------------------------------------------------------------ verdict --

NEW=$(dmesg | tail -n +$((DMESG_LINES + 1)))

if printf '%s\n' "$NEW" | grep -q "possible circular locking dependency"; then
	printf '%s\n' "$NEW" | sed -n '/possible circular locking dependency/,$p'
	if printf '%s\n' "$NEW" | grep -q "kernfs_rwsem"; then
		msg "REPRODUCED: the expected cycle, kernfs_rwsem is in the chain"
		exit 1
	fi
	msg "INCONCLUSIVE: a circular dependency was reported, but without"
	msg "kernfs_rwsem -- that is a different bug, compare against more.txt"
	exit 2
fi

msg "no lockdep splat"
if grep -qE '^ *debug_locks: *0' /proc/lockdep_stats; then
	msg "INCONCLUSIVE: lockdep turned itself off during the run"
	exit 2
fi
if [ "$SWAPON_OK" = 0 ]; then
	msg "INCONCLUSIVE: swapon did not get far enough, fix that first"
	exit 2
fi
msg "PASS: the i_mmap_lock -> kernfs_rwsem edge was not taken"
msg "(expected on a kernel carrying btrfs-swapon-kernfs-fix.patch)"
exit 0

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: Lockdep circular dependency: btrfs_swap_activate() calls sysfs_notify()
  2026-07-31 11:14   ` Christian Borntraeger
@ 2026-08-03  9:07     ` Christian Borntraeger
  2026-08-03  9:44       ` Qu Wenruo
  0 siblings, 1 reply; 5+ messages in thread
From: Christian Borntraeger @ 2026-08-03  9:07 UTC (permalink / raw)
  To: Qu Wenruo, Chris Mason, David Sterba, Qu Wenruo; +Cc: linux-btrfs, linux-kernel



Am 31.07.26 um 13:14 schrieb Christian Borntraeger:
> 
> 
> Am 31.07.26 um 13:12 schrieb Qu Wenruo:
>>
>>
>> 在 2026/7/31 20:27, Christian Borntraeger 写道:
>>> One or more of the following files ( btrfs-swapon-kernfs-repro.sh ) violates IBM policy and all attachment(s) have been removed from the message.
> 
> Oh.
> 
> 
> Lets try this:
> #!/bin/bash
> # Reproducer for the lockdep circular dependency
> #
Can you reproduce the lockdep message?

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: Lockdep circular dependency: btrfs_swap_activate() calls sysfs_notify()
  2026-08-03  9:07     ` Christian Borntraeger
@ 2026-08-03  9:44       ` Qu Wenruo
  0 siblings, 0 replies; 5+ messages in thread
From: Qu Wenruo @ 2026-08-03  9:44 UTC (permalink / raw)
  To: Christian Borntraeger, Chris Mason, David Sterba, Qu Wenruo
  Cc: linux-btrfs, linux-kernel



在 2026/8/3 18:37, Christian Borntraeger 写道:
> 
> 
> Am 31.07.26 um 13:14 schrieb Christian Borntraeger:
>>
>>
>> Am 31.07.26 um 13:12 schrieb Qu Wenruo:
>>>
>>>
>>> 在 2026/7/31 20:27, Christian Borntraeger 写道:
>>>> One or more of the following files ( btrfs-swapon-kernfs-repro.sh ) 
>>>> violates IBM policy and all attachment(s) have been removed from the 
>>>> message.
>>
>> Oh.
>>
>>
>> Lets try this:
>> #!/bin/bash
>> # Reproducer for the lockdep circular dependency
>> #
> Can you reproduce the lockdep message?
> 

Sorry, I am on vacation since the last reply, until Wednesday.

However the script seems to have several minor problems that got 
rejected by my VM:

- EOF not matching
   The involved python scripts expects "EOF" without any other prefix,
   but the script is using "\tEOF", which failed to end the file.

   My bash version is 5.3.3, not sure if this is version specific.

- Bad indent rejected by python3
   The python version is 3.13.7, which rejects any weird indent, so
   it fails like:

     File "<stdin>", line 1
       import mmap, os, sys
   IndentationError: unexpected indent

Even with all those minor problems fixed, it still didn't trigger on my VM.

I tried 128 runs, the script is triggered for a newly created btrfs in a 
loop, no lockdep warning triggered, all the same successful runs:

[repro] kernel 7.2.0-rc5-custom+, lockdep active
[repro] using existing btrfs at /mnt/btrfs/
[repro] dirtying a btrfs file mapping (mmap_lock -> sb_pagefaults -> 
i_mmap_lock)
[repro] reading sysfs directories (kernfs_rwsem -> mmap_lock)
[repro] creating swap file /mnt/btrfs//lockdep-swapfile
[repro] swapon -- this is the call that closes the cycle
[repro] swapon succeeded
[repro] no lockdep splat
[repro] PASS: the i_mmap_lock -> kernfs_rwsem edge was not taken
[repro] (expected on a kernel carrying btrfs-swapon-kernfs-fix.patch)
[repro] swapoff /mnt/btrfs//lockdep-swapfile

The kernel has LOCKDEP and DEBUG_ATOMIC_SLEEP set.

Although this is on a newer branch, based on btrfs' for-next.

Thus not sure if there is anything new, or if it's really too timing 
specific.

You may want to send out your fix for extra reviews, other developers 
may provide extra review on the fix.

Thanks,
Qu

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-08-03  9:44 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-07-31 10:57 Lockdep circular dependency: btrfs_swap_activate() calls sysfs_notify() Christian Borntraeger
2026-07-31 11:12 ` Qu Wenruo
2026-07-31 11:14   ` Christian Borntraeger
2026-08-03  9:07     ` Christian Borntraeger
2026-08-03  9:44       ` Qu Wenruo

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®