From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-pj1-f50.google.com (mail-pj1-f50.google.com [209.85.216.50]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4FE982DA74C for ; Thu, 11 Jun 2026 18:06:00 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.50 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1781201161; cv=none; b=MbblnlOWfgZKfz1EpV5TzRdHkplSXxioZO5f8v/lDHKZemWyUBjbwhdDHwJb62NTFnYXEh2ADKhvsXlVW6cpqGPfnJM4X1Q/mhXAa0Zq2onGS/jgJ8irCds0DphpXWdOh4sBDluRj3LB0UWj+v+OlfOZT/+uGO6i47UKCL5iPCA= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1781201161; c=relaxed/simple; bh=pUww7H2bjQMlRBPUphl7A7bqahuGoP+VGc5oETs7tsQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=WEUasNRO3h6I83sIySSQTVSy7wUJE0c/FlPE1BAGMJcIdk5If3pUQtlqSA7Qa74BST9De6GZUvcw9eIQaWFamVpU7zILstXZRXNI0jRD1Vg0DOk65mDS3khLDm8Bf0BzLbO0h6KLza2ZBCYsNwS+/DRhNxPFTkV0OhUNADg504U= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=bdebZCMs; arc=none smtp.client-ip=209.85.216.50 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="bdebZCMs" Received: by mail-pj1-f50.google.com with SMTP id 98e67ed59e1d1-36b9033d230so162944a91.1 for ; Thu, 11 Jun 2026 11:06:00 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1781201160; x=1781805960; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=v1j3eDvP8UchTgRE6DH/XADnZJh8JMdhP22aHpf9sOg=; b=bdebZCMsBTyacqNXLFQ8L9SL77p3ZximFZulorgN/gQ4pLM17zpfxmlufwQE7Y304n aOMEjs4qBOCGRZN7kL8y666+z5GdXOzb0wUPwvTw5Oq+5G4BWY6jZZE9Rx78hlLXA3Xm MaJrnMnozqHhQ/Yb8UXRo5l9VqvVlcoeu4+rXwz/An2ctFpPc/2OR5/QpWWtLgFB6Q6Z Gy18he7U2RkhzEAsXjPoVSCIhJKAmdvjGSBmfjh288+iJTIoZr5jw+mQ+G9BGlBhlo/q i1H0FMI7YZobu5jL602c59DZvGXZafjTbwhjb+ZFLVV0lSuOg1X4JVZ5Y8AqJ8s6vHHl SZXw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1781201160; x=1781805960; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=v1j3eDvP8UchTgRE6DH/XADnZJh8JMdhP22aHpf9sOg=; b=D05mtnDAe4Qb8ThfhvajAccICUpjwGZ/jknb6ZPB2UZQjEJ/lZoLZnQXjYrQ1Rap/e tMblmgw6CykcCfYYo5CZC0i6em6jxK+l93tZ2cuYiF2NidHks1d7aDxmVjv+w7pHLsg4 nWF8cyE7JL7xHMj5BK1Z0rXiL1zTAy5H3XVmhBfk0fFfTwP5L2lmgfLDjFz4ZSlS6FLE fHlTPB+SdkA1zeonnUckE2NXMOp0CQl7diIs9xJWWixJaUNQnvEMwTEQr1PbslC5e1GO 5oxRG9ibeAHcVlO+W4wJqKFotBamm1y8Wv9JKDVYCHpg4M5qboWe3WG5trr7KxQtZXuu 5EPQ== X-Forwarded-Encrypted: i=1; AFNElJ+K3Kgxpgh1caJL+R5wlkQ2ux7XkmUB9z5eU5MZDHZmmYPGGLz4o7LQJXqqrM8tMZtMlyizqSUojnam59I=@vger.kernel.org X-Gm-Message-State: AOJu0YxrASF/C3dbIKgfjsfb46FMyONdBN5KC8oAsBh/+Qtf68jNGUSK x3UzZMwgfRb8tRX2LXdtUVOoeqKbkYCbJUVNNw1GZ0qYV9ZYPCIhCYDBkpgRcoiM X-Gm-Gg: Acq92OEtuF12PTCjC0BfFJiC5/CSM7qmoC3eSrMqwlW8jQ1KPcYZs9AK6uVwbYdPpoA KrDGi/UuW5s4+h5ncg7cVysqQx2CKTqlinI0FUk7qbQYfrJBpMtt5ezlIflAZqEABOVEsRIeomL qzGlF63/2hgVpEda0NyL2ClMmfQrMn/ScKNShBG3YSpzPlwJ11dJEBNQHItBu5YEV7bKDp5lN10 LSOlEtCkKjNV7/O7S+1ggZ2J1bywSZPrQhmOMuz5nyBcMLgc+Z0+pxS1fOjSnp9YCXBNVVa7H6w YCOgxnkMsnlJlanN8QBpWGPt039zftaIuw1v4CrMmi+D7PgfvtVeBE2UKgOmPt+lqeQEm2+O9o7 2640ENl1zFZl/2TLolb0hARfp8ivgO6JEaqCQv58uY+MqrON69UDQRe7IbzspVWJ3BcWQqpbiqK fBJRDsJAouVU98B3SJGb7lFZ5nKH2e/8PIQBJOF8Ph0gvfcVnnNcZCoWSMOjItyBZ23170S9a/3 YjkiRxHG6xnIa/3HUfj/MD5RyBQweG/tAqjd8Tm6Y2xlxEpDxqk0QGL4RY/nIbxQ6i0V9yx7im8 bcPEJMGj+DZdipDGEtUlaVGIS+fRAUSvlSLd X-Received: by 2002:a17:90b:3f43:b0:36a:8254:8eb1 with SMTP id 98e67ed59e1d1-3780375948emr3053052a91.6.1781201159504; Thu, 11 Jun 2026 11:05:59 -0700 (PDT) Received: from cs-1047136853211-default.asia-southeast1-a.c.d33bddc1d573818c7-tp.internal (86.147.142.34.bc.googleusercontent.com. [34.142.147.86]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-37752b5a8ecsm4131788a91.7.2026.06.11.11.05.57 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 11 Jun 2026 11:05:59 -0700 (PDT) From: Aditya Srivastava To: Carlos Maiolino Cc: Christoph Hellwig , linux-xfs@vger.kernel.org, linux-kernel@vger.kernel.org, Aditya Prakash Srivastava Subject: [PATCH v2] xfs: prevent close() from hanging on frozen filesystems Date: Thu, 11 Jun 2026 18:05:32 +0000 Message-ID: <20260611180532.1725-1-aditya.ansh182@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit From: Aditya Prakash Srivastava When a file with active speculative post-EOF preallocations is closed, xfs_file_release() synchronously triggers xfs_free_eofblocks() to clean them up. This requires allocating a write transaction (xfs_trans_alloc), which blocks indefinitely if the filesystem is currently frozen or in the process of freezing, as it waits to acquire the superblock's write lock via sb_start_intwrite(). As a result, a close() system call on a read-write file descriptor can hang indefinitely in percpu_rwsem_wait() until the filesystem is thawed, even if the file is closed by a non-writer process or after all writing activity has already ceased. This issue has been seen across multiple downstream environments and has a long history of causing severe system disruption. For example: - Downstream Red Hat Bugzilla 1474726 (dating back to 2017) details complete system hangs during system backups when rsync and fsfreeze are used. Even seemingly harmless read-only commands like 'cat /var/log/messages' would hang on close() in __sb_start_write via xfs_free_eofblocks, requiring a hard reboot. - Downstream LeApp integration test scenarios (e.g. systemd-rsync migration checks) consistently hit this hang when trying to freeze the system. Historically, XFS maintainers dismissed this behavior as NOTABUG, claiming that close() is not a read-only operation and is expected to block since it allocates write transactions. However, this behavior is highly disruptive. User-space applications view close() as a resource reclamation system call, not a write operation, and do not expect it to block. Hanging on close() frequently triggers container healthcheck failures, systemd service timeouts, and cluster failover cascades. Additionally, no other major Linux filesystem (such as ext4 or btrfs) synchronously allocates write transactions during close() system calls, making this hang a highly unexpected and disruptive behavior unique to XFS. To fix this properly and avoid any potential race conditions where a freeze might come in immediately after a writable check (such as xfs_fs_writable), we introduce a new transaction allocation flag: XFS_TRANS_TRYLOCK. When XFS_TRANS_TRYLOCK is set, __xfs_trans_alloc() attempts to obtain freeze protection using sb_start_intwrite_trylock() instead of blocking on sb_start_intwrite(). If the trylock fails, the allocation is aborted gracefully, freeing the newly allocated transaction handle and returning -EAGAIN. We then extend xfs_free_eofblocks() to accept transaction allocation flags, and pass XFS_TRANS_TRYLOCK during xfs_file_release(). If the call returns -EAGAIN, we cleanly clear the XFS_EOFBLOCKS_RELEASED flag so that subsequent releases or the background blockgc garbage collector can get another chance to clean up these speculative preallocations once the filesystem thaws. This approach is safe, race-free, and ensures close() never blocks on frozen filesystems. As requested, I have added a GPLv2-compatible license to the C reproducer provided below, and I will be working on wiring up this reproducer into the standard xfstests suite in the near future. A simple GPLv2-licensed C reproducer demonstrating the hang (compile with -pthread): /* * GPLv2-compatible XFS freeze close() hang reproducer. * Copyright (c) 2026 Aditya Prakash Srivastava. All Rights Reserved. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include volatile int close_started = 0; volatile int close_completed = 0; void *close_thread(void *arg) { int fd = *(int *)arg; close_started = 1; close(fd); close_completed = 1; return NULL; } int main(int argc, char *argv[]) { struct statfs sfs; statfs(argv[1], &sfs); if (sfs.f_type != 0x58465342) return 1; int freeze_fd = open(dirname(strdup(argv[1])), O_RDONLY); int write_fd = open(argv[1], O_WRONLY | O_CREAT | O_TRUNC, 0644); char buf[65536] = {0}; for (int i = 0; i < 320; i++) write(write_fd, buf, sizeof(buf)); ioctl(freeze_fd, FIFREEZE, 0); pthread_t thread; pthread_create(&thread, NULL, close_thread, &write_fd); while (!close_started) usleep(1000); usleep(1000000); // Wait 1s if (!close_completed) printf("SUCCESS: close() hung!\\n"); ioctl(freeze_fd, FITHAW, 0); pthread_join(thread, NULL); unlink(argv[1]); return 0; } Link: https://bugzilla.kernel.org/show_bug.cgi?id=205833 Link: https://bugzilla.redhat.com/show_bug.cgi?id=1474726 Suggested-by: Christoph Hellwig Signed-off-by: Aditya Prakash Srivastava --- Changes since v1: - Drop the racy xfs_fs_writable() check from the previous version. - Introduce the new XFS_TRANS_TRYLOCK transaction flag. - Attempt non-blocking superblock locking via sb_start_intwrite_trylock() when XFS_TRANS_TRYLOCK is set, returning -EAGAIN on failure. - Update xfs_free_eofblocks() to accept flags, allowing xfs_file_release() to handle -EAGAIN and gracefully retry/defer post-thaw. - Add GPLv2-compatible licensing to the C reproducer. fs/xfs/libxfs/xfs_shared.h | 3 +++ fs/xfs/xfs_bmap_util.c | 9 +++++---- fs/xfs/xfs_bmap_util.h | 2 +- fs/xfs/xfs_file.c | 13 +++++++++++-- fs/xfs/xfs_icache.c | 2 +- fs/xfs/xfs_inode.c | 2 +- fs/xfs/xfs_trans.c | 18 ++++++++++++++++-- 7 files changed, 38 insertions(+), 11 deletions(-) diff --git a/fs/xfs/libxfs/xfs_shared.h b/fs/xfs/libxfs/xfs_shared.h index b1e0d9bc1f7d..6b2a1026ff60 100644 --- a/fs/xfs/libxfs/xfs_shared.h +++ b/fs/xfs/libxfs/xfs_shared.h @@ -164,6 +164,9 @@ void xfs_log_get_max_trans_res(struct xfs_mount *mp, /* Transaction has locked the rtbitmap and rtsum inodes */ #define XFS_TRANS_RTBITMAP_LOCKED (1u << 9) +/* Try lock filesystem superblock for freeze protection */ +#define XFS_TRANS_TRYLOCK (1u << 10) + /* * Field values for xfs_trans_mod_sb. */ diff --git a/fs/xfs/xfs_bmap_util.c b/fs/xfs/xfs_bmap_util.c index 0ab00615f1ad..56c7919bf1e5 100644 --- a/fs/xfs/xfs_bmap_util.c +++ b/fs/xfs/xfs_bmap_util.c @@ -574,7 +574,8 @@ xfs_can_free_eofblocks( */ int xfs_free_eofblocks( - struct xfs_inode *ip) + struct xfs_inode *ip, + uint flags) { struct xfs_trans *tp; struct xfs_mount *mp = ip->i_mount; @@ -604,9 +605,9 @@ xfs_free_eofblocks( return 0; } - error = xfs_trans_alloc(mp, &M_RES(mp)->tr_itruncate, 0, 0, 0, &tp); + error = xfs_trans_alloc(mp, &M_RES(mp)->tr_itruncate, 0, 0, flags, &tp); if (error) { - ASSERT(xfs_is_shutdown(mp)); + ASSERT(error == -EAGAIN || xfs_is_shutdown(mp)); return error; } @@ -928,7 +929,7 @@ xfs_prepare_shift( * into the accessible region of the file. */ if (xfs_can_free_eofblocks(ip)) { - error = xfs_free_eofblocks(ip); + error = xfs_free_eofblocks(ip, 0); if (error) return error; } diff --git a/fs/xfs/xfs_bmap_util.h b/fs/xfs/xfs_bmap_util.h index c477b3361630..227371579205 100644 --- a/fs/xfs/xfs_bmap_util.h +++ b/fs/xfs/xfs_bmap_util.h @@ -66,7 +66,7 @@ int xfs_insert_file_space(struct xfs_inode *, xfs_off_t offset, /* EOF block manipulation functions */ bool xfs_can_free_eofblocks(struct xfs_inode *ip); -int xfs_free_eofblocks(struct xfs_inode *ip); +int xfs_free_eofblocks(struct xfs_inode *ip, uint flags); int xfs_swap_extents(struct xfs_inode *ip, struct xfs_inode *tip, struct xfs_swapext *sx); diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c index 845a97c9b063..16aba428854e 100644 --- a/fs/xfs/xfs_file.c +++ b/fs/xfs/xfs_file.c @@ -1807,8 +1807,17 @@ xfs_file_release( if (!xfs_iflags_test(ip, XFS_EOFBLOCKS_RELEASED) && xfs_ilock_nowait(ip, XFS_IOLOCK_EXCL)) { if (xfs_can_free_eofblocks(ip) && - !xfs_iflags_test_and_set(ip, XFS_EOFBLOCKS_RELEASED)) - xfs_free_eofblocks(ip); + !xfs_iflags_test_and_set(ip, XFS_EOFBLOCKS_RELEASED)) { + int error = xfs_free_eofblocks(ip, XFS_TRANS_TRYLOCK); + + /* + * If transaction allocation fails due to a frozen/freezing + * filesystem, clear the released flag so that subsequent + * releases or background blockgc can retry post-thaw. + */ + if (error == -EAGAIN) + xfs_iflags_clear(ip, XFS_EOFBLOCKS_RELEASED); + } xfs_iunlock(ip, XFS_IOLOCK_EXCL); } diff --git a/fs/xfs/xfs_icache.c b/fs/xfs/xfs_icache.c index 2040a9292ee6..c575b4acb24c 100644 --- a/fs/xfs/xfs_icache.c +++ b/fs/xfs/xfs_icache.c @@ -1259,7 +1259,7 @@ xfs_inode_free_eofblocks( *lockflags |= XFS_IOLOCK_EXCL; if (xfs_can_free_eofblocks(ip)) - return xfs_free_eofblocks(ip); + return xfs_free_eofblocks(ip, 0); /* inode could be preallocated */ trace_xfs_inode_free_eofblocks_invalid(ip); diff --git a/fs/xfs/xfs_inode.c b/fs/xfs/xfs_inode.c index 9978ac1422fc..1c75f51af4d7 100644 --- a/fs/xfs/xfs_inode.c +++ b/fs/xfs/xfs_inode.c @@ -1423,7 +1423,7 @@ xfs_inactive( * reference to the inode at this point anyways. */ if (xfs_can_free_eofblocks(ip)) - error = xfs_free_eofblocks(ip); + error = xfs_free_eofblocks(ip, 0); goto out; } diff --git a/fs/xfs/xfs_trans.c b/fs/xfs/xfs_trans.c index 148cc32449c1..b214ef5c28c1 100644 --- a/fs/xfs/xfs_trans.c +++ b/fs/xfs/xfs_trans.c @@ -218,8 +218,20 @@ __xfs_trans_alloc( ASSERT(!(flags & XFS_TRANS_RES_FDBLKS) || xfs_has_lazysbcount(mp)); tp = kmem_cache_zalloc(xfs_trans_cache, GFP_KERNEL | __GFP_NOFAIL); - if (!(flags & XFS_TRANS_NO_WRITECOUNT)) - sb_start_intwrite(mp->m_super); + if (!(flags & XFS_TRANS_NO_WRITECOUNT)) { + /* + * If TRYLOCK is specified, attempt a non-blocking lock to + * avoid blocking indefinitely on frozen/freezing filesystems. + */ + if (flags & XFS_TRANS_TRYLOCK) { + if (!sb_start_intwrite_trylock(mp->m_super)) { + kmem_cache_free(xfs_trans_cache, tp); + return ERR_PTR(-EAGAIN); + } + } else { + sb_start_intwrite(mp->m_super); + } + } xfs_trans_set_context(tp); tp->t_flags = flags; tp->t_mountp = mp; @@ -252,6 +264,8 @@ xfs_trans_alloc( */ retry: tp = __xfs_trans_alloc(mp, flags); + if (IS_ERR(tp)) + return PTR_ERR(tp); WARN_ON(mp->m_super->s_writers.frozen == SB_FREEZE_COMPLETE); error = xfs_trans_reserve(tp, resp, blocks, rtextents); if (error == -ENOSPC && want_retry) { -- 2.47.3