Skip to content

borg2: compact crashes when encountering unindexed object #9868

Description

@ThomasWaldmann

Found by Fable 5:

Concurrent creates produce duplicate chunks that later crash borg compact (and leak space).

Two clients backing up overlapping data concurrently will both see a new chunk id as "not seen" and each writes its own copy into its own pack. Both incremental index fragments get written; when merged, the ChunkIndex can point to only one copy — the other pack now contains an object no index entry covers.

compact_packs() calls compact_pack(pid, keep_ids=..., drop_ids=...) with the default complete=True (compact_cmd.py:283), and compact_pack then asserts the indexed objects tile the pack with no gaps (repository.py:936 and the covered == pack_size assert at line 943). A pack holding a superseded, unindexed object has a gap, so as soon as that pack qualifies for a threshold rewrite, compact dies with AssertionError — after delete_chunkindex_from_repo() already ran, and it recurs on every subsequent run (the slow index rebuild can't help: a dict can index only one copy per chunk id). Repository.delete's docstring already acknowledges superseded bytes and passes complete=False; compact assumes the opposite.

Two related accounting gaps from the same root cause (pack usage computed only from index entries, compact_cmd.py:219-225):

superseded bytes are invisible to pack_unused, so a pack full of them but with all indexed objects used is "all used → keep" — the duplicate copies are never reclaimed;
a pack with no indexed objects (e.g. a create that crashed after PackWriter.flush() but before the fragment write) is entirely invisible to compact and leaks forever.

Suggested direction: compact should derive per-pack ground truth from the pack headers (it already has PackReader.iter_headers) or at least call compact_pack(..., complete=False) and treat unindexed spans as droppable waste.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions