Rollup merge of #125087 - tbu-:pr_file_stream_len, r=ChrisDenton

Optimize `Seek::stream_len` impl for `File`

It uses the file metadata on Unix with a fallback for files incorrectly reported as zero-sized. It uses `GetFileSizeEx` on Windows.

This reduces the number of syscalls needed for determining the file size of an open file from 3 to 1.
This commit is contained in:
Matthias Krüger 2025-06-06 00:58:42 +02:00 committed by GitHub
commit c141cbf263
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 90 additions and 11 deletions

View file

@ -1311,9 +1311,39 @@ impl Write for &File {
}
#[stable(feature = "rust1", since = "1.0.0")]
impl Seek for &File {
/// Seek to an offset, in bytes in a file.
///
/// See [`Seek::seek`] docs for more info.
///
/// # Platform-specific behavior
///
/// This function currently corresponds to the `lseek64` function on Unix
/// and the `SetFilePointerEx` function on Windows. Note that this [may
/// change in the future][changes].
///
/// [changes]: io#platform-specific-behavior
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
self.inner.seek(pos)
}
/// Returns the length of this file (in bytes).
///
/// See [`Seek::stream_len`] docs for more info.
///
/// # Platform-specific behavior
///
/// This function currently corresponds to the `statx` function on Linux
/// (with fallbacks) and the `GetFileSizeEx` function on Windows. Note that
/// this [may change in the future][changes].
///
/// [changes]: io#platform-specific-behavior
fn stream_len(&mut self) -> io::Result<u64> {
if let Some(result) = self.inner.size() {
return result;
}
io::stream_len_default(self)
}
fn stream_position(&mut self) -> io::Result<u64> {
self.inner.tell()
}
@ -1363,6 +1393,9 @@ impl Seek for File {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
(&*self).seek(pos)
}
fn stream_len(&mut self) -> io::Result<u64> {
(&*self).stream_len()
}
fn stream_position(&mut self) -> io::Result<u64> {
(&*self).stream_position()
}
@ -1412,6 +1445,9 @@ impl Seek for Arc<File> {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
(&**self).seek(pos)
}
fn stream_len(&mut self) -> io::Result<u64> {
(&**self).stream_len()
}
fn stream_position(&mut self) -> io::Result<u64> {
(&**self).stream_position()
}

View file

