Support durable host-to-isolated workspace migration
This commit is contained in:
parent
db9a561d0c
commit
0dda65fc2e
1 changed files with 1176 additions and 32 deletions
|
|
@ -8,8 +8,8 @@ use std::os::unix::ffi::{OsStrExt, OsStringExt};
|
|||
use std::os::unix::fs::FileExt;
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
|
||||
use fuser::{
|
||||
|
|
@ -32,6 +32,7 @@ use rustix::time::Timespec;
|
|||
use crate::backing::{BackingRoot, validate_name, validate_relative};
|
||||
use crate::classifier::tracked_paths_from_index;
|
||||
use crate::classifier::{Classification, ClassificationSnapshot, GitClassifier};
|
||||
use crate::migration::{Provenance, reserved};
|
||||
use nodes::{
|
||||
DirectoryEntry, DirectoryStream, NodeTable, OpenFile, PosixLockKey, ResolvedEntry, errno,
|
||||
file_type, open_flags, system_time,
|
||||
|
|
@ -67,6 +68,8 @@ pub struct IgnoredWorkspace {
|
|||
files: Mutex<HashMap<u64, Arc<OpenFile>>>,
|
||||
directories: Mutex<HashMap<u64, DirectoryStream>>,
|
||||
posix_lock_files: Mutex<HashMap<PosixLockKey, Arc<File>>>,
|
||||
namespace: RwLock<()>,
|
||||
provenance: Mutex<Provenance>,
|
||||
next_handle: AtomicU64,
|
||||
}
|
||||
|
||||
|
|
@ -155,6 +158,7 @@ impl IgnoredWorkspace {
|
|||
if host.identity()? == isolated.identity()? {
|
||||
return Err(io::Error::other("workspace and isolated authorities alias"));
|
||||
}
|
||||
crate::migration::initialize(&host, &isolated)?;
|
||||
let classifier_root = BackingRoot::from_file(host.duplicate()?)?;
|
||||
let classifier =
|
||||
GitClassifier::from_root(workspace_label, classifier_root, global_excludes)?;
|
||||
|
|
@ -167,6 +171,8 @@ impl IgnoredWorkspace {
|
|||
files: Mutex::new(HashMap::new()),
|
||||
directories: Mutex::new(HashMap::new()),
|
||||
posix_lock_files: Mutex::new(HashMap::new()),
|
||||
namespace: RwLock::new(()),
|
||||
provenance: Mutex::new(Provenance::default()),
|
||||
next_handle: AtomicU64::new(1),
|
||||
})
|
||||
}
|
||||
|
|
@ -229,6 +235,9 @@ impl IgnoredWorkspace {
|
|||
validate_name(name)?;
|
||||
let path = self.path_for(parent)?.join(name);
|
||||
validate_relative(&path)?;
|
||||
if reserved(&path) {
|
||||
return Err(io::Error::from_raw_os_error(libc::ENOENT));
|
||||
}
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
|
|
@ -243,6 +252,9 @@ impl IgnoredWorkspace {
|
|||
}
|
||||
|
||||
fn resolve(&self, snapshot: &ClassificationSnapshot, path: &Path) -> io::Result<ResolvedEntry> {
|
||||
if reserved(path) {
|
||||
return Err(io::Error::from_raw_os_error(libc::ENOENT));
|
||||
}
|
||||
let host = self.host.stat(path).ok();
|
||||
let isolated = self.isolated.stat(path).ok();
|
||||
let directory = isolated
|
||||
|
|
@ -305,6 +317,9 @@ impl IgnoredWorkspace {
|
|||
let file = self
|
||||
.root(entry.backing)
|
||||
.open_entry(&path, open_flags(flags), Mode::empty())?;
|
||||
if flags & libc::O_TRUNC != 0 {
|
||||
self.record_changed(entry.backing, &path);
|
||||
}
|
||||
let handle = self.next_handle.fetch_add(1, Ordering::Relaxed);
|
||||
self.files
|
||||
.lock()
|
||||
|
|
@ -314,7 +329,9 @@ impl IgnoredWorkspace {
|
|||
Arc::new(OpenFile {
|
||||
file,
|
||||
backing: entry.backing,
|
||||
path,
|
||||
access_mode: flags & libc::O_ACCMODE,
|
||||
migration_quiesced: AtomicBool::new(flags & libc::O_ACCMODE == libc::O_RDONLY),
|
||||
}),
|
||||
);
|
||||
Ok(handle)
|
||||
|
|
@ -347,6 +364,60 @@ impl IgnoredWorkspace {
|
|||
Ok(entries)
|
||||
}
|
||||
|
||||
fn record_created(&self, backing: Classification, path: &Path) -> io::Result<()> {
|
||||
if backing != Classification::Host {
|
||||
return Ok(());
|
||||
}
|
||||
let stat = self.host.stat(path)?;
|
||||
let mut provenance = self
|
||||
.provenance
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
provenance.record(path.to_path_buf(), &stat);
|
||||
let mut parent = path.parent();
|
||||
while let Some(path) = parent {
|
||||
if let Ok(stat) = self.host.stat(path) {
|
||||
provenance.refresh(path, &stat);
|
||||
}
|
||||
parent = path.parent();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn record_changed(&self, backing: Classification, path: &Path) {
|
||||
if backing != Classification::Host {
|
||||
return;
|
||||
}
|
||||
let mut provenance = self
|
||||
.provenance
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
if let Ok(stat) = self.host.stat(path) {
|
||||
provenance.refresh(path, &stat);
|
||||
}
|
||||
}
|
||||
|
||||
fn has_unquiesced_descendant(&self, source: &Path) -> bool {
|
||||
self.files
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.values()
|
||||
.any(|open| {
|
||||
(open.path == source || open.path.starts_with(source))
|
||||
&& !open.migration_quiesced.load(Ordering::Acquire)
|
||||
})
|
||||
}
|
||||
|
||||
fn wait_for_unquiesced_descendants(&self, source: &Path) -> bool {
|
||||
for _ in 0..100 {
|
||||
if !self.has_unquiesced_descendant(source) {
|
||||
return false;
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(1));
|
||||
}
|
||||
self.has_unquiesced_descendant(source)
|
||||
}
|
||||
|
||||
fn reply_entry(&self, path: &Path, reply: ReplyEntry) {
|
||||
let snapshot = self.classifier.snapshot_for(path);
|
||||
match self
|
||||
|
|
|
|||
Loading…
Reference in a new issue