Harden blob GC and supervise the content-index worker
Deduplication GC (garbage_collect, Phase 2): - Add an orphan grace period before a ref_count=0 blob's backing file is physically deleted, mirroring git's gc.pruneExpire. New storage.blobs.orphaned_at records when a blob last reached ref_count 0; the delete trigger and every decrement / 0-ref insert path stamp it, every re-reference clears it. - Cross-check that no manifest lists the chunk and no file points at the blob before deleting it (mirrors Phase 1's file check), so a stale ref_count can only delay collection, never delete live content. - Unlink the backing files with bounded parallel fan-out. Together these close a TOCTOU where a concurrent upload of identical content could re-reference a chunk in the window between the GC row delete committing and the backing file being unlinked. Individual file deletes still reclaim eagerly; only bulk empty-trash and the periodic sweep observe the grace window. Trash: match ErrorKind::NotFound instead of substring-matching the error message when treating an already-deleted item as success. Content-index worker: supervise the drain loop and restart it with backoff after a panic, instead of letting a panic silently freeze the search index while the dirty queue grows unbounded. Adds migration 20260802000000_blob_gc_grace.sql and an integration test covering the grace window and reference cross-checks. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0172rsVwzTwD216R9HXT2aU4
This commit is contained in:
@@ -568,9 +568,11 @@ impl TrashUseCase for TrashService {
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
// Check if the file is not found - in that case, we can continue
|
||||
// because we still want to remove the item from the trash index
|
||||
if format!("{}", e).contains("not found") {
|
||||
// File already gone — still remove the trash index
|
||||
// entry. Match on the typed error kind, not the
|
||||
// message text, so a reworded message can't
|
||||
// silently turn this into a hard failure.
|
||||
if e.kind == ErrorKind::NotFound {
|
||||
info!(
|
||||
"File not found, may already have been deleted: {}",
|
||||
file_id
|
||||
@@ -608,8 +610,9 @@ impl TrashUseCase for TrashService {
|
||||
info!("Successfully deleted folder permanently: {}", folder_id);
|
||||
}
|
||||
Err(e) => {
|
||||
// Check if the folder is not found - in that case, we can continue
|
||||
if format!("{}", e).contains("not found") {
|
||||
// Folder already gone — still remove the trash
|
||||
// index entry. Typed-kind match (see file branch).
|
||||
if e.kind == ErrorKind::NotFound {
|
||||
info!(
|
||||
"Folder not found, may already have been deleted: {}",
|
||||
folder_id
|
||||
|
||||
Reference in New Issue
Block a user