@ -2028,7 +2028,7 @@ pub trait Seek {
/// Returns the length of this stream (in bytes).
///
/// This method is implemented using up to three seek operations. If this
/// The default implementation uses up to three seek operations. If this
/// method returns successfully, the seek position is unchanged (i.e. the
/// position before calling this method is the same as afterwards).
/// However, if this method returns an error, the seek position is
@ -2062,16 +2062,7 @@ pub trait Seek {
/// ```
#[unstable(feature = "seek_stream_len", issue = "59359")]
fn stream_len(&mut self) -> Result<u64> {
let old_pos = self.stream_position()?;
let len = self.seek(SeekFrom::End(0))?;
// Avoid seeking a third time when we were already at the end of the
// stream. The branch is usually way cheaper than a seek operation.
if old_pos != len {
self.seek(SeekFrom::Start(old_pos))?;
}
Ok(len)
stream_len_default(self)
}
/// Returns the current seek position from the start of the stream.
@ -2132,6 +2123,19 @@ pub trait Seek {
}
}
pub(crate) fn stream_len_default<T: Seek + ?Sized>(self_: &mut T) -> Result<u64> {
let old_pos = self_.stream_position()?;
let len = self_.seek(SeekFrom::End(0))?;
// Avoid seeking a third time when we were already at the end of the
// stream. The branch is usually way cheaper than a seek operation.
if old_pos != len {
self_.seek(SeekFrom::Start(old_pos))?;
}
Ok(len)
}
/// Enumeration of possible methods to seek within an I/O object.
///
/// It is used by the [`Seek`] trait.

View file

@ -422,6 +422,10 @@ impl File {
self.0.seek(pos)
}
pub fn size(&self) -> Option<io::Result<u64>> {
None
}
pub fn tell(&self) -> io::Result<u64> {
self.0.tell()
}

View file

@ -459,6 +459,10 @@ impl File {
self.tell()
}
pub fn size(&self) -> Option<io::Result<u64>> {
None
}
pub fn tell(&self) -> io::Result<u64> {
unsafe {
let mut out_offset = MaybeUninit::uninit();

View file

@ -280,6 +280,10 @@ impl File {
self.0
}
pub fn size(&self) -> Option<io::Result<u64>> {
self.0
}
pub fn tell(&self) -> io::Result<u64> {
self.0
}

View file

@ -1464,6 +1464,15 @@ impl File {
Ok(n as u64)
}
pub fn size(&self) -> Option<io::Result<u64>> {
match self.file_attr().map(|attr| attr.size()) {
// Fall back to default implementation if the returned size is 0,
// we might be in a proc mount.
Ok(0) => None,
result => Some(result),
}
}
pub fn tell(&self) -> io::Result<u64> {
self.seek(SeekFrom::Current(0))
}

View file

@ -259,6 +259,10 @@ impl File {
self.0
}
pub fn size(&self) -> Option<io::Result<u64>> {
self.0
}
pub fn tell(&self) -> io::Result<u64> {
self.0
}

View file

@ -516,6 +516,10 @@ impl File {
self.fd.seek(pos)
}
pub fn size(&self) -> Option<io::Result<u64>> {
None
}
pub fn tell(&self) -> io::Result<u64> {
self.fd.tell()
}

View file

@ -616,6 +616,14 @@ impl File {
Ok(newpos as u64)
}
pub fn size(&self) -> Option<io::Result<u64>> {
let mut result = 0;
Some(
cvt(unsafe { c::GetFileSizeEx(self.handle.as_raw_handle(), &mut result) })
.map(|_| result as u64),
)
}
pub fn tell(&self) -> io::Result<u64> {
self.seek(SeekFrom::Current(0))
}

View file

@ -2156,6 +2156,7 @@ GetExitCodeProcess
GetFileAttributesW
GetFileInformationByHandle
GetFileInformationByHandleEx
GetFileSizeEx
GetFileType
GETFINALPATHNAMEBYHANDLE_FLAGS
GetFinalPathNameByHandleW

View file

@ -44,6 +44,7 @@ windows_targets::link!("kernel32.dll" "system" fn GetExitCodeProcess(hprocess :
windows_targets::link!("kernel32.dll" "system" fn GetFileAttributesW(lpfilename : PCWSTR) -> u32);
windows_targets::link!("kernel32.dll" "system" fn GetFileInformationByHandle(hfile : HANDLE, lpfileinformation : *mut BY_HANDLE_FILE_INFORMATION) -> BOOL);
windows_targets::link!("kernel32.dll" "system" fn GetFileInformationByHandleEx(hfile : HANDLE, fileinformationclass : FILE_INFO_BY_HANDLE_CLASS, lpfileinformation : *mut core::ffi::c_void, dwbuffersize : u32) -> BOOL);
windows_targets::link!("kernel32.dll" "system" fn GetFileSizeEx(hfile : HANDLE, lpfilesize : *mut i64) -> BOOL);
windows_targets::link!("kernel32.dll" "system" fn GetFileType(hfile : HANDLE) -> FILE_TYPE);
windows_targets::link!("kernel32.dll" "system" fn GetFinalPathNameByHandleW(hfile : HANDLE, lpszfilepath : PWSTR, cchfilepath : u32, dwflags : GETFINALPATHNAMEBYHANDLE_FLAGS) -> u32);
windows_targets::link!("kernel32.dll" "system" fn GetFullPathNameW(lpfilename : PCWSTR, nbufferlength : u32, lpbuffer : PWSTR, lpfilepart : *mut PWSTR) -> u32);