Skip to content

Commit 75195b5

Browse files
committed
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.
1 parent c3fe9e7 commit 75195b5

File tree

10 files changed

+84
-10
lines changed

10 files changed

+84
-10
lines changed

library/std/src/fs.rs

+35
Original file line numberDiff line numberDiff line change
@@ -1226,9 +1226,38 @@ impl Write for &File {
12261226
}
12271227
#[stable(feature = "rust1", since = "1.0.0")]
12281228
impl Seek for &File {
1229+
/// Seek to an offset, in bytes in a file.
1230+
///
1231+
/// See [`Seek::seek`] docs for more info.
1232+
///
1233+
/// # Platform-specific behavior
1234+
///
1235+
/// This function currently corresponds to the `lseek64` function on Unix
1236+
/// and the `SetFilePointerEx` function on Windows. Note that this [may
1237+
/// change in the future][changes].
1238+
///
1239+
/// [changes]: io#platform-specific-behavior
12291240
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
12301241
self.inner.seek(pos)
12311242
}
1243+
1244+
/// Returns the length of this file (in bytes).
1245+
///
1246+
/// See [`Seek::stream_len`] docs for more info.
1247+
///
1248+
/// # Platform-specific behavior
1249+
///
1250+
/// This function currently corresponds to the `statx` function on Linux
1251+
/// (with fallbacks) and the `GetFileSizeEx` function on Windows. Note that
1252+
/// this [may change in the future][changes].
1253+
///
1254+
/// [changes]: io#platform-specific-behavior
1255+
fn stream_len(&mut self) -> io::Result<u64> {
1256+
if let Some(result) = self.inner.size() {
1257+
return result;
1258+
}
1259+
io::stream_len_default(self)
1260+
}
12321261
}
12331262

12341263
#[stable(feature = "rust1", since = "1.0.0")]
@@ -1275,6 +1304,9 @@ impl Seek for File {
12751304
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
12761305
(&*self).seek(pos)
12771306
}
1307+
fn stream_len(&mut self) -> io::Result<u64> {
1308+
(&*self).stream_len()
1309+
}
12781310
}
12791311

12801312
#[stable(feature = "io_traits_arc", since = "1.73.0")]
@@ -1321,6 +1353,9 @@ impl Seek for Arc<File> {
13211353
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
13221354
(&**self).seek(pos)
13231355
}
1356+
fn stream_len(&mut self) -> io::Result<u64> {
1357+
(&**self).stream_len()
1358+
}
13241359
}
13251360

13261361
impl OpenOptions {

library/std/src/io/mod.rs

+14-10
Original file line numberDiff line numberDiff line change
@@ -2051,16 +2051,7 @@ pub trait Seek {
20512051
/// ```
20522052
#[unstable(feature = "seek_stream_len", issue = "59359")]
20532053
fn stream_len(&mut self) -> Result<u64> {
2054-
let old_pos = self.stream_position()?;
2055-
let len = self.seek(SeekFrom::End(0))?;
2056-
2057-
// Avoid seeking a third time when we were already at the end of the
2058-
// stream. The branch is usually way cheaper than a seek operation.
2059-
if old_pos != len {
2060-
self.seek(SeekFrom::Start(old_pos))?;
2061-
}
2062-
2063-
Ok(len)
2054+
stream_len_default(self)
20642055
}
20652056

20662057
/// Returns the current seek position from the start of the stream.
@@ -2121,6 +2112,19 @@ pub trait Seek {
21212112
}
21222113
}
21232114

2115+
pub(crate) fn stream_len_default<T: Seek + ?Sized>(self_: &mut T) -> Result<u64> {
2116+
let old_pos = self_.stream_position()?;
2117+
let len = self_.seek(SeekFrom::End(0))?;
2118+
2119+
// Avoid seeking a third time when we were already at the end of the
2120+
// stream. The branch is usually way cheaper than a seek operation.
2121+
if old_pos != len {
2122+
self_.seek(SeekFrom::Start(old_pos))?;
2123+
}
2124+
2125+
Ok(len)
2126+
}
2127+
21242128
/// Enumeration of possible methods to seek within an I/O object.
21252129
///
21262130
/// It is used by the [`Seek`] trait.

library/std/src/sys/pal/hermit/fs.rs

+4
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,10 @@ impl File {
425425
Err(Error::from_raw_os_error(22))
426426
}
427427

428+
pub fn size(&self) -> Option<io::Result<u64>> {
429+
None
430+
}
431+
428432
pub fn duplicate(&self) -> io::Result<File> {
429433
Err(Error::from_raw_os_error(22))
430434
}

library/std/src/sys/pal/solid/fs.rs

+4
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,10 @@ impl File {
465465
}
466466
}
467467

