WebDAV async + cache TTL: spawn_blocking for props persistence, 5min TTL eviction
Some checks failed
Test / test (push) Has been cancelled
Test / build (push) Has been cancelled

P2 improvements:
- patch_props: use tokio::spawn_blocking for blocking VFS writes
- WEBDAV_HANDLER_CACHE: add CachedHandler with Instant timestamp
- TTL check on each request (300s = 5 minutes), recreate if expired
- create_handler_for_user() helper function

Tests: 288 passed, 0 failed
This commit is contained in:
Warren
2026-06-21 16:14:42 +08:00
parent 9acd174388
commit 5000ba7c14
2 changed files with 129 additions and 78 deletions

View File

@@ -776,58 +776,62 @@ impl DavFileSystem for VfsDavFs {
let vfs = self.vfs.clone_boxed();
let root = self.root.clone();
Box::pin(async move {
let mut map = match data.write() {
Ok(guard) => guard,
Err(e) => {
log::warn!("props_data RwLock poisoned in patch_props, recovering");
e.into_inner()
}
let results: Vec<(StatusCode, DavProp)> = {
let mut map = match data.write() {
Ok(guard) => guard,
Err(e) => {
log::warn!("props_data RwLock poisoned in patch_props, recovering");
e.into_inner()
}
};
patch
.into_iter()
.map(|(set, prop)| {
let code = if set {
map.entry(key.clone()).or_default().push(prop.clone());
StatusCode::OK
} else {
if let Some(props) = map.get_mut(&key) {
props.retain(|p| {
p.name != prop.name || p.namespace != prop.namespace
});
}
StatusCode::NO_CONTENT
};
(code, prop)
})
.collect()
};
let results: Vec<(StatusCode, DavProp)> = patch
.into_iter()
.map(|(set, prop)| {
let code = if set {
map.entry(key.clone()).or_default().push(prop.clone());
StatusCode::OK
} else {
if let Some(props) = map.get_mut(&key) {
props.retain(|p| {
p.name != prop.name || p.namespace != prop.namespace
});
}
StatusCode::NO_CONTENT
};
(code, prop)
})
.collect();
drop(map);
let map = match data.read() {
Ok(guard) => guard,
Err(e) => {
log::warn!("props_data RwLock poisoned in patch_props persistence, recovering");
e.into_inner()
}
let entries: HashMap<String, Vec<DeadPropEntry>> = {
let map = match data.read() {
Ok(guard) => guard,
Err(e) => {
log::warn!("props_data RwLock poisoned in patch_props persistence, recovering");
e.into_inner()
}
};
map.iter()
.map(|(k, v)| (k.clone(), v.iter().map(DeadPropEntry::from).collect()))
.collect()
};
let entries: HashMap<String, Vec<DeadPropEntry>> = map
.iter()
.map(|(k, v)| (k.clone(), v.iter().map(DeadPropEntry::from).collect()))
.collect();
if let Ok(json) = serde_json::to_string(&entries) {
let path = root.join(".webdav_props.json");
let flags = OpenFlags::new().write().create().truncate().mode(0o644);
match vfs.open_file(&path, &flags) {
Ok(mut file) => {
if let Err(e) = file.write_all(json.as_bytes()) {
log::warn!("patch_props write_all failed: {:?}", e);
let _ = tokio::task::spawn_blocking(move || {
let flags = OpenFlags::new().write().create().truncate().mode(0o644);
match vfs.open_file(&path, &flags) {
Ok(mut file) => {
if let Err(e) = file.write_all(json.as_bytes()) {
log::warn!("patch_props write_all failed: {:?}", e);
}
if let Err(e) = file.flush() {
log::warn!("patch_props flush failed: {:?}", e);
}
}
if let Err(e) = file.flush() {
log::warn!("patch_props flush failed: {:?}", e);
Err(e) => {
log::warn!("patch_props open_file failed: {:?}", e);
}
}
Err(e) => {
log::warn!("patch_props open_file failed: {:?}", e);
}
}
}).await;
}
Ok(results)
})