Re: [PATCH] xfs/842: test close() deadlocks on frozen filesystems

Zorro Lang <[email protected]>
Newsgroups org.kernel.vger.fstests,org.kernel.vger.linux-xfs
Message-ID <ai7oBqnd2dy32Ew9@zlang-mailbox>
On Fri, Jun 12, 2026 at 12:37:42PM +0000, Aditya Srivastava wrote:
> From: Aditya Prakash Srivastava <[email protected]>
> 
> When a file with active speculative post-EOF preallocations is closed,
> XFS synchronously attempts to free them via xfs_free_eofblocks().
> This requires allocating a write transaction, which blocks indefinitely
> if the filesystem is frozen, causing close() to hang.
> 
> Add a regression test using a helper C program to verify that close()
> returns cleanly (with -EAGAIN if using XFS_TRANS_WRITECOUNT_TRYLOCK)
> without blocking the close system call.
> 
> This is a regression test for the corresponding kernel patch:
> "xfs: prevent close() from hanging on frozen filesystems"
>
> On a successfully patched kernel, the close system call returns cleanly
> and the test helper outputs 'close() completed immediately!', matching
> the golden output. On an unpatched kernel, the close system call blocks,
> the helper times out and outputs 'close() hung under freeze!', cleanly
> flagging the deadlock to fstests.
> 
> Link: https://bugzilla.kernel.org/show_bug.cgi?id=205833
> Link: https://bugzilla.redhat.com/show_bug.cgi?id=1474726
> Signed-off-by: Aditya Prakash Srivastava <[email protected]>
> ---
>  src/Makefile                 |   1 +
>  src/xfs_freeze_close_repro.c | 127 +++++++++++++++++++++++++++++++++++
>  tests/xfs/842                |  32 +++++++++
>  tests/xfs/842.out            |   2 +
>  4 files changed, 162 insertions(+)
>  create mode 100644 src/xfs_freeze_close_repro.c
>  create mode 100755 tests/xfs/842
>  create mode 100644 tests/xfs/842.out
> 
> diff --git a/src/Makefile b/src/Makefile
> index 31ac43b2..9553e7c9 100644
> --- a/src/Makefile
> +++ b/src/Makefile
> @@ -68,6 +68,7 @@ LCFLAGS += -DHAVE_FILE_GETATTR
>  endif
>  
>  ifeq ($(PKG_PLATFORM),linux)
> +LINUX_TARGETS += xfs_freeze_close_repro

Please add xfs_freeze_close_repro into above/original "LINUX_TARGETS = ..."
directly.

>  TARGETS += $(LINUX_TARGETS)
>  endif
>  
> diff --git a/src/xfs_freeze_close_repro.c b/src/xfs_freeze_close_repro.c
> new file mode 100644
> index 00000000..075f2d18
> --- /dev/null
> +++ b/src/xfs_freeze_close_repro.c
> @@ -0,0 +1,127 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2026 Aditya Prakash Srivastava. All Rights Reserved.
> + *
> + * xfstests helper to reproduce close() hang on frozen XFS filesystems.
> + */
> +#define _GNU_SOURCE

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <unistd.h>
> +#include <fcntl.h>
> +#include <pthread.h>
> +#include <sys/ioctl.h>
> +#include <sys/vfs.h>
> +#include <linux/fs.h>
> +#include <libgen.h>
> +
> +int close_started;
> +int close_completed;

volatile ?

> +
> +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;
> +	char *dir_buf;
> +	char *parent_dir;
> +	int freeze_fd;
> +	int write_fd;
> +	char buf[65536];
> +	pthread_t thread;
> +	int hung = 1;
> +	int i;
> +
> +	if (argc < 2) {
> +		fprintf(stderr, "Usage: %s <file_on_xfs>\n", argv[0]);
> +		return 1;
> +	}
> +
> +	if (statfs(argv[1], &sfs) < 0) {
> +		dir_buf = strdup(argv[1]);
> +		parent_dir = dirname(dir_buf);
> +		if (statfs(parent_dir, &sfs) < 0) {
> +			perror("statfs");
> +			free(dir_buf);
> +			return 1;
> +		}
> +		free(dir_buf);
> +	}
> +	if (sfs.f_type != 0x58465342) {
> +		fprintf(stderr, "Not on XFS\n");
> +		return 1;
> +	}

Why limit this program run on XFS? There're not XFS specific test steps.

> +
> +	dir_buf = strdup(argv[1]);
> +	freeze_fd = open(dirname(dir_buf), O_RDONLY);
> +	if (freeze_fd < 0) {
> +		perror("open dir");
> +		free(dir_buf);
> +		return 1;
> +	}
> +	free(dir_buf);
> +
> +	write_fd = open(argv[1], O_WRONLY | O_CREAT | O_TRUNC, 0644);
> +	if (write_fd < 0) {
> +		perror("open file");
> +		close(freeze_fd);
> +		return 1;
> +	}
> +
> +	memset(buf, 'A', sizeof(buf));
> +	for (i = 0; i < 320; i++) {
> +		if (write(write_fd, buf, sizeof(buf)) < 0) {
> +			perror("write");
> +			close(write_fd);
> +			close(freeze_fd);
> +			return 1;

goto out_err;

> +		}
> +	}
> +
> +	if (ioctl(freeze_fd, FIFREEZE, 0) < 0) {
> +		perror("ioctl FIFREEZE");
> +		close(write_fd);
> +		close(freeze_fd);
> +		return 1;

goto out_err;

> +	}

frozen = 1;

> +
> +	if (pthread_create(&thread, NULL, close_thread, &write_fd) != 0) {
> +		perror("pthread_create");
> +		ioctl(freeze_fd, FITHAW, 0);
> +		close(write_fd);
> +		close(freeze_fd);
> +		return 1;

goto out_err;

> +	}
> +
> +	while (!close_started)
> +		usleep(1000);
> +
> +	for (i = 0; i < 30; i++) {

I'm wondering if "3 seconds" is enough to make sure "it's hang" absolutely :)

> +		usleep(100000);
> +		if (close_completed) {
> +			hung = 0;
> +			break;
> +		}
> +	}
> +
> +	if (hung)
> +		printf("close() hung under freeze!\n");
> +	else
> +		printf("close() completed immediately!\n");
> +
> +	ioctl(freeze_fd, FITHAW, 0);

frozen = 0;

> +	pthread_join(thread, NULL);
> +	close(freeze_fd);
> +	return hung ? 0 : 1;

out_err:
	if (write_fd >= 0)
		close(write_fd);
	if (frozen)
		ioctl(freeze_fd, FITHAW, 0);
	if (freeze_fd >= 0)
		close(freeze_fd);
	return 1;

> +}
> diff --git a/tests/xfs/842 b/tests/xfs/842
> new file mode 100755
> index 00000000..8b225ea2
> --- /dev/null
> +++ b/tests/xfs/842
> @@ -0,0 +1,32 @@
> +#! /bin/bash
> +# SPDX-License-Identifier: GPL-2.0
> +# Copyright (c) 2026 Aditya Prakash Srivastava.  All Rights Reserved.
> +#
> +# FS QA Test No. 842
> +#
> +# Verify that closing a file with active speculative post-EOF preallocations
> +# on a frozen XFS filesystem does not deadlock inside close().
> +#
> +. ./common/preamble
> +_begin_fstest auto quick freeze
> +
> +# Import helper functions
> +. ./common/rc
> +. ./common/filter
              ^^^^^^
It's useless.

> +
> +# Real-world configs
> +_supported_fs xfs

There's not "_supported_fs" helper anymore.

And I don't think this case is a xfs specific test case. I think it can be
run on all filesystems which supports fsfreeze --

_require_freeze

> +_require_scratch
> +_require_test_program "xfs_freeze_close_repro"
> +
> +_scratch_mkfs >> $seqres.full 2>&1
> +_scratch_mount
> +
> +TEST_FILE=$SCRATCH_MNT/test_eofblocks_freeze
> +
> +# Execute the reproducer helper on scratch mount
> +$here/src/xfs_freeze_close_repro $TEST_FILE
> +
> +status=$?

The output of xfs_freeze_close_repro will break the .out, don't need to
set status=$? at here. If you want, you can call _fail ... after the
xfs_freeze_close_repro returns non-zero.

> +_scratch_unmount

"unmount" is not necessary at here. But I think you need a specific _cleanup()
function for this case. Due to your C program does fs freeze. If it's killed
by something suddently, we must make the fs is freezed before running next
test case.

_cleanup()
{
        # Make sure $SCRATCH_MNT is unfreezed
        xfs_freeze -u $SCRATCH_MNT 2>/dev/null

        ....
        ....
}

> +_exit

_exit 0

Since to your kernel patch is still under review, let's see how it goes at first

Thanks,
Zorro

> diff --git a/tests/xfs/842.out b/tests/xfs/842.out
> new file mode 100644
> index 00000000..386fb9cc
> --- /dev/null
> +++ b/tests/xfs/842.out
> @@ -0,0 +1,2 @@
> +QA output created by 842
> +close() completed immediately!
> -- 
> 2.47.3
>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.