468+
pub fn size(&self) -> Option<io::Result<u64>> {
469+
None
470+
}
471+
468472
pub fn duplicate(&self) -> io::Result<File> {
469473
unsupported()
470474
}

library/std/src/sys/pal/unix/fs.rs

+9
Original file line numberDiff line numberDiff line change
@@ -1438,6 +1438,15 @@ impl File {
14381438
Ok(n as u64)
14391439
}
14401440

1441+
pub fn size(&self) -> Option<io::Result<u64>> {
1442+
match self.file_attr().map(|attr| attr.size()) {
1443+
// Fall back to default implementation if the returned size is 0,
1444+
// we might be in a proc mount.
1445+
Ok(0) => None,
1446+
result => Some(result),
1447+
}
1448+
}
1449+
14411450
pub fn duplicate(&self) -> io::Result<File> {
14421451
self.0.duplicate().map(File)
14431452
}

library/std/src/sys/pal/unsupported/fs.rs

+4
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,10 @@ impl File {
258258
self.0
259259
}
260260

261+
pub fn size(&self) -> io::Result<u64> {
262+
self.0
263+
}
264+
261265
pub fn duplicate(&self) -> io::Result<File> {
262266
self.0
263267
}

library/std/src/sys/pal/wasi/fs.rs

+4
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,10 @@ impl File {
517517
self.fd.seek(pos)
518518
}
519519

520+
pub fn size(&self) -> Option<io::Result<u64>> {
521+
None
522+
}
523+
520524
pub fn duplicate(&self) -> io::Result<File> {
521525
// https://github.com/CraneStation/wasmtime/blob/master/docs/WASI-rationale.md#why-no-dup
522526
unsupported()

library/std/src/sys/pal/windows/c/bindings.txt

+1
Original file line numberDiff line numberDiff line change
@@ -2156,6 +2156,7 @@ GetExitCodeProcess
21562156
GetFileAttributesW
21572157
GetFileInformationByHandle
21582158
GetFileInformationByHandleEx
2159+
GetFileSizeEx
21592160
GetFileType
21602161
GETFINALPATHNAMEBYHANDLE_FLAGS
21612162
GetFinalPathNameByHandleW

library/std/src/sys/pal/windows/c/windows_sys.rs

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ windows_targets::link!("kernel32.dll" "system" fn GetExitCodeProcess(hprocess :
4444
windows_targets::link!("kernel32.dll" "system" fn GetFileAttributesW(lpfilename : PCWSTR) -> u32);
4545
windows_targets::link!("kernel32.dll" "system" fn GetFileInformationByHandle(hfile : HANDLE, lpfileinformation : *mut BY_HANDLE_FILE_INFORMATION) -> BOOL);
4646
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);
47+
windows_targets::link!("kernel32.dll" "system" fn GetFileSizeEx(hfile : HANDLE, lpfilesize : *mut i64) -> BOOL);
4748
windows_targets::link!("kernel32.dll" "system" fn GetFileType(hfile : HANDLE) -> FILE_TYPE);
4849
windows_targets::link!("kernel32.dll" "system" fn GetFinalPathNameByHandleW(hfile : HANDLE, lpszfilepath : PWSTR, cchfilepath : u32, dwflags : GETFINALPATHNAMEBYHANDLE_FLAGS) -> u32);
4950
windows_targets::link!("kernel32.dll" "system" fn GetFullPathNameW(lpfilename : PCWSTR, nbufferlength : u32, lpbuffer : PWSTR, lpfilepart : *mut PWSTR) -> u32);

library/std/src/sys/pal/windows/fs.rs

+8
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,14 @@ impl File {
631631
Ok(newpos as u64)
632632
}
633633

634+
pub fn size(&self) -> Option<io::Result<u64>> {
635+
let mut result = 0;
636+
Some(
637+
cvt(unsafe { c::GetFileSizeEx(self.handle.as_raw_handle(), &mut result) })
638+
.map(|_| result as u64),
639+
)
640+
}
641+
634642
pub fn duplicate(&self) -> io::Result<File> {
635643
Ok(Self { handle: self.handle.try_clone()? })
636644
}

0 commit comments

Comments
 (0)