clockworklabs/SpacetimeDB · error · io::Error
all-zeroes commit header
Error message
all-zeroes commit header
What it means
While consuming commits from the input stream, EOF is handled explicitly just above, so the next bytes must be a commit header. commit::Header::decode returned None, which per the code happens only for an all-zero header. Zeros at this position mean the stream is misaligned with its framing, or the producer is emitting uninitialized/preallocated bytes.
Source
Thrown at crates/commitlog/src/stream/writer.rs:306
// The stream is exhausted, break the outer loop.
trace!("eof");
return Ok(AppendInnerResult::StreamExhausted);
};
if buf.starts_with(&segment::MAGIC) {
// New segment, break inner loop.
trace!("segment marker");
return Ok(AppendInnerResult::SegmentMarker);
}
// Read the header, so we can determine the size of the commit.
if read_exact(stream, &mut self.commit_buf.header).await?.is_eof() {
return Ok(AppendInnerResult::StreamExhausted);
}
let Some(commit_header) = commit::Header::decode(&self.commit_buf.header[..])
.inspect_err(|e| warn!("failed to decode commit header: {e}"))?
else {
// Nb. eof handled above.
return Err(io::Error::new(io::ErrorKind::InvalidData, "all-zeroes commit header"));
};
// Read the rest of the commit.
self.commit_buf.body.resize(
commit_header.len as usize + CHECKSUM_LEN[current_segment.header.checksum_algorithm as usize],
0,
);
stream.read_exact(&mut self.commit_buf.body).await?;
// Decode the commit and verify its checksum.
let commit = StoredCommit::decode(self.commit_buf.as_reader())
.inspect_err(|e| warn!("failed to decode commit: {e}"))?
.expect("commit decode cannot return `None` because we already decoded the header");
// Check that the commit offset is what we expect.
let expected_offset = self
.last_written_tx_range
.as_ref()
.map(|range| range.end)View on GitHub (pinned to 524b4487d9)
Solutions
- Stream only up to the committed length of each segment, never to the file's allocated size.
- Verify both ends run the same commitlog format version and checksum algorithm.
- Restart the stream from the last known-good segment boundary or a fresh snapshot.
Defensive patterns
Strategy: validation
Validate before calling
fn is_all_zero(buf: &[u8]) -> bool {
buf.iter().all(|&b| b == 0)
}
// Before feeding a raw region to the stream writer, reject all-zero buffers:
// they indicate padding or unwritten preallocated space, not valid commits. Try / catch
match writer.append_all(stream, progress).await {
Err(e) if e.kind() == io::ErrorKind::InvalidData && e.to_string().contains("all-zeroes commit header") => {
// Framing is misaligned: restart the stream from the last good segment boundary.
}
r => r,
} Prevention
- Stream only up to each segment's committed length, never its allocated file size.
- Keep checksum-algorithm and format-version settings identical on both ends.
- Treat any zero-filled tail in a source segment as unwritten and stop there.
When it happens
Trigger: An earlier record declared a wrong length, shifting subsequent reads into zero padding; streaming a segment file past its last committed write into fallocate-preallocated zero space; a producer bug or truncated/corrupted feed; a checksum-algorithm mismatch changing body sizes and desynchronizing the reader.
Common situations: Reading a commitlog segment up to its file size instead of its committed length after a writer crash; nodes on both ends configured with different checksum algorithms; bit rot or truncation in transit.
Related errors
- InvalidData
- out-of-order offset: expected={} actual={}
- failed to decode commit
- mismatch key in index offset file: expected={} actual={}
- no current segment, expected segment header
AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16).
Data as JSON: /api/errors/efa7176311309045.
Report an issue: GitHub.