Skip to content

Size snapshot #46340

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

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions src/libstd/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,25 @@ impl Read for File {
self.inner.read(buf)
}

fn size_snapshot(&self) -> Option<usize> {
// Ignore I/O errors; we're just querying the size and position of an
// already-open file in preparation for reading from it.
if let Ok(meta) = self.metadata() {
let len = meta.len();
if let Ok(position) = self.inner.seek(SeekFrom::Current(0)) {
if let Some(distance) = len.checked_sub(position) {
let size = distance as usize;
// Don't trust a length of zero. For example, "pseudofiles"
// on Linux like /proc/meminfo report a size of 0.
if size != 0 && size as u64 == distance {
return Some(size);
}
}
}
}
None
}

#[inline]
unsafe fn initializer(&self) -> Initializer {
Initializer::nop()
Expand All @@ -473,6 +492,10 @@ impl<'a> Read for &'a File {
self.inner.read(buf)
}

fn size_snapshot(&self) -> Option<usize> {
(**self).size_snapshot()
}

#[inline]
unsafe fn initializer(&self) -> Initializer {
Initializer::nop()
Expand Down Expand Up @@ -782,6 +805,8 @@ impl Metadata {

/// Returns the size of the file, in bytes, this metadata is for.
///
/// As a special case, a size of `0` indicates that the size is unknown.
///
/// # Examples
///
/// ```
Expand Down
9 changes: 9 additions & 0 deletions src/libstd/io/buffered.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,15 @@ impl<R: Read> Read for BufReader<R> {
Ok(nread)
}

#[inline]
fn size_snapshot(&self) -> Option<usize> {
let buffered_len = self.cap - self.pos;
if let Some(size) = self.inner.size_snapshot() {
return buffered_len.checked_add(size);
}
None
}

// we can't skip unconditionally because of the large buffer case in read.
unsafe fn initializer(&self) -> Initializer {
self.inner.initializer()
Expand Down
10 changes: 10 additions & 0 deletions src/libstd/io/cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,16 @@ impl<T> Read for Cursor<T> where T: AsRef<[u8]> {
Ok(())
}

fn size_snapshot(&self) -> Option<usize> {
if let Some(diff) = (self.inner.as_ref().len() as u64).checked_sub(self.pos) {
let size = diff as usize;
if size as u64 == diff {
return Some(size);
}
}
None
}

#[inline]
unsafe fn initializer(&self) -> Initializer {
Initializer::nop()
Expand Down
15 changes: 15 additions & 0 deletions src/libstd/io/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ impl<'a, R: Read + ?Sized> Read for &'a mut R {
(**self).read(buf)
}

#[inline]
fn size_snapshot(&self) -> Option<usize> {
(**self).size_snapshot()
}

#[inline]
unsafe fn initializer(&self) -> Initializer {
(**self).initializer()
Expand Down Expand Up @@ -92,6 +97,11 @@ impl<R: Read + ?Sized> Read for Box<R> {
(**self).read(buf)
}

#[inline]
fn size_snapshot(&self) -> Option<usize> {
(**self).size_snapshot()
}

#[inline]
unsafe fn initializer(&self) -> Initializer {
(**self).initializer()
Expand Down Expand Up @@ -181,6 +191,11 @@ impl<'a> Read for &'a [u8] {
Ok(amt)
}

#[inline]
fn size_snapshot(&self) -> Option<usize> {
Some(self.len())
}

#[inline]
unsafe fn initializer(&self) -> Initializer {
Initializer::nop()
Expand Down
91 changes: 74 additions & 17 deletions src/libstd/io/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -366,32 +366,50 @@ fn append_to_string<F>(buf: &mut String, f: F) -> Result<usize>
fn read_to_end<R: Read + ?Sized>(r: &mut R, buf: &mut Vec<u8>) -> Result<usize> {
let start_len = buf.len();
let mut g = Guard { len: buf.len(), buf: buf };
let ret;
let size_snapshot = r.size_snapshot();

// Determine the size to start reading with.
let initial_resize_len = if let Some(size) = size_snapshot {
// We know the (present) size. Don't use reserve_exact because when the
// initial size of buf is zero, reserve should still give us exactly the
// size we request, and when it's non-zero, we're concatenating things.
g.buf.reserve(size);
start_len + size
} else {
// We don't know the size. Start with a relatively small guess.
g.buf.reserve(32);
g.buf.capacity()
};
unsafe {
g.buf.set_len(initial_resize_len);
r.initializer().initialize(&mut g.buf[g.len..]);
}

loop {
if g.len == g.buf.len() {
unsafe {
g.buf.reserve(32);
let capacity = g.buf.capacity();
g.buf.set_len(capacity);
r.initializer().initialize(&mut g.buf[g.len..]);
}
match r.read(&mut g.buf[g.len..]) {
Ok(0) => break,
Ok(n) => g.len += n,
Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
Err(e) => return Err(e),
}

match r.read(&mut g.buf[g.len..]) {
Ok(0) => {
ret = Ok(g.len - start_len);
if g.len == g.buf.len() {
if size_snapshot.is_some() {
// We finished what the snapshot told us, so we're done.
debug_assert_eq!(size_snapshot.unwrap(), g.len - start_len);
break;
}
Ok(n) => g.len += n,
Err(ref e) if e.kind() == ErrorKind::Interrupted => {}
Err(e) => {
ret = Err(e);
break;
// We've used up our available buffer space; allocate more.
g.buf.reserve(32);
let capacity = g.buf.capacity();
unsafe {
g.buf.set_len(capacity);
r.initializer().initialize(&mut g.buf[g.len..]);
}
}
}

ret
Ok(g.len - start_len)
}

/// The `Read` trait allows for reading bytes from a source.
Expand Down Expand Up @@ -553,6 +571,21 @@ pub trait Read {
Initializer::zeroing()
}

/// Return a snapshot of how many bytes would be read from this source until EOF
/// if read immediately, or None if that is unknown. Depending on the source, the
/// size may change at any time, so this isn't a guarantee that exactly that number
/// of bytes will actually be read.
///
/// This is used by [`read_to_end`] and [`read_to_string`] to pre-allocate a memory buffer.
///
/// [`read_to_end`]: #method.read_to_end
/// [`read_to_string`]: #method.read_to_string
#[unstable(feature = "read_size_snapshot", issue = /* FIXME */ "0")]
#[inline]
fn size_snapshot(&self) -> Option<usize> {
None
}

/// Read all bytes until EOF in this source, placing them into `buf`.
///
/// All bytes read from this source will be appended to the specified buffer
Expand Down Expand Up @@ -1729,6 +1762,19 @@ impl<T: Read, U: Read> Read for Chain<T, U> {
self.second.read(buf)
}

fn size_snapshot(&self) -> Option<usize> {
if self.done_first {
self.second.size_snapshot()
} else {
if let Some(second_size) = self.second.size_snapshot() {
if let Some(first_size) = self.first.size_snapshot() {
return first_size.checked_add(second_size);
}
}
None
}
}

unsafe fn initializer(&self) -> Initializer {
let initializer = self.first.initializer();
if initializer.should_initialize() {
Expand Down Expand Up @@ -1927,6 +1973,17 @@ impl<T: Read> Read for Take<T> {
Ok(n)
}

fn size_snapshot(&self) -> Option<usize> {
if let Some(inner_size) = self.inner.size_snapshot() {
let min = cmp::min(self.limit, inner_size as u64);
let size = min as usize;
if size as u64 == min {
return Some(size);
}
}
None
}

unsafe fn initializer(&self) -> Initializer {
self.inner.initializer()
}
Expand Down