mirror of
https://github.com/cargo-bins/cargo-binstall.git
synced 2025-05-05 11:40:04 +00:00
Auto remove file in AsyncFileWriter
unless done is called. Also moves creation of the dir/file into the blocking thread to avoid blocking. Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>
This commit is contained in:
parent
5d70f61317
commit
911c52d8e1
2 changed files with 70 additions and 21 deletions
|
@ -3,37 +3,60 @@ use std::io::{self, Write};
|
|||
use std::path::Path;
|
||||
|
||||
use bytes::Bytes;
|
||||
use scopeguard::{guard, Always, ScopeGuard};
|
||||
use tokio::{sync::mpsc, task::spawn_blocking};
|
||||
|
||||
use super::AutoAbortJoinHandle;
|
||||
|
||||
enum Content {
|
||||
/// Data to write to file
|
||||
Data(Bytes),
|
||||
|
||||
/// Abort the writing and remove the file.
|
||||
Abort,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AsyncFileWriter {
|
||||
struct AsyncFileWriterInner {
|
||||
/// Use AutoAbortJoinHandle so that the task
|
||||
/// will be cancelled on failure.
|
||||
handle: AutoAbortJoinHandle<io::Result<()>>,
|
||||
tx: mpsc::Sender<Bytes>,
|
||||
tx: mpsc::Sender<Content>,
|
||||
}
|
||||
|
||||
impl AsyncFileWriter {
|
||||
pub fn new(path: &Path) -> io::Result<Self> {
|
||||
fs::create_dir_all(path.parent().unwrap())?;
|
||||
|
||||
let mut file = fs::File::create(path)?;
|
||||
let (tx, rx) = mpsc::channel::<Bytes>(100);
|
||||
impl AsyncFileWriterInner {
|
||||
fn new(path: &Path) -> io::Result<Self> {
|
||||
let path = path.to_owned();
|
||||
let (tx, rx) = mpsc::channel::<Content>(100);
|
||||
|
||||
let handle = AutoAbortJoinHandle::new(spawn_blocking(move || {
|
||||
// close rx on error so that tx.send will return an error
|
||||
let mut rx = scopeguard::guard(rx, |mut rx| {
|
||||
let mut rx = guard(rx, |mut rx| {
|
||||
rx.close();
|
||||
});
|
||||
|
||||
while let Some(bytes) = rx.blocking_recv() {
|
||||
file.write_all(&*bytes)?;
|
||||
fs::create_dir_all(path.parent().unwrap())?;
|
||||
let mut file = fs::File::create(&path)?;
|
||||
|
||||
// remove it unless the operation isn't aborted and no write
|
||||
// fails.
|
||||
let remove_guard = guard(path, |path| {
|
||||
fs::remove_file(path).ok();
|
||||
});
|
||||
|
||||
while let Some(content) = rx.blocking_recv() {
|
||||
match content {
|
||||
Content::Data(bytes) => file.write_all(&*bytes)?,
|
||||
Content::Abort => return Err(io::Error::new(io::ErrorKind::Other, "Aborted")),
|
||||
}
|
||||
}
|
||||
|
||||
file.flush()?;
|
||||
|
||||
// Operation isn't aborted and all writes succeed,
|
||||
// disarm the remove_guard.
|
||||
ScopeGuard::into_inner(remove_guard);
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
|
||||
|
@ -42,8 +65,8 @@ impl AsyncFileWriter {
|
|||
|
||||
/// Upon error, this writer shall not be reused.
|
||||
/// Otherwise, `Self::done` would panic.
|
||||
pub async fn write(&mut self, bytes: Bytes) -> io::Result<()> {
|
||||
if self.tx.send(bytes).await.is_err() {
|
||||
async fn write(&mut self, bytes: Bytes) -> io::Result<()> {
|
||||
if self.tx.send(Content::Data(bytes)).await.is_err() {
|
||||
// task failed
|
||||
Err(Self::wait(&mut self.handle).await.expect_err(
|
||||
"Implementation bug: write task finished successfully before all writes are done",
|
||||
|
@ -53,7 +76,7 @@ impl AsyncFileWriter {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn done(mut self) -> io::Result<()> {
|
||||
async fn done(mut self) -> io::Result<()> {
|
||||
// Drop tx as soon as possible so that the task would wrap up what it
|
||||
// was doing and flush out all the pending data.
|
||||
drop(self.tx);
|
||||
|
@ -67,4 +90,37 @@ impl AsyncFileWriter {
|
|||
Err(join_err) => Err(io::Error::new(io::ErrorKind::Other, join_err)),
|
||||
}
|
||||
}
|
||||
|
||||
fn abort(self) {
|
||||
let tx = self.tx;
|
||||
// If Self::write fail, then the task is already tear down,
|
||||
// tx closed and no need to abort.
|
||||
if !tx.is_closed() {
|
||||
// Use send here because blocking_send would panic if used
|
||||
// in async threads.
|
||||
tokio::spawn(async move {
|
||||
tx.send(Content::Abort).await.ok();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// AsyncFileWriter removes the file if `done` isn't called.
|
||||
#[derive(Debug)]
|
||||
pub struct AsyncFileWriter(ScopeGuard<AsyncFileWriterInner, fn(AsyncFileWriterInner), Always>);
|
||||
|
||||
impl AsyncFileWriter {
|
||||
pub fn new(path: &Path) -> io::Result<Self> {
|
||||
AsyncFileWriterInner::new(path).map(|inner| Self(guard(inner, AsyncFileWriterInner::abort)))
|
||||
}
|
||||
|
||||
/// Upon error, this writer shall not be reused.
|
||||
/// Otherwise, `Self::done` would panic.
|
||||
pub async fn write(&mut self, bytes: Bytes) -> io::Result<()> {
|
||||
self.0.write(bytes).await
|
||||
}
|
||||
|
||||
pub async fn done(self) -> io::Result<()> {
|
||||
ScopeGuard::into_inner(self.0).done().await
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue