test(api): diagnose all leftovers, and name the pinning source

The run gave the decisive fact: `derived=1`. A content_derived_blobs row
still points at the leftover blob, so GC is CORRECT to keep it — the
leak is the row, not the bytes. purge_derived_blobs only runs when the
SOURCE is reaped, so the question is why that never happened.

So the dump now prints the source hash and what still holds it:
src_files, src_manifest, src_blob. If the source has a live file the
answer is "not deleted"; if it has none but a positive refcount, a
release was missed upstream; if it has no row at all, the source was
reaped WITHOUT purging, which would be a real ordering bug in reap_blob.

Also fixes the dump reporting only one of three blobs. `docker compose
exec -T` reads stdin, so it consumed the rest of the here-string feeding
the loop — the other two were never queried and vanished silently. The
same silent-truncation shape the diagnosis exists to expose, in the
diagnosis. `< /dev/null` closes it.
This commit is contained in:
Edouard Vanbelle
2026-08-28 08:12:25 +02:00
parent 2b9505f344
commit 0119110345
+14 -1
View File
@@ -447,8 +447,21 @@ if [[ -n "$BLOB_FILES" ]]; then
|| ' blob_refs=' || COALESCE((SELECT ref_count::text FROM storage.blobs WHERE hash='$h'), '-')
|| ' files=' || (SELECT count(*) FROM storage.files WHERE blob_hash='$h')
|| ' derived=' || (SELECT count(*) FROM storage.content_derived_blobs WHERE blob_hash='$h')
|| ' attached=' || (SELECT count(*) FROM storage.file_attached_blobs WHERE blob_hash='$h');" \
|| ' attached=' || (SELECT count(*) FROM storage.file_attached_blobs WHERE blob_hash='$h')
-- When a derived row is what pins the blob, the question is
-- why its SOURCE was never reaped — purge_derived_blobs only
-- runs from the source's reap. Print the source and whether
-- anything still holds it.
|| COALESCE((SELECT ' src=' || d.source_hash
|| ' src_files=' || (SELECT count(*) FROM storage.files WHERE blob_hash = d.source_hash)
|| ' src_manifest=' || COALESCE((SELECT ref_count::text FROM storage.chunk_manifests WHERE file_hash = d.source_hash), '-')
|| ' src_blob=' || COALESCE((SELECT ref_count::text FROM storage.blobs WHERE hash = d.source_hash), '-')
FROM storage.content_derived_blobs d WHERE d.blob_hash='$h' LIMIT 1), '');" \
< /dev/null \
2> >(grep -v 'Executing external compose provider' >&2) || true
# `< /dev/null`: `docker compose exec -T` reads stdin, and without this it
# consumes the rest of the here-string — so only the FIRST leftover was
# ever diagnosed and the others vanished silently.
done <<< "$BLOB_FILES"
log "Leftover blob files ($BLOB_COUNT):"