Decides whether an object can be removed without breaking something that still works.
A reference count answers "does anything point at this" and that is not the same question. Three
separate things make an object un-collectable, and a prune that checks only the first has, in
every generation of this kind of system, eventually deleted something a restore needed.
Commits and bases. A frame under a tree that a reachable commit points at is live, however
old the commit is and however many times the subject has changed since. Reachability is computed
by walking from the refs rather than read from a counter, because a counter is a cache of a walk
and the walk is the thing that is actually true.
Dictionaries. A frame compressed against a trained dictionary cannot be decompressed without
it. The dictionary is not an optimisation after the fact; it is part of the frame. A dictionary
any live frame names survives even when the dictionary itself is old, unused by new writes, and
looks like an obvious candidate.
Delta-chain parents. This is the class that delta coding introduced and the one most easily
missed, because the parent frame is often referenced by nothing at all - its own commit may be
long pruned. It is still needed: the frame that deltas against it decodes to garbage without it,
and so does everything after that one. A chain is only collectable whole.
Every method here answers with the reason, not just a boolean, so a receipt can say what held an
object alive rather than reporting a refusal with no explanation.
How many dependents to look at before treating a frame as widely depended on.
A frame with more children than this is protected without enumerating them. The exact count
changes nothing about the decision, and a chain that wide is a re-anchoring job rather than a
prune candidate.
CHAIN_LIMIT
CHAIN_LIMIT = 1024
How far a delta chain is followed before the index is treated as corrupt.
Content addressing makes a genuine cycle impossible, so a chain this long means an index row
names a parent it should not. Far above the 32-link policy cap, so a legitimate chain never
reaches it.
Properties
$liveFrames
$liveFrames : array|null
Frames reachable from a ref, keyed by address; NULL until the walk has run.
Type
array<string, true>|null
—
$liveDictionaries
$liveDictionaries : array|null
Dictionaries live frames name, keyed by id; NULL until the walk has run.
Type
array<string, true>|null
—
$liveCommits
$liveCommits : array|null
Commits reachable from a ref, keyed by id; NULL until the walk has run.
Type
array<string, true>|null
—
$liveTrees
$liveTrees : array|null
Base anchors reachable from a ref, keyed by address; NULL until the walk has run.
Type
array<string, true>|null
—
$unreadable
$unreadable : list
Objects the walk could not read, so the answer is incomplete.
Consulted for each frame's dictionary and delta parent.
\Drupal\strata\Tree\CommitLog
$commitLog
Walks history from each ref.
\Drupal\strata\Tree\RefStore
$refs
Lists the refs history is reachable from.
\Drupal\strata\Tree\BaseReader
$bases
Reads the anchor chain so the frames its entries name can be collected.
Returns
mixed
—
walk()
walk() : void
Walks every ref and records what is reachable.
Runs once and is cached, because a prune asks about thousands of candidates against one
snapshot of history. Call Reachability::refresh() after anything that moves a ref.
Returns
void
—
refresh()
refresh() : void
Discards the cached walk.
A prune that has just moved a ref is looking at different history than the one it started
with, and answering from the old snapshot is how a live object gets collected.
Returns
void
—
isComplete()
isComplete() : bool
Whether the walk read everything it needed to.
A prune must refuse outright when this is FALSE. An incomplete walk under-reports what is
live, and under-reporting is exactly the direction that deletes something needed.
Returns
bool
—
TRUE when every object the walk reached for was readable.
unreadable()
unreadable() : list<string>
Objects the walk could not read.
Returns
list
—
One line per unreadable object, naming it and the reason.
frameReason()
frameReason(string $hash) : string|null
Whether a frame must survive a prune, and why.
Parameters
string
$hash
Frame content address.
Returns
string|null
—
The reason it is live, or NULL when nothing needs it.
isFrameCollectable()
isFrameCollectable(string $hash) : bool
Whether a frame can be removed.
Parameters
string
$hash
Frame content address.
Returns
bool
—
TRUE when nothing reachable needs it.
dictionaryReason()
dictionaryReason(string $id) : string|null
Whether a dictionary must survive a prune, and why.
Asked against both the walk and the index, because a frame outside reachable history can
still be readable and still need its dictionary.
Parameters
string
$id
Dictionary id.
Returns
string|null
—
The reason it is live, or NULL when nothing needs it.
commitReason()
commitReason(string $id) : string|null
Whether a commit must survive a prune, and why.
Parameters
string
$id
Commit id.
Returns
string|null
—
The reason it is live, or NULL when no ref reaches it.
treeReason()
treeReason(string $address) : string|null
Whether a tree node must survive a prune, and why.
Parameters
string
$address
Node address.
Returns
string|null
—
The reason it is live, or NULL when no reachable commit points at it.
Frames the index holds that no reachable history needs.
Every candidate has been through all three classes, so a caller can act on the list without
re-checking. The list is bounded, because a prune runs inside a cron window.
Parameters
int
$limit
Most candidates to return.
Returns
list<\Drupal\strata\Cas\FrameRecord>
—
Collectable records.
walkFrom()
walkFrom(string $tip) : void
Walks one chain of history from a tip.
Every parent is followed, not only the first. A merge commit names the branch tip it brought in
as a second parent, and those commits are reachable from this ref whether or not the branch's own
ref still exists. Following the first parent alone would report them collectable the moment the
branch was deleted, and a prune would take the frames out from under a commit the trunk names.
Parameters
string
$tip
Commit id to start at.
Returns
void
—
walkAnchor()
walkAnchor(string $address) : void
Walks an anchor chain, marking every anchor and the frames its entries name live.
The whole chain rather than the resolved index: an entry a later anchor replaced is still what
the anchor that named it restores from, and every anchor is a restore target.
Parameters
string
$address
Anchor address.
Returns
void
—
markFrame()
markFrame(string $hash) : void
Marks a frame live, along with its dictionary and its whole delta chain.
The chain is followed here rather than checked later, because a parent's own commit may be
gone and nothing else would ever reach it.
Parameters
string
$hash
Frame content address.
Returns
void
—
dependentReason()
dependentReason(string $hash) : string|null
Whether anything live decodes against this frame.
The upward direction of the chain: the frame itself may be unreferenced and unreachable while
a frame that IS live sits one link downstream of it.
Parameters
string
$hash
Frame content address.
Returns
string|null
—
The reason it is live, or NULL when nothing decodes against it.