What's new in v0.22.1
v0.22.1 is about what the daemon does while it waits for a disk. Nine file operations ran straight on the runtime's worker threads, and a worker parked on a write is a worker that stops polling every other task it holds. Nothing failed, nothing was reported, and on a fast local disk nothing was visible. That is the shape of the problem: it only shows on the filesystem that is already having a bad day.
The thread a slow disk parks
The window archive and the incident archive each drain their channel on a task of their own, writing synchronously. That part was deliberate and stays: producers hand work over with try_send and drop on a full channel, so a stalled filesystem never reaches back into the analysis path. What was wrong is the kind of thread underneath. A runtime worker runs many tasks, so the write that parks it parks them too, and the recovery after a partial write parks them further, a truncation and a rotation included.
Both writers now run on a blocking thread and take their channel with blocking_recv. Their bodies are unchanged, so the chain hashing, the sequence numbers, the rotation and the recovery after a failed write behave exactly as they did. Only the thread they sit on is different, which is the whole point: a slow disk now parks a thread whose entire job is to write.
Seven calls that only run once
The rest are setup and teardown, and they move to the async file API: the four around the JSON socket, which check the path for a symlink, clear a stale socket, tighten the socket to 0600 and clean up when that fails, the socket removal on daemon shutdown, and the output identity check that closes capture. None of them is on a hot path, and each was still a worker parked on a syscall for no reason.
Exercised in the simulation lab against a local binary, five scenarios green: the archive chain hashing and its writer, the rotation and the drop accounting, the incident archive writer, capture finish, and the JSON socket beside the ack routes. Between them they cover eight of the nine call sites. The ninth is the socket removal during shutdown, which needs a cluster to reach and is a single unlink of a Unix socket.
Upgrade impact
- Nothing observable changes. No output moves, no file lands anywhere different, and the archive on disk is byte-for-byte what the previous release wrote, chain hashes included.
- A daemon on a slow or stalled filesystem keeps answering. Its query API, its OTLP intake and its analysis no longer share a thread with a write that is waiting on the disk.
- No configuration key is added or removed, no route, metric or wire format changes, no public signature in
perf-sentinel-coremoves, the acknowledgment signatures and the detection verdicts are untouched, and MSRV stays 1.98.1.
Full detail in CHANGELOG.md.
Verifying this release
# Binary integrity via SLSA Build L3 attestation
gh attestation verify perf-sentinel-linux-amd64 \
--repo robintra/perf-sentinel
# A periodic disclosure produced by this binary
perf-sentinel verify-hash --report perf-sentinel-report.json \
--expected-identity "https://github.com/robintra/perf-sentinel/.github/workflows/release.yml@refs/tags/v0.22.1" \
--expected-issuer "https://token.actions.githubusercontent.com" \
--verify-binary ./perf-sentinel-linux-amd64gh CLI 2.49 or newer required for gh attestation verify.