\Drupal\strata\Compaction Reachability

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.

Summary

Methods
Properties
Constants
__construct
walk
refresh
isComplete
unreadable
frameReason
isFrameCollectable
dictionaryReason
commitReason
treeReason
statistics
collectableFrames
No public properties found
DEPENDENT_LIMIT
CHAIN_LIMIT
No protected methods found
No protected properties found
No protected constants found
walkFrom
walkAnchor
markFrame
dependentReason
liveFrames
liveDictionaries
liveCommits
liveTrees
unreadable
index
commitLog
refs
bases
No private constants found

Constant

DEPENDENT_LIMIT

DEPENDENT_LIMIT = 64

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.

Type

array<int, string> —

$commitLog

$commitLog : \Drupal\strata\Tree\CommitLog

Type

CommitLog

$refs

$refs : \Drupal\strata\Tree\RefStore

Type

RefStore

$bases

$bases : \Drupal\strata\Tree\BaseReader

Type

BaseReader

Methods

__construct()

__construct(\Drupal\strata\Cas\FrameIndexInterface  $index, \Drupal\strata\Tree\CommitLog  $commitLog, \Drupal\strata\Tree\RefStore  $refs, \Drupal\strata\Tree\BaseReader  $bases) : mixed

Constructs a reachability set.

Parameters

\Drupal\strata\Cas\FrameIndexInterface $index

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.

statistics()

statistics() : array{commits: int, trees: int, frames: int, dictionaries: int, unreadable: int}

What the walk reached.

Returns

array{commits: int, trees: int, frames: int, dictionaries: int, unreadable: int} —

Counts, for a receipt and for the storage explorer.

collectableFrames()

collectableFrames(int  $limit = 1000) : list<\Drupal\strata\Cas\FrameRecord>

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.