-
Notifications
You must be signed in to change notification settings - Fork 7.9k
main/streams/streams: use copy_file_range() on Linux #8413
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
main/streams/streams.c
Outdated
@@ -1555,6 +1555,81 @@ PHPAPI zend_result _php_stream_copy_to_stream_ex(php_stream *src, php_stream *de | |||
return SUCCESS; | |||
} | |||
|
|||
#ifdef __linux__ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would it be possible to detect the support at configure time instead (ie freebsd supports it too). ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure - I thought about that, but feared it would break some obscure system where copy_file_range
exists but does something else.
I've amended the patch to include a configure-time check. (And a fix for the CI EOVERFLOW
failure, I hope.)
e3df41c
to
3558bef
Compare
After the amendmend which enabled a configure-time check for
It passes on my Linux box, but I don't have FreeBSD. Can somebody with FreeBSD please help and check why this fails? |
if no one does, will look into it in the next hour. |
seems on freebsd it can t copy a so large file, if I decrease by two the size the test pass. |
main/streams/streams.c
Outdated
php_stream_cast(dest, PHP_STREAM_AS_FD, (void*)&dest_fd, 0); | ||
|
||
/* clamp to INT_MAX to avoid EOVERFLOW */ | ||
const size_t cfr_max = MIN(maxlen, (size_t)INT_MAX); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't be SSIZE_MAX ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was not sure what the kernel's real limit was; I remember long ago I read that Linux's limit for all I/O was 2 GB which is INT_MAX. I'll find out!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's ... more complicated than that.
https://github.com/torvalds/linux/blob/v5.17/fs/read_write.c#L1434=
/* Ensure offsets don't wrap. */
if (pos_in + count < pos_in || pos_out + count < pos_out)
return -EOVERFLOW;
(Note that this code is buggy because the compiler is allowed to optimize this check away.)
(Also note that this check is rather clumsy because the count
variable is clamped to the remaining size of the file only after this check.)
SSIZE_MAX
is 2^63-1
on 64 bit CPUs, which means it's safe until you encounter a file of 2^63
bytes, which will not happen for a very loooong time. Should be safe enough to say "copy as much as you can", I guess.
As you suggested, |
it seems it s the same limit on freebsd however looking at the source most of code consumers use it as much as they can then fall back to the write/read workflow if it fails. |
Then changing the limit to |
3558bef
to
a12d33e
Compare
Just for correctness, I have force-pushed changing |
apparently the zip header gets corrupted after the copy in the freebsd case, copy_file_range does not copy the full data in one shot thus needs to copy chunk by chunk maybe. |
Hmmm, my change falls back to old-style copying if php-src/main/streams/streams.c Lines 1595 to 1601 in a12d33e
I tried to reproduce the problem by clamping to very small valus; for example, clamping to just 1:
This one bytes is successfully copied, and the operation is continued with classic read/write. Unit tests succeed that way. This still isn't the real problem, I think. |
I m guessing flags must remain 0 for Linux but for FreeBSD can you give |
What does flag 0x01000000 mean? |
Indeed not documented really but it is to return after 1 second. The reason I ask is it worked for me locally but wanted to confirm. |
a12d33e
to
fe251fb
Compare
OK, force-pushed with your suggested flag. Let's see if that makes the FreeBSD CI happy. That still isn't a proper solution. I suggest disabling |
Yes fair enough. |
"All checks have passed" with this kludge ... interesting! PHP maintainers, I'm waiting for instructions on how to proceed to get this PR merged. Merge with this kludge? Exclude FreeBSD from this feature for now, fix FreeBSD later? First fix FreeBSD properly (I need help with that!), then merge? |
|
fe251fb
to
94df69f
Compare
Aye. Force-pushed with |
Would it be possible to write a new test file for this case? I know some tests files are very large but that's something we try to prevent nowadays (as it makes troubleshooting harder see: https://qa.php.net/write-test.php#howbig). Otherwise not an expert but it LGTM |
Verify that the file offset has been moved by exactly the amount.
copy_file_range() is a Linux-specific system call which allows efficient copying between two file descriptors, eliminating the need to transfer data from the kernel to userspace and back. For networking file systems like NFS and Ceph, it even eliminates copying data to the client, and local filesystems like Btrfs and XFS can create shared extents.
94df69f
to
171ae38
Compare
@Girgias moved the new "interleaved" tests to a new test file. |
If CI is happy I'll merge it :) |
copy_file_range() is a Linux-specific system call which allows
efficient copying between two file descriptors, eliminating the need
to transfer data from the kernel to userspace and back. For
networking file systems like NFS and Ceph, it even eliminates copying
data to the client, and local filesystems like Btrfs and XFS can
create shared extents.