1
Fork 0

Call FileEncoder::finish in rmeta encoding

This commit is contained in:
Ben Kimock 2023-10-27 21:26:43 -04:00
parent c387f012b1
commit fbaa24ee35
14 changed files with 82 additions and 41 deletions

View file

@ -5,12 +5,13 @@ use std::io::{self, Write};
use std::marker::PhantomData;
use std::ops::Range;
use std::path::Path;
use std::path::PathBuf;
// -----------------------------------------------------------------------------
// Encoder
// -----------------------------------------------------------------------------
pub type FileEncodeResult = Result<usize, io::Error>;
pub type FileEncodeResult = Result<usize, (PathBuf, io::Error)>;
/// The size of the buffer in `FileEncoder`.
const BUF_SIZE: usize = 8192;
@ -34,6 +35,9 @@ pub struct FileEncoder {
// This is used to implement delayed error handling, as described in the
// comment on `trait Encoder`.
res: Result<(), io::Error>,
path: PathBuf,
#[cfg(debug_assertions)]
finished: bool,
}
impl FileEncoder {
@ -41,14 +45,18 @@ impl FileEncoder {
// File::create opens the file for writing only. When -Zmeta-stats is enabled, the metadata
// encoder rewinds the file to inspect what was written. So we need to always open the file
// for reading and writing.
let file = File::options().read(true).write(true).create(true).truncate(true).open(path)?;
let file =
File::options().read(true).write(true).create(true).truncate(true).open(&path)?;
Ok(FileEncoder {
buf: vec![0u8; BUF_SIZE].into_boxed_slice().try_into().unwrap(),
path: path.as_ref().into(),
buffered: 0,
flushed: 0,
file,
res: Ok(()),
#[cfg(debug_assertions)]
finished: false,
})
}
@ -63,6 +71,10 @@ impl FileEncoder {
#[cold]
#[inline(never)]
pub fn flush(&mut self) {
#[cfg(debug_assertions)]
{
self.finished = false;
}
if self.res.is_ok() {
self.res = self.file.write_all(&self.buf[..self.buffered]);
}
@ -74,6 +86,10 @@ impl FileEncoder {
&self.file
}
pub fn path(&self) -> &Path {
&self.path
}
#[inline]
fn buffer_empty(&mut self) -> &mut [u8] {
// SAFETY: self.buffered is inbounds as an invariant of the type
@ -97,6 +113,10 @@ impl FileEncoder {
#[inline]
fn write_all(&mut self, buf: &[u8]) {
#[cfg(debug_assertions)]
{
self.finished = false;
}
if let Some(dest) = self.buffer_empty().get_mut(..buf.len()) {
dest.copy_from_slice(buf);
self.buffered += buf.len();
@ -121,6 +141,10 @@ impl FileEncoder {
/// with one instruction, so while this does in some sense do wasted work, we come out ahead.
#[inline]
pub fn write_with<const N: usize>(&mut self, visitor: impl FnOnce(&mut [u8; N]) -> usize) {
#[cfg(debug_assertions)]
{
self.finished = false;
}
let flush_threshold = const { BUF_SIZE.checked_sub(N).unwrap() };
if std::intrinsics::unlikely(self.buffered > flush_threshold) {
self.flush();
@ -152,20 +176,25 @@ impl FileEncoder {
})
}
pub fn finish(mut self) -> Result<usize, io::Error> {
pub fn finish(&mut self) -> FileEncodeResult {
self.flush();
#[cfg(debug_assertions)]
{
self.finished = true;
}
match std::mem::replace(&mut self.res, Ok(())) {
Ok(()) => Ok(self.position()),
Err(e) => Err(e),
Err(e) => Err((self.path.clone(), e)),
}
}
}
#[cfg(debug_assertions)]
impl Drop for FileEncoder {
fn drop(&mut self) {
// Likely to be a no-op, because `finish` should have been called and
// it also flushes. But do it just in case.
self.flush();
if !std::thread::panicking() {
assert!(self.finished);
}
}
}