feat(thumbnails): the sidecar fallback disables itself
Step 10e was written as a removal release: delete the fallback read path once the directories are empty. That has the same flaw as gating deletion on an empty tail, one level up — sidecars are local disk, so no release can know that every instance has drained. The only removal that can actually be written is "if the tier is gone, return". `initialize` now probes the size directories once at boot; when absent, every fallback read short-circuits on a relaxed atomic load and touches no filesystem. The code stays, costs nothing, and can be deleted whenever — or never. Two things had to change for absence to be reachable at all: * `initialize` no longer creates the directories. It create_dir_all-ed all three at every boot, so the import job removed them and the next restart put them back — the absence this gates on was unreachable by construction. Found on a sandbox where the job had drained the tier and a restart left three empty directories behind. Nothing has written a sidecar since step 10d2, so there was nothing to create them for. * The probe tests the size directories, not the root. On macOS Finder leaves a .DS_Store in the root, which blocks remove_dir there permanently; gating on the root would keep the fallback alive on every developer machine for a reason unrelated to thumbnails. No size directory means no sidecar. Every sidecar read and existence check now goes through `read_sidecar` / `sidecar_exists`, so the guard exists once rather than at each of the twelve sites that built a path and read it — the build-then-read pair was duplicated six times over. The import job's root removal reports its outcome instead of discarding it. It is the one result an operator is waiting for, and "directory not empty" with no sidecars left is a failure worth naming. Falls open: the flag starts true, so a service constructed without `initialize` behaves as before. A drain completing mid-process leaves it stale-true until restart, which costs the same failed opens as today; it never goes false while sidecars remain. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -577,7 +577,32 @@ impl RecoverableJobHandler for ThumbDerivedImport {
|
||||
let dir = self.thumbnails_root.join(size.dir_name());
|
||||
let _ = fs::remove_dir(&dir).await;
|
||||
}
|
||||
let _ = fs::remove_dir(&self.thumbnails_root).await;
|
||||
// Report the root, rather than discarding the result as the size
|
||||
// directories do. This is the one outcome an operator is waiting
|
||||
// for — absence is what makes the fallback inert — and it fails
|
||||
// for a reason worth naming: on macOS Finder leaves a `.DS_Store`
|
||||
// in the root, so `remove_dir` refuses forever while every
|
||||
// sidecar underneath is long gone.
|
||||
match fs::remove_dir(&self.thumbnails_root).await {
|
||||
Ok(()) => tracing::info!(
|
||||
target: "oxicloud::dedup",
|
||||
event = "thumb_derived_import.root_removed",
|
||||
run_id = %store.run_id(),
|
||||
path = %self.thumbnails_root.display(),
|
||||
"🧹 legacy sidecar directory removed — the fallback read path \
|
||||
is inert from the next restart"
|
||||
),
|
||||
Err(e) => tracing::info!(
|
||||
target: "oxicloud::dedup",
|
||||
event = "thumb_derived_import.root_kept",
|
||||
run_id = %store.run_id(),
|
||||
path = %self.thumbnails_root.display(),
|
||||
reason = %e,
|
||||
"legacy sidecar directory not removed; if this says \
|
||||
'directory not empty' with no sidecars left, something \
|
||||
else put a file there (a .DS_Store, typically)"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
tracing::info!(
|
||||
|
||||
Reference in New Issue
Block a user