From: Jens Axboe <axboe@kernel.dk>
To: Christian Brauner <brauner@kernel.org>,
Max Kellermann <max.kellermann@ionos.com>
Cc: Alexander Viro <viro@zeniv.linux.org.uk>,
linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH] fs/splice: don't block splice_direct_to_actor() after data was read
Date: Wed, 20 Sep 2023 11:28:17 -0600 [thread overview]
Message-ID: <f37c00c5-467a-4339-9e20-ca5a12905cd3@kernel.dk> (raw)
In-Reply-To: <20230919-kommilitonen-hufen-d270d1568897@brauner>
On 9/19/23 8:18 AM, Christian Brauner wrote:
> [+Cc Jens]
>
> On Tue, Sep 19, 2023 at 10:12:58AM +0200, Max Kellermann wrote:
>> If userspace calls sendfile() with a very large "count" parameter, the
>> kernel can block for a very long time until 2 GiB (0x7ffff000 bytes)
>> have been read from the hard disk and pushed into the socket buffer.
>>
>> Usually, that is not a problem, because the socket write buffer gets
>> filled quickly, and if the socket is non-blocking, the last
>> direct_splice_actor() call will return -EAGAIN, causing
>> splice_direct_to_actor() to break from the loop, and sendfile() will
>> return a partial transfer.
>>
>> However, if the network happens to be faster than the hard disk, and
>> the socket buffer keeps getting drained between two
>> generic_file_read_iter() calls, the sendfile() system call can keep
>> running for a long time, blocking for disk I/O over and over.
>>
>> That is undesirable, because it can block the calling process for too
>> long. I discovered a problem where nginx would block for so long that
>> it would drop the HTTP connection because the kernel had just
>> transferred 2 GiB in one call, and the HTTP socket was not writable
>> (EPOLLOUT) for more than 60 seconds, resulting in a timeout:
>>
>> sendfile(4, 12, [5518919528] => [5884939344], 1813448856) = 366019816 <3.033067>
>> sendfile(4, 12, [5884939344], 1447429040) = -1 EAGAIN (Resource temporarily unavailable) <0.000037>
>> epoll_wait(9, [{EPOLLOUT, {u32=2181955104, u64=140572166585888}}], 512, 60000) = 1 <0.003355>
>> gettimeofday({tv_sec=1667508799, tv_usec=201201}, NULL) = 0 <0.000024>
>> sendfile(4, 12, [5884939344] => [8032418896], 2147480496) = 2147479552 <10.727970>
>> writev(4, [], 0) = 0 <0.000439>
>> epoll_wait(9, [], 512, 60000) = 0 <60.060430>
>> gettimeofday({tv_sec=1667508869, tv_usec=991046}, NULL) = 0 <0.000078>
>> write(5, "10.40.5.23 - - [03/Nov/2022:21:5"..., 124) = 124 <0.001097>
>> close(12) = 0 <0.000063>
>> close(4) = 0 <0.000091>
>>
>> In newer nginx versions (since 1.21.4), this problem was worked around
>> by defaulting "sendfile_max_chunk" to 2 MiB:
>>
>> https://github.com/nginx/nginx/commit/5636e7f7b4
>>
>> Instead of asking userspace to provide an artificial upper limit, I'd
>> like the kernel to block for disk I/O at most once, and then pass back
>> control to userspace.
>>
>> There is prior art for this kind of behavior in filemap_read():
>>
>> /*
>> * If we've already successfully copied some data, then we
>> * can no longer safely return -EIOCBQUEUED. Hence mark
>> * an async read NOWAIT at that point.
>> */
>> if ((iocb->ki_flags & IOCB_WAITQ) && already_read)
>> iocb->ki_flags |= IOCB_NOWAIT;
>>
>> This modifies the caller-provided "struct kiocb", which has an effect
>> on repeated filemap_read() calls. This effect however vanishes
>> because the "struct kiocb" is not persistent; splice_direct_to_actor()
>> doesn't have one, and each generic_file_splice_read() call initializes
>> a new one, losing the "IOCB_NOWAIT" flag that was injected by
>> filemap_read().
>>
>> There was no way to make generic_file_splice_read() aware that
>> IOCB_NOWAIT was desired because some data had already been transferred
>> in a previous call:
>>
>> - checking whether the input file has O_NONBLOCK doesn't work because
>> this should be fixed even if the input file is not non-blocking
>>
>> - the SPLICE_F_NONBLOCK flag is not appropriate because it affects
>> only whether pipe operations are non-blocking, not whether
>> file/socket operations are non-blocking
>>
>> Since there are no other parameters, I suggest adding the
>> SPLICE_F_NOWAIT flag, which is similar to SPLICE_F_NONBLOCK, but
>> affects the "non-pipe" file descriptor passed to sendfile() or
>> splice(). It translates to IOCB_NOWAIT for regular files. For now, I
>> have documented the flag to be kernel-internal with a high bit, like
>> io_uring does with SPLICE_F_FD_IN_FIXED, but making this part of the
>> system call ABI may be a good idea as well.
I think adding the flag for this case makes sense, and also exposing it
on the UAPI side. My only concern is full coverage of it. We can't
really have a SPLICE_F_NOWAIT flag that only applies to some cases.
That said, asking for a 2G splice, and getting a 2G splice no matter how
slow it may be, is a bit of a "doctor it hurts when I..." scenario.
--
Jens Axboe
next prev parent reply other threads:[~2023-09-20 17:28 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-09-19 8:12 [PATCH] fs/splice: don't block splice_direct_to_actor() after data was read Max Kellermann
2023-09-19 14:18 ` Christian Brauner
2023-09-20 17:28 ` Jens Axboe [this message]
2023-09-20 18:16 ` Max Kellermann
2023-09-25 13:10 ` Christian Brauner
2023-09-26 6:36 ` [PATCH v2] " Max Kellermann
2023-09-26 10:21 ` Christian Brauner
2023-09-26 10:41 ` Max Kellermann
2023-09-26 12:26 ` Christian Brauner
2023-09-26 6:39 ` [PATCH] " Max Kellermann
-- strict thread matches above, loose matches on Subject: below --
2022-11-05 9:04 Max Kellermann
2022-11-08 7:37 ` Christoph Hellwig
2022-11-20 13:19 ` kernel test robot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=f37c00c5-467a-4339-9e20-ca5a12905cd3@kernel.dk \
--to=axboe@kernel.dk \
--cc=brauner@kernel.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=max.kellermann@ionos.com \
--cc=viro@zeniv.linux.org.uk \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.