mirror of
https://github.com/torvalds/linux.git
synced 2026-11-06 21:58:01 +01:00
splice: retry tee() instead of returning 0 when it races
tee() can return 0 while the input pipe still has writers, when
another task reads the input pipe or fills the output pipe at the same
time. tee(2) documents 0 as "no data was copied" because the input pipe
has no writers, and loops built on it (the man page example breaks out
on 0) stop early. In a test with one thread writing 1 byte at a time
to the input pipe, one reading it and one draining the output pipe,
about one tee() call in 150 returned 0.
do_tee() runs ipipe_prep() and opipe_prep() to wait for data and room,
each under its own pipe lock, and only then takes both locks in
link_pipe(). If the input pipe was emptied or the output pipe filled in
between, link_pipe() finds nothing to do and returns 0.
splice_pipe_to_pipe(), which was modelled on tee(), handles the same
race by dropping both locks and going back to the prep steps ("A zero
return value means EOF, so retry instead"), and returns -EAGAIN for
SPLICE_F_NONBLOCK.
Do the same in link_pipe(): move the prep steps into it and retry when
nothing was linked although the input pipe still has writers.
Tested under QEMU (x86_64) with the program in the notes: 154 of 22698
tee() calls returned 0 before the change, none of 34429 after it.
Fixes: aadd06e5c5 ("[PATCH] splice: fix problems with sys_tee()")
Signed-off-by: Nickolai Zeldovich <nickolai@csail.mit.edu>
Link: https://patch.msgid.link/20261018025541.799135-1-nickolai@csail.mit.edu
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
This commit is contained in:
parent
23584035ad
commit
b2aa970a0a
43
fs/splice.c
43
fs/splice.c
|
|
@ -1884,6 +1884,15 @@ static ssize_t link_pipe(struct pipe_inode_info *ipipe,
|
|||
unsigned int i_tail, o_tail;
|
||||
ssize_t ret = 0;
|
||||
|
||||
retry:
|
||||
ret = ipipe_prep(ipipe, flags);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = opipe_prep(opipe, flags);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Potential ABBA deadlock, work around it by ordering lock
|
||||
* grabbing by pipe info address. Otherwise two different processes
|
||||
|
|
@ -1905,13 +1914,32 @@ static ssize_t link_pipe(struct pipe_inode_info *ipipe,
|
|||
i_head = ipipe->head;
|
||||
o_tail = opipe->tail;
|
||||
|
||||
if (pipe_empty(i_head, i_tail) && !ipipe->writers)
|
||||
break;
|
||||
|
||||
/*
|
||||
* If we have iterated all input buffers or run out of
|
||||
* output room, break.
|
||||
*/
|
||||
if (pipe_empty(i_head, i_tail) ||
|
||||
pipe_full(o_head, o_tail, opipe->max_usage))
|
||||
break;
|
||||
pipe_full(o_head, o_tail, opipe->max_usage)) {
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
if (flags & SPLICE_F_NONBLOCK) {
|
||||
ret = -EAGAIN;
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* We raced with another reader/writer and haven't
|
||||
* linked any buffers. A zero return value means EOF,
|
||||
* so retry instead.
|
||||
*/
|
||||
pipe_unlock(ipipe);
|
||||
pipe_unlock(opipe);
|
||||
goto retry;
|
||||
}
|
||||
|
||||
ibuf = pipe_buf(ipipe, i_tail);
|
||||
obuf = pipe_buf(opipe, o_head);
|
||||
|
|
@ -1982,16 +2010,7 @@ ssize_t do_tee(struct file *in, struct file *out, size_t len,
|
|||
if ((in->f_flags | out->f_flags) & O_NONBLOCK)
|
||||
flags |= SPLICE_F_NONBLOCK;
|
||||
|
||||
/*
|
||||
* Keep going, unless we encounter an error. The ipipe/opipe
|
||||
* ordering doesn't really matter.
|
||||
*/
|
||||
ret = ipipe_prep(ipipe, flags);
|
||||
if (!ret) {
|
||||
ret = opipe_prep(opipe, flags);
|
||||
if (!ret)
|
||||
ret = link_pipe(ipipe, opipe, len, flags);
|
||||
}
|
||||
ret = link_pipe(ipipe, opipe, len, flags);
|
||||
}
|
||||
|
||||
if (ret > 0) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user