* Really bad I/O lock contention between non-overlapping reader and writer
@ 2026-10-12 17:47 Vitali Lovich
2026-10-12 18:23 ` Vitali Lovich
` (2 more replies)
0 siblings, 3 replies; 9+ messages in thread
From: Vitali Lovich @ 2026-10-12 17:47 UTC (permalink / raw)
To: linux-xfs
[-- Attachment #1: Type: text/plain, Size: 1844 bytes --]
I think I encountered suboptimal locking behavior with concurrent
readers and writers on a file, which resulted in huge latency spikes
for the reader even though the writers and readers are touching
unrelated ranges within the file. The writer/hole puncher hold an
exclusive lock on the inode while forcing a flush of outstanding
writes to disk which creates huge latency spikes for reads.
I've observed read's taking >> 500ms even though none of the writes
could be overlapping and traces seem to indicate it's the shared inode
lock contending with the exclusive inode lock that writes / hole
punching is holding. In my real-world test case traces indicated that
the cause might be inode_dio_wait while holding the exclusive inode
lock.
I've observed this behavior on XFS, EXT4 and BTRFS so it seems like a
general VFS challenge. Generally, Btrfs seems to have less read
latencies created than Ext4 (~2.5x more at the 0-1ms read than EXT4),
which in turn has smaller and fewer tail latencies than XFS (~2x more
at 0-1ms and extreme is smaller) although all filesystems exhibit the
issue. Starting with the XFS list in case I'm way off base that this
is a problem.
Attaching a simplified reproduction of what I'm doing with one writer
appending data to the file, a separate writer punching holes in
unrelated ranges, and an O_DIRECT reader that's reading data. Reads
are doing random 4kib I/O in [0, 64MiB), hole punching is punching
4kib holes in [64MiB, 576Mib] and writing is always appending after
577MiB of the file. The directory the anonymous file is created in is
CWD by default unless you pass a different path. I'd expect read
latencies to not be impacted by waiting for writes to flush yet it
seems like clearly there is such a serialization.
cc -O2 -g -std=gnu11 -Wall -Wextra -Werror repro-syscall.c -pthread -o
syscall-repro
[-- Attachment #2: pessimistic_locking_between_readers_and_writers.c --]
[-- Type: text/x-csrc, Size: 5089 bytes --]
/*
* File layout (MiB; ranges are half-open):
* [0, 64) random 4 KiB reads
* [64, 576) one-time 4 KiB hole punches
* [576, 577) unused guard
* [577, ...) repeated 512 MiB writes
*
* Before starting the worker threads, initialization writes and fsyncs the
* entire [0, 577) range in 1 MiB chunks. Thus every punched range was written
* first. The reader chooses aligned blocks only within [0, 64), and each
* pread ends no later than 64 MiB. Punches stay within [64, 576); the writer
* starts at 577 MiB and appends, so none of the three operations overlap.
*
* The read, write, and punch descriptors are opened on the same inode. The
* pathname is unlinked after opening; the descriptors keep that file alive.
*/
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdatomic.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#define K (1024ULL)
#define M (1024ULL * K)
/* File layout: readable data | punched data | guard | appended writes. */
#define READ_MIB 64
#define HOLE_MIB 512
#define WRITE_MIB 512
#define PUNCH_KIB 4
#define BUCKETS 1001
static int rfd, wfd, pfd;
static atomic_bool stop;
static atomic_ullong writes, punches, reads;
static uint64_t hist[BUCKETS], max_read_ns, over100ms;
static inline uint64_t ns(void) {
struct timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
return (uint64_t)t.tv_sec * 1000000000 + t.tv_nsec;
}
static void die(const char *s) { perror(s); exit(1); }
static void *aligned(size_t n) {
void *p;
int e = posix_memalign(&p, 4096, n);
if (e) { errno = e; die("posix_memalign"); }
memset(p, 0x5a, n);
return p;
}
static void *reader(void *unused) {
(void)unused;
char *b = aligned(4096);
uint64_t x = 0x9e3779b97f4a7c15ULL;
while (!atomic_load(&stop)) {
x ^= x << 13; x ^= x >> 7; x ^= x << 17;
off_t off = (off_t)((x % (READ_MIB * M / 4096)) * 4096);
uint64_t a = ns();
ssize_t n = pread(rfd, b, 4096, off);
uint64_t d = ns() - a;
if (n != 4096) die("pread");
if (d > max_read_ns) max_read_ns = d;
unsigned bucket = d / 1000000;
hist[bucket < BUCKETS ? bucket : BUCKETS - 1]++;
over100ms += d >= 100000000;
atomic_fetch_add(&reads, 1);
}
free(b); return NULL;
}
static void *writer(void *unused) {
(void)unused;
char *b = aligned(WRITE_MIB * M);
off_t off = (READ_MIB + HOLE_MIB + 1) * M;
while (!atomic_load(&stop)) {
size_t done = 0;
while (done < WRITE_MIB * M) {
ssize_t n = pwrite(wfd, b + done, WRITE_MIB * M - done, off + done);
if (n < 0) die("pwrite");
if (!n) { errno = EIO; die("zero pwrite"); }
done += (size_t)n;
}
off += WRITE_MIB * M;
atomic_fetch_add(&writes, 1);
}
free(b); return NULL;
}
static void *puncher(void *unused) {
(void)unused;
off_t off = READ_MIB * M;
const off_t end = (READ_MIB + HOLE_MIB) * M;
while (!atomic_load(&stop) && off < end) {
if (fallocate(pfd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, off, PUNCH_KIB * K)) die("fallocate punch");
off += PUNCH_KIB * K;
atomic_fetch_add(&punches, 1);
}
return NULL;
}
int main(int argc, char **argv) {
const char *dir = argc > 1 ? argv[1] : ".";
unsigned seconds = argc > 2 ? strtoul(argv[2], NULL, 10) : 10;
char path[4096];
if (snprintf(path, sizeof(path), "%s/syscall-repro-XXXXXX", dir) >= (int)sizeof(path)) { errno = ENAMETOOLONG; die("path"); }
int fd = mkstemp(path); if (fd < 0) die("mkstemp");
rfd = open(path, O_RDONLY | O_DIRECT); if (rfd < 0) die("open read");
wfd = open(path, O_RDWR | O_DIRECT); if (wfd < 0) die("open write");
pfd = open(path, O_RDWR); if (pfd < 0) die("open punch");
unlink(path);
char *init = aligned(M);
off_t end = (READ_MIB + HOLE_MIB + 1) * M;
for (off_t o = 0; o < end; o += M)
if (pwrite(fd, init, M, o) != M) die("initialize");
if (fsync(fd)) die("fsync init");
free(init); close(fd);
printf("direct pread/pwrite; fallocate punch; read=[0,%d MiB), punch=[%d,%d MiB), write starts at %lld MiB; seconds=%u\n", READ_MIB, READ_MIB, READ_MIB + HOLE_MIB, (long long)(READ_MIB + HOLE_MIB + 1), seconds);
pthread_t rt, wt, pt;
if (pthread_create(&rt, NULL, reader, NULL) || pthread_create(&wt, NULL, writer, NULL) || pthread_create(&pt, NULL, puncher, NULL)) die("pthread_create");
sleep(seconds);
atomic_store(&stop, true);
pthread_join(rt, NULL); pthread_join(wt, NULL); pthread_join(pt, NULL);
printf("summary reads=%llu writes=%llu punches=%llu max-read=%.1f ms >100ms=%llu\n", atomic_load(&reads), atomic_load(&writes), atomic_load(&punches), max_read_ns / 1e6, (unsigned long long)over100ms);
puts("latency-ms,count");
for (unsigned i = 0; i < BUCKETS; ++i) if (hist[i]) printf("%u,%llu\n", i, (unsigned long long)hist[i]);
return 0;
}
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: Really bad I/O lock contention between non-overlapping reader and writer
2026-10-12 17:47 Really bad I/O lock contention between non-overlapping reader and writer Vitali Lovich
@ 2026-10-12 18:23 ` Vitali Lovich
2026-10-12 19:37 ` Alan Huang
2026-10-12 20:47 ` Dave Chinner
2 siblings, 0 replies; 9+ messages in thread
From: Vitali Lovich @ 2026-10-12 18:23 UTC (permalink / raw)
To: linux-xfs
I also forgot to mention I'm on 7.2.8-arch1 and am happy to try
developing a fix. Not sure who to coordinate / collaborate with.
On Mon, Oct 12, 2026 at 10:47 AM Vitali Lovich <vlovich@gmail.com> wrote:
>
> I think I encountered suboptimal locking behavior with concurrent
> readers and writers on a file, which resulted in huge latency spikes
> for the reader even though the writers and readers are touching
> unrelated ranges within the file. The writer/hole puncher hold an
> exclusive lock on the inode while forcing a flush of outstanding
> writes to disk which creates huge latency spikes for reads.
>
> I've observed read's taking >> 500ms even though none of the writes
> could be overlapping and traces seem to indicate it's the shared inode
> lock contending with the exclusive inode lock that writes / hole
> punching is holding. In my real-world test case traces indicated that
> the cause might be inode_dio_wait while holding the exclusive inode
> lock.
>
> I've observed this behavior on XFS, EXT4 and BTRFS so it seems like a
> general VFS challenge. Generally, Btrfs seems to have less read
> latencies created than Ext4 (~2.5x more at the 0-1ms read than EXT4),
> which in turn has smaller and fewer tail latencies than XFS (~2x more
> at 0-1ms and extreme is smaller) although all filesystems exhibit the
> issue. Starting with the XFS list in case I'm way off base that this
> is a problem.
>
> Attaching a simplified reproduction of what I'm doing with one writer
> appending data to the file, a separate writer punching holes in
> unrelated ranges, and an O_DIRECT reader that's reading data. Reads
> are doing random 4kib I/O in [0, 64MiB), hole punching is punching
> 4kib holes in [64MiB, 576Mib] and writing is always appending after
> 577MiB of the file. The directory the anonymous file is created in is
> CWD by default unless you pass a different path. I'd expect read
> latencies to not be impacted by waiting for writes to flush yet it
> seems like clearly there is such a serialization.
>
> cc -O2 -g -std=gnu11 -Wall -Wextra -Werror repro-syscall.c -pthread -o
> syscall-repro
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: Really bad I/O lock contention between non-overlapping reader and writer
2026-10-12 17:47 Really bad I/O lock contention between non-overlapping reader and writer Vitali Lovich
2026-10-12 18:23 ` Vitali Lovich
@ 2026-10-12 19:37 ` Alan Huang
2026-10-12 19:53 ` Vitali Lovich
2026-10-12 20:47 ` Dave Chinner
2 siblings, 1 reply; 9+ messages in thread
From: Alan Huang @ 2026-10-12 19:37 UTC (permalink / raw)
To: Vitali Lovich; +Cc: linux-xfs
On Oct 13, 2026, at 01:47, Vitali Lovich <vlovich@gmail.com> wrote:
>
> I think I encountered suboptimal locking behavior with concurrent
> readers and writers on a file, which resulted in huge latency spikes
> for the reader even though the writers and readers are touching
> unrelated ranges within the file. The writer/hole puncher hold an
> exclusive lock on the inode while forcing a flush of outstanding
> writes to disk which creates huge latency spikes for reads.
IIRC, there is no fine-grained concurrency control on the same inode.
>
> I've observed read's taking >> 500ms even though none of the writes
> could be overlapping and traces seem to indicate it's the shared inode
> lock contending with the exclusive inode lock that writes / hole
> punching is holding. In my real-world test case traces indicated that
> the cause might be inode_dio_wait while holding the exclusive inode
> lock.
>
> I've observed this behavior on XFS, EXT4 and BTRFS so it seems like a
> general VFS challenge. Generally, Btrfs seems to have less read
> latencies created than Ext4 (~2.5x more at the 0-1ms read than EXT4),
> which in turn has smaller and fewer tail latencies than XFS (~2x more
> at 0-1ms and extreme is smaller) although all filesystems exhibit the
> issue. Starting with the XFS list in case I'm way off base that this
> is a problem.
>
> Attaching a simplified reproduction of what I'm doing with one writer
> appending data to the file, a separate writer punching holes in
> unrelated ranges, and an O_DIRECT reader that's reading data. Reads
> are doing random 4kib I/O in [0, 64MiB), hole punching is punching
> 4kib holes in [64MiB, 576Mib] and writing is always appending after
> 577MiB of the file. The directory the anonymous file is created in is
> CWD by default unless you pass a different path. I'd expect read
> latencies to not be impacted by waiting for writes to flush yet it
> seems like clearly there is such a serialization.
>
> cc -O2 -g -std=gnu11 -Wall -Wextra -Werror repro-syscall.c -pthread -o
> syscall-repro
> <pessimistic_locking_between_readers_and_writers.c>
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: Really bad I/O lock contention between non-overlapping reader and writer
2026-10-12 19:37 ` Alan Huang
@ 2026-10-12 19:53 ` Vitali Lovich
2026-10-12 20:27 ` Alan Huang
0 siblings, 1 reply; 9+ messages in thread
From: Vitali Lovich @ 2026-10-12 19:53 UTC (permalink / raw)
To: Alan Huang; +Cc: linux-xfs
On Mon, Oct 12, 2026 at 12:38 PM Alan Huang <mmpgouride@gmail.com> wrote:
>
> On Oct 13, 2026, at 01:47, Vitali Lovich <vlovich@gmail.com> wrote:
> >
> > I think I encountered suboptimal locking behavior with concurrent
> > readers and writers on a file, which resulted in huge latency spikes
> > for the reader even though the writers and readers are touching
> > unrelated ranges within the file. The writer/hole puncher hold an
> > exclusive lock on the inode while forcing a flush of outstanding
> > writes to disk which creates huge latency spikes for reads.
>
> IIRC, there is no fine-grained concurrency control on the same inode.
Correct. However, I think there's a middle ground without rewriting
the VFS layer too intrusively. The rough idea is to have a new
mechanism for filesystems (that opt-in) to track range operations. The
inode lock would then only be needed to protect the range tree. It
still has lock contention on the lock obviously, but the problematic
operation - needing to do I/O (or even worse waiting for it to flush)
while holding an exclusive lock - is relaxed when the ranges are
non-overlapping.
Thoughts?
>
> >
> > I've observed read's taking >> 500ms even though none of the writes
> > could be overlapping and traces seem to indicate it's the shared inode
> > lock contending with the exclusive inode lock that writes / hole
> > punching is holding. In my real-world test case traces indicated that
> > the cause might be inode_dio_wait while holding the exclusive inode
> > lock.
> >
> > I've observed this behavior on XFS, EXT4 and BTRFS so it seems like a
> > general VFS challenge. Generally, Btrfs seems to have less read
> > latencies created than Ext4 (~2.5x more at the 0-1ms read than EXT4),
> > which in turn has smaller and fewer tail latencies than XFS (~2x more
> > at 0-1ms and extreme is smaller) although all filesystems exhibit the
> > issue. Starting with the XFS list in case I'm way off base that this
> > is a problem.
> >
> > Attaching a simplified reproduction of what I'm doing with one writer
> > appending data to the file, a separate writer punching holes in
> > unrelated ranges, and an O_DIRECT reader that's reading data. Reads
> > are doing random 4kib I/O in [0, 64MiB), hole punching is punching
> > 4kib holes in [64MiB, 576Mib] and writing is always appending after
> > 577MiB of the file. The directory the anonymous file is created in is
> > CWD by default unless you pass a different path. I'd expect read
> > latencies to not be impacted by waiting for writes to flush yet it
> > seems like clearly there is such a serialization.
> >
> > cc -O2 -g -std=gnu11 -Wall -Wextra -Werror repro-syscall.c -pthread -o
> > syscall-repro
> > <pessimistic_locking_between_readers_and_writers.c>
>
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: Really bad I/O lock contention between non-overlapping reader and writer
2026-10-12 19:53 ` Vitali Lovich
@ 2026-10-12 20:27 ` Alan Huang
0 siblings, 0 replies; 9+ messages in thread
From: Alan Huang @ 2026-10-12 20:27 UTC (permalink / raw)
To: Vitali Lovich; +Cc: linux-xfs
On Oct 13, 2026, at 03:53, Vitali Lovich <vlovich@gmail.com> wrote:
>
> On Mon, Oct 12, 2026 at 12:38 PM Alan Huang <mmpgouride@gmail.com> wrote:
>>
>> On Oct 13, 2026, at 01:47, Vitali Lovich <vlovich@gmail.com> wrote:
>>>
>>> I think I encountered suboptimal locking behavior with concurrent
>>> readers and writers on a file, which resulted in huge latency spikes
>>> for the reader even though the writers and readers are touching
>>> unrelated ranges within the file. The writer/hole puncher hold an
>>> exclusive lock on the inode while forcing a flush of outstanding
>>> writes to disk which creates huge latency spikes for reads.
>>
>> IIRC, there is no fine-grained concurrency control on the same inode.
> Correct. However, I think there's a middle ground without rewriting
> the VFS layer too intrusively. The rough idea is to have a new
> mechanism for filesystems (that opt-in) to track range operations. The
> inode lock would then only be needed to protect the range tree. It
> still has lock contention on the lock obviously, but the problematic
> operation - needing to do I/O (or even worse waiting for it to flush)
> while holding an exclusive lock - is relaxed when the ranges are
> non-overlapping.
>
Tracking the range operations might need the previous/next-key locking.
But It is up to the maintainers to decide whether to implement concurrency control for operations on the same inode.
> Thoughts?
>
>>
>>>
>>> I've observed read's taking >> 500ms even though none of the writes
>>> could be overlapping and traces seem to indicate it's the shared inode
>>> lock contending with the exclusive inode lock that writes / hole
>>> punching is holding. In my real-world test case traces indicated that
>>> the cause might be inode_dio_wait while holding the exclusive inode
>>> lock.
>>>
>>> I've observed this behavior on XFS, EXT4 and BTRFS so it seems like a
>>> general VFS challenge. Generally, Btrfs seems to have less read
>>> latencies created than Ext4 (~2.5x more at the 0-1ms read than EXT4),
>>> which in turn has smaller and fewer tail latencies than XFS (~2x more
>>> at 0-1ms and extreme is smaller) although all filesystems exhibit the
>>> issue. Starting with the XFS list in case I'm way off base that this
>>> is a problem.
>>>
>>> Attaching a simplified reproduction of what I'm doing with one writer
>>> appending data to the file, a separate writer punching holes in
>>> unrelated ranges, and an O_DIRECT reader that's reading data. Reads
>>> are doing random 4kib I/O in [0, 64MiB), hole punching is punching
>>> 4kib holes in [64MiB, 576Mib] and writing is always appending after
>>> 577MiB of the file. The directory the anonymous file is created in is
>>> CWD by default unless you pass a different path. I'd expect read
>>> latencies to not be impacted by waiting for writes to flush yet it
>>> seems like clearly there is such a serialization.
>>>
>>> cc -O2 -g -std=gnu11 -Wall -Wextra -Werror repro-syscall.c -pthread -o
>>> syscall-repro
>>> <pessimistic_locking_between_readers_and_writers.c>
>>
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: Really bad I/O lock contention between non-overlapping reader and writer
2026-10-12 17:47 Really bad I/O lock contention between non-overlapping reader and writer Vitali Lovich
2026-10-12 18:23 ` Vitali Lovich
2026-10-12 19:37 ` Alan Huang
@ 2026-10-12 20:47 ` Dave Chinner
2026-10-13 23:49 ` Vitali Lovich
[not found] ` <CAF8PYMi5DX0AkzbSTFnCchHnr3QeB-S7J+2FNMCqB8zrtR9ojA@mail.gmail.com>
2 siblings, 2 replies; 9+ messages in thread
From: Dave Chinner @ 2026-10-12 20:47 UTC (permalink / raw)
To: Vitali Lovich; +Cc: linux-xfs
On Mon, Oct 12, 2026 at 10:47:56AM -0700, Vitali Lovich wrote:
> I think I encountered suboptimal locking behavior with concurrent
> readers and writers on a file, which resulted in huge latency spikes
> for the reader even though the writers and readers are touching
> unrelated ranges within the file. The writer/hole puncher hold an
> exclusive lock on the inode while forcing a flush of outstanding
> writes to disk which creates huge latency spikes for reads.
>
> I've observed read's taking >> 500ms even though none of the writes
> could be overlapping and traces seem to indicate it's the shared inode
> lock contending with the exclusive inode lock that writes / hole
> punching is holding. In my real-world test case traces indicated that
> the cause might be inode_dio_wait while holding the exclusive inode
> lock.
>
> I've observed this behavior on XFS, EXT4 and BTRFS so it seems like a
> general VFS challenge. Generally, Btrfs seems to have less read
> latencies created than Ext4 (~2.5x more at the 0-1ms read than EXT4),
> which in turn has smaller and fewer tail latencies than XFS (~2x more
> at 0-1ms and extreme is smaller) although all filesystems exhibit the
> issue. Starting with the XFS list in case I'm way off base that this
> is a problem.
>
> Attaching a simplified reproduction of what I'm doing with one writer
> appending data to the file,
The repro is using DIO writes. Speaking for XFS, these need needs
exclusive locking to serialise extending the file without causing
data corruption or stale data exposure. Once extended, the locking
demotes to shared, so the IO itself runs concurrently with reads.
Appending DIO writes are basically a fast combined 'truncate up,
write' operation, and so has the same initial serialisation
constraints as 'truncate up' (i.e. exclusive locking).
> a separate writer punching holes in
> unrelated ranges,
fallocate() is like truncate - it also needs exclusive locking and
no IO in flight to avoid a UAF of the data extent being punched out.
> and an O_DIRECT reader that's reading data.
Shared locking only.
> Reads
> are doing random 4kib I/O in [0, 64MiB), hole punching is punching
> 4kib holes in [64MiB, 576Mib] and writing is always appending after
> 577MiB of the file.
Educate me: what application workload has this IO pattern? Is this a
real world problem. i.e. why should we care about this contrived
scenario?
Also, have you tried to implement highly concurrent range locks that
don't have far worse contention on the range lock tracking
structures themselves than a simple rwsem? Some of us here have
actually tried to do this, and the fact that we still use rwsems
should tell you a lot...
-Dave.
--
Dave Chinner
dgc@kernel.org
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: Really bad I/O lock contention between non-overlapping reader and writer
2026-10-12 20:47 ` Dave Chinner
@ 2026-10-13 23:49 ` Vitali Lovich
2026-10-14 3:01 ` Dave Chinner
[not found] ` <CAF8PYMi5DX0AkzbSTFnCchHnr3QeB-S7J+2FNMCqB8zrtR9ojA@mail.gmail.com>
1 sibling, 1 reply; 9+ messages in thread
From: Vitali Lovich @ 2026-10-13 23:49 UTC (permalink / raw)
To: Dave Chinner; +Cc: linux-xfs
On Mon, Oct 12, 2026 at 1:47 PM Dave Chinner <dgc@kernel.org> wrote:
>
> On Mon, Oct 12, 2026 at 10:47:56AM -0700, Vitali Lovich wrote:
> > I think I encountered suboptimal locking behavior with concurrent
> > readers and writers on a file, which resulted in huge latency spikes
> > for the reader even though the writers and readers are touching
> > unrelated ranges within the file. The writer/hole puncher hold an
> > exclusive lock on the inode while forcing a flush of outstanding
> > writes to disk which creates huge latency spikes for reads.
> >
> > I've observed read's taking >> 500ms even though none of the writes
> > could be overlapping and traces seem to indicate it's the shared inode
> > lock contending with the exclusive inode lock that writes / hole
> > punching is holding. In my real-world test case traces indicated that
> > the cause might be inode_dio_wait while holding the exclusive inode
> > lock.
> >
> > I've observed this behavior on XFS, EXT4 and BTRFS so it seems like a
> > general VFS challenge. Generally, Btrfs seems to have less read
> > latencies created than Ext4 (~2.5x more at the 0-1ms read than EXT4),
> > which in turn has smaller and fewer tail latencies than XFS (~2x more
> > at 0-1ms and extreme is smaller) although all filesystems exhibit the
> > issue. Starting with the XFS list in case I'm way off base that this
> > is a problem.
> >
> > Attaching a simplified reproduction of what I'm doing with one writer
> > appending data to the file,
>
> The repro is using DIO writes. Speaking for XFS, these need needs
> exclusive locking to serialise extending the file without causing
> data corruption or stale data exposure. Once extended, the locking
> demotes to shared, so the IO itself runs concurrently with reads.
>
> Appending DIO writes are basically a fast combined 'truncate up,
> write' operation, and so has the same initial serialisation
> constraints as 'truncate up' (i.e. exclusive locking).
I think that's true if the write continues at the last byte of the
previous one. If the write leaves a gap for whatever reason (e.g.
writes are 4kib aligned but we only had 3.5kib of data before the file
end) the fast-path isn't taken and you do have to wait for a full
flush because XFS doesn't realize there's nothing overlapping. This
scenario isn't present in the test case I published but I think it
acts very similarly to hole punching.
>
> > a separate writer punching holes in
> > unrelated ranges,
>
> fallocate() is like truncate - it also needs exclusive locking and
> no IO in flight to avoid a UAF of the data extent being punched out.
It needs exclusive locking and draining around the range in flight,
not necessarily the entire i_node.
> > and an O_DIRECT reader that's reading data.
>
> Shared locking only.
If I wasn't clear, the problem is that the shared lock has a head of
line blocking behind the exclusive lock that's draining the I/O even
though userspace knows it's never touching that range.
> > Reads
> > are doing random 4kib I/O in [0, 64MiB), hole punching is punching
> > 4kib holes in [64MiB, 576Mib] and writing is always appending after
> > 577MiB of the file.
>
> Educate me: what application workload has this IO pattern? Is this a
> real world problem. i.e. why should we care about this contrived
> scenario?
I'm trying to implement an LSM database that stores everything in one
file instead of taking the RocksDB approach of 1 file per SST. One
file per SST has scaling problems of its own in other ways, but for
concurrent reads and writes they acquire locks on different inodes
(file and directory, respectively), which is why RocksDB doesn't have
the problem highlighted in this test-case - it always reads and writes
to separate i_nodes with the downside being that it ends up having to
open an insane amount of files on startup and juggle them if there's
more than the kernel limit allows.
>
> Also, have you tried to implement highly concurrent range locks that
> don't have far worse contention on the range lock tracking
> structures themselves than a simple rwsem? Some of us here have
> actually tried to do this, and the fact that we still use rwsems
> should tell you a lot...
I'm not pretending it's an easy problem to solve. I've come up with a
few approaches and would love your feedback on what's the best path:
1. DIO tracking so that the inode keeps track of all outstanding
DIO's. Rather than the barrier, we scan the list to find if there's
any conflicts. I've prototyped this and it clearly solves the false
synchronization barrier from this in my database (unrelated writes
prolonging reads). This has a negative impact on normal workloads so I
imagine this would be hidden behind an explicit ioctl if acceptable.
2. Introduce generational barriers where hole punching / writing past
the end of the file still waits for DIOs but outside the exclusive
lock. Inside the exclusive lock all it does is register that the range
that it's doing the operation on and the wait happens outside the
exclusive lock. Operations that overlap will either wait for all
outstanding DIO or perhaps satisfy if they can (e.g. a read of an
fallocate technically could just supply the 0s without waiting for
DIO).
Option 1 is the "safest" in that it introduces no new concepts but it
does carry overhead both in terms of memory and performance. Option 2
is probably the highest performance option and might even be safe as
an always-on default.
Is either or both of these a non-starter idea in terms of acceptance
(assuming I can actually make it safe)?
>
> -Dave.
> --
> Dave Chinner
> dgc@kernel.org
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: Really bad I/O lock contention between non-overlapping reader and writer
2026-10-13 23:49 ` Vitali Lovich
@ 2026-10-14 3:01 ` Dave Chinner
0 siblings, 0 replies; 9+ messages in thread
From: Dave Chinner @ 2026-10-14 3:01 UTC (permalink / raw)
To: Vitali Lovich; +Cc: linux-xfs
On Tue, Oct 13, 2026 at 04:49:53PM -0700, Vitali Lovich wrote:
> On Mon, Oct 12, 2026 at 1:47 PM Dave Chinner <dgc@kernel.org> wrote:
> >
> > On Mon, Oct 12, 2026 at 10:47:56AM -0700, Vitali Lovich wrote:
> > > I think I encountered suboptimal locking behavior with concurrent
> > > readers and writers on a file, which resulted in huge latency spikes
> > > for the reader even though the writers and readers are touching
> > > unrelated ranges within the file. The writer/hole puncher hold an
> > > exclusive lock on the inode while forcing a flush of outstanding
> > > writes to disk which creates huge latency spikes for reads.
> > >
> > > I've observed read's taking >> 500ms even though none of the writes
> > > could be overlapping and traces seem to indicate it's the shared inode
> > > lock contending with the exclusive inode lock that writes / hole
> > > punching is holding. In my real-world test case traces indicated that
> > > the cause might be inode_dio_wait while holding the exclusive inode
> > > lock.
> > >
> > > I've observed this behavior on XFS, EXT4 and BTRFS so it seems like a
> > > general VFS challenge. Generally, Btrfs seems to have less read
> > > latencies created than Ext4 (~2.5x more at the 0-1ms read than EXT4),
> > > which in turn has smaller and fewer tail latencies than XFS (~2x more
> > > at 0-1ms and extreme is smaller) although all filesystems exhibit the
> > > issue. Starting with the XFS list in case I'm way off base that this
> > > is a problem.
> > >
> > > Attaching a simplified reproduction of what I'm doing with one writer
> > > appending data to the file,
> >
> > The repro is using DIO writes. Speaking for XFS, these need needs
> > exclusive locking to serialise extending the file without causing
> > data corruption or stale data exposure. Once extended, the locking
> > demotes to shared, so the IO itself runs concurrently with reads.
> >
> > Appending DIO writes are basically a fast combined 'truncate up,
> > write' operation, and so has the same initial serialisation
> > constraints as 'truncate up' (i.e. exclusive locking).
> I think that's true if the write continues at the last byte of the
> previous one. If the write leaves a gap for whatever reason (e.g.
> writes are 4kib aligned but we only had 3.5kib of data before the file
> end) the fast-path isn't taken and you do have to wait for a full
> flush because XFS doesn't realize there's nothing overlapping. This
> scenario isn't present in the test case I published but I think it
> acts very similarly to hole punching.
What happens when there are blocked beyond EOF that the file
extension exposes?
> > > a separate writer punching holes in
> > > unrelated ranges,
> >
> > fallocate() is like truncate - it also needs exclusive locking and
> > no IO in flight to avoid a UAF of the data extent being punched out.
> It needs exclusive locking and draining around the range in flight,
> not necessarily the entire i_node.
You don't need to explain how range locking works to me. If you
think that you need to explain basic stuff like this to me, then you
likely haven't understood what I've said.
> > > and an O_DIRECT reader that's reading data.
> >
> > Shared locking only.
> If I wasn't clear, the problem is that the shared lock has a head of
> line blocking behind the exclusive lock that's draining the I/O even
> though userspace knows it's never touching that range.
As I said above: competing shared vs exclusive locking implies head
of line blocking that causes latency issues. You don't need to
explain basic stuff to me.
> > > Reads
> > > are doing random 4kib I/O in [0, 64MiB), hole punching is punching
> > > 4kib holes in [64MiB, 576Mib] and writing is always appending after
> > > 577MiB of the file.
> >
> > Educate me: what application workload has this IO pattern? Is this a
> > real world problem. i.e. why should we care about this contrived
> > scenario?
> I'm trying to implement an LSM database that stores everything in one
> file instead of taking the RocksDB approach of 1 file per SST. One
> file per SST has scaling problems of its own in other ways, but for
> concurrent reads and writes they acquire locks on different inodes
> (file and directory, respectively), which is why RocksDB doesn't have
> the problem highlighted in this test-case - it always reads and writes
> to separate i_nodes with the downside being that it ends up having to
> open an insane amount of files on startup and juggle them if there's
> more than the kernel limit allows.
XFS can handle access to millions of files iat a time efficiently.
It's designed to work with massive data sets and lots of files.
You're having problems because you're trying to optimise everything
into a single file, hence hitting largely unfixable single file
scalability issues.
> > Also, have you tried to implement highly concurrent range locks that
> > don't have far worse contention on the range lock tracking
> > structures themselves than a simple rwsem? Some of us here have
> > actually tried to do this, and the fact that we still use rwsems
> > should tell you a lot...
>
> I'm not pretending it's an easy problem to solve. I've come up with a
> few approaches and would love your feedback on what's the best path:
>
> 1. DIO tracking so that the inode keeps track of all outstanding
> DIO's. Rather than the barrier, we scan the list to find if there's
> any conflicts.
Yup, that's a range lock.
> I've prototyped this and it clearly solves the false
> synchronization barrier from this in my database (unrelated writes
> prolonging reads). This has a negative impact on normal workloads so I
> imagine this would be hidden behind an explicit ioctl if acceptable.
No, "optional" locking models are a non-starter. It either works for
everything or it is a non-starter.
> 2. Introduce generational barriers where hole punching / writing past
> the end of the file still waits for DIOs but outside the exclusive
> lock.
The exclusive lock forms an IO submission barrier allowing in-flight
DIOs to drain and hence the extending write/fallocate operation to
start.
Without that submission barrier, how do you prevent incoming DIOs
(read and write within EOF) from preventing the in flight counter
from ever reaching zero?
i.e. a submission barrier is needed to avoid moving unbound
latencies to the extending write/fallocate operations...
> Inside the exclusive lock all it does is register that the range
> that it's doing the operation on and the wait happens outside the
> exclusive lock. Operations that overlap will either wait for all
Now we're back to range tracking....
> outstanding DIO or perhaps satisfy if they can (e.g. a read of an
> fallocate technically could just supply the 0s without waiting for
> DIO).
No, we can't return zeros to userspace until after the fallocate
operation completes successfully. If the operation has not started,
we have to return the data as it currently exists on disk - that is
a fundamental data integrity requirement.
> Is either or both of these a non-starter idea in terms of acceptance
> (assuming I can actually make it safe)?
If you come up with something that works in the general case, and it
scales out to millions of concurrent random 4kB DIO read/write IOPS
on a single file across dozens of processes submitting IO, then I'll
take what you are proposing seriously.
But until I see code that is provably correct with benchmarks that
show IO similar performance to rwsem shared locking, I'll remain
skeptical that this can be done.
Cheers,
Dave.
--
Dave Chinner
dgc@kernel.org
^ permalink raw reply [flat|nested] 9+ messages in thread
[parent not found: <CAF8PYMi5DX0AkzbSTFnCchHnr3QeB-S7J+2FNMCqB8zrtR9ojA@mail.gmail.com>]
* Re: Really bad I/O lock contention between non-overlapping reader and writer
[not found] ` <CAF8PYMi5DX0AkzbSTFnCchHnr3QeB-S7J+2FNMCqB8zrtR9ojA@mail.gmail.com>
@ 2026-10-14 2:34 ` Dave Chinner
0 siblings, 0 replies; 9+ messages in thread
From: Dave Chinner @ 2026-10-14 2:34 UTC (permalink / raw)
To: Vitali Lovich; +Cc: linux-xfs
On Mon, Oct 12, 2026 at 01:59:53PM -0700, Vitali Lovich wrote:
> On Mon, Oct 12, 2026 at 1:47 PM Dave Chinner <dgc@kernel.org> wrote:
>
> > On Mon, Oct 12, 2026 at 10:47:56AM -0700, Vitali Lovich wrote:
> > > I think I encountered suboptimal locking behavior with concurrent
> > > readers and writers on a file, which resulted in huge latency spikes
> > > for the reader even though the writers and readers are touching
> > > unrelated ranges within the file. The writer/hole puncher hold an
> > > exclusive lock on the inode while forcing a flush of outstanding
> > > writes to disk which creates huge latency spikes for reads.
> > >
> > > I've observed read's taking >> 500ms even though none of the writes
> > > could be overlapping and traces seem to indicate it's the shared inode
> > > lock contending with the exclusive inode lock that writes / hole
> > > punching is holding. In my real-world test case traces indicated that
> > > the cause might be inode_dio_wait while holding the exclusive inode
> > > lock.
> > >
> > > I've observed this behavior on XFS, EXT4 and BTRFS so it seems like a
> > > general VFS challenge. Generally, Btrfs seems to have less read
> > > latencies created than Ext4 (~2.5x more at the 0-1ms read than EXT4),
> > > which in turn has smaller and fewer tail latencies than XFS (~2x more
> > > at 0-1ms and extreme is smaller) although all filesystems exhibit the
> > > issue. Starting with the XFS list in case I'm way off base that this
> > > is a problem.
> > >
> > > Attaching a simplified reproduction of what I'm doing with one writer
> > > appending data to the file,
> >
> > The repro is using DIO writes. Speaking for XFS, these need needs
> > exclusive locking to serialise extending the file without causing
> > data corruption or stale data exposure. Once extended, the locking
> > demotes to shared, so the IO itself runs concurrently with reads.
>
>
> I’ll confirm if that’s the case but also it seems strange at a conceptual
> level that you’d need to hold the exclusive lock for such a long period of
> time, especially when there’s only a single thread reading and writing
> independent ranges (that’s not what this reproducer is because I didn’t
> want to complicate it with io_uring but it reproduces there too).
Define "long period". extending the file may require IO to be done
to zero allocated extents that the file extension exposes. Even just
the partial block range beyond EOF needs to be guaranteed to hold
zeros -before- the file size is extended.
Truncate up isn't just "write the new size to the in-memory inode";
both IO and transactions can be necessary, and they havbe to be done
atomically w.r.t. the file size extension. Hence, exclusive locking.
> > > Reads
> > > are doing random 4kib I/O in [0, 64MiB), hole punching is punching
> > > 4kib holes in [64MiB, 576Mib] and writing is always appending after
> > > 577MiB of the file.
> >
> > Educate me: what application workload has this IO pattern? Is this a
> > real world problem. i.e. why should we care about this contrived
> > scenario?
>
>
> 100% real scenario. This repro is just a contrived minimal example to try
> to reproduce what I’m seeing. The specific idea is to have an LSM database
> store all LSM tables within 1 file instead of separate files per LSM tables
> like RocksDB does.
The fundamental problem with your model is that it assumes that
single file IO and extent manipulation operations scale without
contention or serialisation. That has never been the case, never will
be the case.
Perhaps there's good reasons for RocksDB using a 'many files' model
for it's back end storage, and only now are you learning why they
used that model....
> It works really well for lots of reasons and generally
> works really really well but I’m running into unexpected P99 latency spikes
> in certain workloads and P100 often goes all the way past 1s.
Yup, and as long as you want to use fallocate or truncate/append
operations, there's very little that can be done about it.
> It really
> looks like it’s this exclusive locking even though the application
> guarantees we never read from any data that’s not been fsync’ed and I only
> punch holes when I know there’s never going to be any more access for that
> range. And btw in my application everything is happening on one thread.
Tell me: how is the filesystem supposed to know what the application
is doing? They aren't omnicent....
> > Also, have you tried to implement highly concurrent range locks that
> > don't have far worse contention on the range lock tracking
> > structures themselves than a simple rwsem? Some of us here have
> > actually tried to do this, and the fact that we still use rwsems
> > should tell you a lot...
>
> In case I wasn’t clear I don’t think it’s the lock contention that’s the
> problem.
Yes, but what I'm saying is that moving to range locking creates a
per-inode new lock contention problem that doesn't currently exist,
and that it's a far worse performance issue than what we have
now....
> My real workload (not this contrived one) does all I/O from a
> single thread. The problem is purely doing super expensive I/O (or at least
> waiting for it to complete) while holding an exclusive lock preventing
> other stuff from completing. Keeping the range structure synchronized I
> think would be unlikely to show up in practice
So the theory goes. The reality:
https://lore.kernel.org/linux-xfs/20220919230947.GM3600936@dread.disaster.area/
If it were simple, XFS would have had range locking years ago.
-Dave.
--
Dave Chinner
dgc@kernel.org
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-10-14 3:01 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-12 17:47 Really bad I/O lock contention between non-overlapping reader and writer Vitali Lovich
2026-10-12 18:23 ` Vitali Lovich
2026-10-12 19:37 ` Alan Huang
2026-10-12 19:53 ` Vitali Lovich
2026-10-12 20:27 ` Alan Huang
2026-10-12 20:47 ` Dave Chinner
2026-10-13 23:49 ` Vitali Lovich
2026-10-14 3:01 ` Dave Chinner
[not found] ` <CAF8PYMi5DX0AkzbSTFnCchHnr3QeB-S7J+2FNMCqB8zrtR9ojA@mail.gmail.com>
2026-10-14 2:34 ` Dave Chinner
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox