GitoxideLabs/gitoxide · error
at least in theory
Error message
at least in theory
What it means
`poll_fill_buf` (the async BufRead implementation for sideband-multiplexed packetline streams) asserts that the reader is never asked to fill its buffer while the internal state is `ReadLine` — i.e. while a line-based read is in progress. Hitting the panic means the stream's state machine was interleaved incorrectly, typically by mixing line reads and buffered reads on the same connection.
Solutions
- Complete each line read before issuing buffered reads (or vice versa); don't interleave read_line with fill_buf on the same stream
- Use the blocking/std implementation or the appropriate mode consistently for the protocol phase (line-based handshake vs binary sideband data)
- Serialize access to the stream (own it in one task, or use a mutex) and report the panic to gix-packetline if it occurs with sequential reads
Example fix
// before let line = reader.read_line(&mut buf).await?; // leaves state ReadLine let chunk = reader.fill_buf().await?; // panics // after let line = reader.read_line(&mut buf).await?; reader.consume(line); let chunk = reader.fill_buf().await?;
Defensive patterns
Strategy: type-guard
Validate before calling
// only call fill_buf when no line read is in progress debug_assert!(!matches!(stream.state(), ReadLine), "cannot fill_buf during ReadLine state");
Type guard
fn can_fill_buf<S: ReadMode>(s: &S) -> bool { matches!(s.state(), State::Idle { .. }) } Try / catch
// panic occurs in poll; guard the call site instead
if stream_is_idle(&stream) {
let buf = Pin::new(&mut stream).fill_buf().await?;
} else {
return Err(io::Error::new(io::ErrorKind::InvalidInput, "read in progress"));
} Prevention
- Never interleave read_line with fill_buf/read_until on the same packetline stream
- Own the stream in a single task; avoid sharing it across concurrent futures
- Use line mode for the handshake phase and buffered mode only for sideband data
- If the panic occurs with strictly sequential reads, file a bug against gix-packetline
When it happens
Trigger: Calling `fill_buf`/BufRead APIs while a `read_line`-style operation is mid-flight on the same pinned stream — e.g. polling fill_buf inside a ReadLine state without completing the line read, or concurrent/mixed use of line and buffered read modes on one packetline sideband.
Common situations: Hand-rolled protocol code mixing `read_line` and `fill_buf`/`read_until` on the same sideband stream; polling a future that owns the reader while another read is pending; spawning tasks sharing one connection without synchronization.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- ' ' is not a valid configuration key
- Cannot use iter_v1() on index of type
- Cannot use iter_v2() on index of type
- BUG: tries to obtain object id from symbolic target
- BUG: expected peeled reference target but found symbolic one
AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08).
Data as JSON: /api/errors/93f774a673074e6c.
Report an issue: GitHub.
Appendix: source
Thrown at gix-packetline/src/async_io/sidebands.rs:329
None => {
return Poll::Ready(Err(io::Error::other(
"encountered non-data line in a data-line only context",
)));
}
};
}
}
}
}
};
this.cap = cap + ofs;
this.pos = ofs;
}
}
let range = self.pos..self.cap;
match &self.get_mut().state {
State::Idle { parent } => Poll::Ready(Ok(&parent.as_ref().expect("parent always available").buf[range])),
State::ReadLine { .. } => unreachable!("at least in theory"),
}
}
fn consume(self: Pin<&mut Self>, amt: usize) {
let this = self.get_mut();
this.pos = std::cmp::min(this.pos + amt, this.cap);
}
}
impl<T, F> AsyncRead for WithSidebands<'_, T, F>
where
T: AsyncRead + Unpin,
F: FnMut(bool, &[u8]) -> ProgressAction + Unpin,
{
fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8]) -> Poll<std::io::Result<usize>> {
use std::io::Read;
let mut rem = ready!(self.as_mut().poll_fill_buf(cx))?;
let nread = rem.read(buf)?;View on GitHub (pinned to e73179060b)