From b2aa970a0a03c117306f48d75a4c3125a8cc9957 Mon Sep 17 00:00:00 2001 From: Nickolai Zeldovich Date: Sat, 17 Oct 2026 22:55:41 -0400 Subject: [PATCH] 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: aadd06e5c56b ("[PATCH] splice: fix problems with sys_tee()") Signed-off-by: Nickolai Zeldovich Link: https://patch.msgid.link/20261018025541.799135-1-nickolai@csail.mit.edu Signed-off-by: Christian Brauner (Amutable) --- fs/splice.c | 43 +++++++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/fs/splice.c b/fs/splice.c index f4bce3589067..e6d43329ed3f 100644 --- a/fs/splice.c +++ b/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) {