vfs: move conflict handling from pyworker to vfs write

Summary:
Previously, `write` can fail because the destination file exists as a
directory, or the parent directory is missing. pyworker handles those cases
by calling `clear_conflicts` to remove conflicted directories and create
missing parent directories and retry `write`. Practically, for all `write`
usecases (including checkout) we always want the `clear_conflicts` behavior.
Therefore, move `clear_conflicts` to vfs `write` and make it private.

Reviewed By: quark-zju

Differential Revision: D26257829

fbshipit-source-id: 03d1da0767202edba61c47ae5654847c0ea3b33e
This commit is contained in:
Andrey Chursin 2021-02-09 17:02:22 -08:00 committed by Facebook GitHub Bot
parent d9232f1db3
commit 14064f8582
3 changed files with 27 additions and 22 deletions

View File

@ -151,25 +151,7 @@ fn update(state: &WriterState, key: Key, flag: Option<UpdateFlag>) -> Result<usi
let content = redact_if_needed(content);
// Fast path: let's try to open the file directly, we'll handle the failure only if this fails.
match state.working_copy.write(&key.path, &content, flag) {
Ok(size) => Ok(size),
Err(e) => {
// Ideally, we shouldn't need to retry for some failures, but this is the slow path, any
// failures not due to a conflicting file would show up here again, so let's not worry
// about it.
state
.working_copy
.clear_conflicts(&key.path)
.with_context(|| {
format!("Can't clear conflicts after handling error \"{:?}\"", e)
})?;
state
.working_copy
.write(&key.path, &content, flag)
.with_context(|| format!("Can't write after handling error \"{:?}\"", e))
}
}
state.working_copy.write(&key.path, &content, flag)
}
fn threaded_writer(

View File

@ -166,8 +166,6 @@ impl CheckoutPlan {
// As of today tokio::fs operations do the same.
// Since we do multiple fs calls inside, it is beneficial to 'pack'
// all of them into single spawn_blocking.
// todo - create directories if needed
async fn write_file(
vfs: &VFS,
path: RepoPathBuf,

View File

@ -181,7 +181,12 @@ impl VFS {
/// Overwrite the content of the file at `path` with `data`. The number of bytes written on
/// disk will be returned.
pub fn write(&self, path: &RepoPath, data: &Bytes, flags: Option<UpdateFlag>) -> Result<usize> {
fn write_inner(
&self,
path: &RepoPath,
data: &Bytes,
flags: Option<UpdateFlag>,
) -> Result<usize> {
let filepath = self
.inner
.auditor
@ -195,6 +200,26 @@ impl VFS {
}
}
/// Overwrite content of the file, try to clear conflicts if attempt fails
///
/// Return an error if fails to overwrite after clearing conflicts, or if clear conflicts fail
pub fn write(&self, path: &RepoPath, data: &Bytes, flag: Option<UpdateFlag>) -> Result<usize> {
// Fast path: let's try to open the file directly, we'll handle the failure only if this fails.
match self.write_inner(path, data, flag) {
Ok(size) => Ok(size),
Err(e) => {
// Ideally, we shouldn't need to retry for some failures, but this is the slow path, any
// failures not due to a conflicting file would show up here again, so let's not worry
// about it.
self.clear_conflicts(path).with_context(|| {
format!("Can't clear conflicts after handling error \"{:?}\"", e)
})?;
self.write_inner(path, data, flag)
.with_context(|| format!("Can't write after handling error \"{:?}\"", e))
}
}
}
pub fn set_executable(&self, path: &RepoPath, flag: bool) -> Result<()> {
let filepath = self
.inner