Skip to content

An extractor that resolves nowhere says where it looked - #174

Merged
torstei merged 1 commit into
mainfrom
fix/extractor-not-found-names-nowhere
Sep 6, 2026
Merged

An extractor that resolves nowhere says where it looked#174
torstei merged 1 commit into
mainfrom
fix/extractor-not-found-names-nowhere

Conversation

@torstei

@torstei torstei commented Sep 6, 2026

Copy link
Copy Markdown
Owner

Found by typing the command line the provisioning example shipped in #172 — the
example was wrong, and the error it produced was worse.

The error named nowhere. A reading directory is listed only if it exists,
so on a host where none does the list is empty:

$ timberfs tally --try --extractor timberfs-apache-combined < access.log
Error: no extractor "timberfs-apache-combined" — neither a path that exists nor a document in

That sentence ends there. No directory named, so nothing tells the reader where
the file was supposed to be. Now:

Error: no extractor "timberfs-apache-combined" — it is not a path that exists,
and there is no extractor directory to search: none of
/usr/lib/timberfs/tally.extractors.d (the timberfs package),
/etc/timberfs/tally.extractors.d or ~/.config/timberfs/tally.extractors.d exists

The "which hold …" list named the wrong thing. It listed the document
names, where this lookup joins <name>.json — the file stem. The two can
differ on a site's own file, and there the message printed a word that does not
resolve. It prints the stem now, plus the document's name where they differ.

That distinction was nowhere in the docs, so it is in timberfs(1) and
tools/README.md now: a provisioning's APPLY names the document,
--extractor names the file, and keeping them the same is what everything
shipped does — already enforced for the shipped set by
everything_shipped_carries_the_reserved_prefix.

Two smaller ones

  • packaging/timberfs-tally.conf.example said timberfs tally --try NAME.
    --try is a flag; the name goes to --extractor.
  • --etc carried requires = "provision" and never enforced it (verified:
    --try --extractor … --etc DIR runs). --try and --check resolve names
    against it too, which is exactly how a site extractor is tried before
    anything is provisioned — so the attribute was a lie either way. Removed, no
    behaviour change.

A test covers both messages.

Found by typing the shipped example's own command line, which was also
wrong.

A reading directory is listed only if it EXISTS, so on a host where none
does the list was empty and the failure read "nor a document in" with
nothing after it — no directory named, no hint where the file should go.
It now names all three, and says which one the timberfs package owns.

The list of what a populated directory holds was the DOCUMENT names,
where this lookup takes the FILE stem. Where they differ, printing the
document name printed a word that does not resolve. It prints the stem,
and says what the document inside is called. Written down in the man page
and in tools/README.md too: a provisioning's APPLY names the document,
`--extractor` names the file, and keeping them the same is what
everything shipped does.

The example said `tally --try NAME`; `--try` is a flag and the name goes
to `--extractor`.

`--etc` claimed `requires = "provision"` and never enforced it — `--try`
and `--check` resolve names against it too, which is how a site extractor
is tried before anything is provisioned.
@torstei
torstei merged commit 600d373 into main Sep 6, 2026
7 checks passed
@torstei
torstei deleted the fix/extractor-not-found-names-nowhere branch September 6, 2026 22:08
torstei added a commit that referenced this pull request Sep 7, 2026
)

* A frame nothing witnesses (plan), and a test that only passed here

The plan: a chunk is the frame then its record, data first, so a crash
between them leaves a `.trunk` frame no `.rings` record describes. Which
of the four crash paths can leave one, why a wal store already recovers
its own from the `.sap.seal`, and the on-disk stage marker that makes the
other three decidable — a crashed `stage()` and a killed non-wal flush
look identical on disk and want opposite treatment.

Reproduced against 0.32.0, and the frame is not merely invisible: once a
shorter frame is written over part of it, `zstd -dc` fails on the whole
trunk, which revokes a documented recovery path while timberfs itself
reads the store correctly.

The test fix is the same lesson twice in one evening. #174's
`a_name_that_resolves_nowhere_says_where_it_looked` DERIVED the reading
directories, so it tested a different thing depending on whether the
timberfs package was installed — and it passed everywhere until 0.32.0
landed on the machine it was written on. The message builder now takes
the directories, so the test states its own world. The timbergraph twin
already did this; the Rust one was left behind.

* A source witness: one sidecar, recovery and a live edge

Where the wal writes the bytes again, a witness records where they are —
`{inode, source_offset, buffer_start}`, some two dozen bytes per batch.
`file-intake` is the intake that can, because it holds a path.

It corrects the plan: the write window of a killed non-wal flush is NOT
unrecoverable there. `follow.rs` extracts each line's stamp from the line
deterministically, so re-reading the recorded span and re-extracting
yields the identical window, and such a frame can be adopted rather than
discarded.

The same witness serves the unflushed edge from the source file, under
the reader's OWN permissions — no helper, no setuid, so it grants
nothing `cat` would not — falling back to store-only on a rotated source,
a re-syncing intake, or a reader who cannot open it. Which mode answered
has to be visible: two operators on one incident, one root and one not,
otherwise see different edges and read the difference as data loss.

Also recorded: why it is not urgent. The wal's cost and its benefit move
together — a busy log's edge is already near-current without one and a
quiet log's wal is free — so the only case that needs this is a
high-volume log wanting a sub-second edge. And lowering FLUSH_AGE is no
substitute at the quiet end: one chunk per line is 56 bytes of index
plus a 58-byte frame for a 48-byte line.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant