astrid-runtime/astrid · error
FUSE service control request exceeds limit
Error message
FUSE service control request exceeds limit
What it means
The FUSE control socket reads newline-delimited JSON requests capped at MAX_CONTROL_BYTES. A line that is empty (EOF) or longer than the limit is rejected with this error instead of being parsed, protecting the service from oversized or truncated control requests.
Solutions
- Keep control requests under MAX_CONTROL_BYTES — trim payloads (e.g. paths, tokens) sent to the socket.
- Ensure every request is a single newline-terminated JSON line written atomically.
- Check the client for errors that cause premature disconnect (read==0) or partial writes.
- Serialize requests with serde_json to one compact line rather than hand-rolled formatting.
- If legitimate requests need more space, raise MAX_CONTROL_BYTES in the service (both sides) consistently.
Example fix
// before stream.write_all(payload.as_bytes())?; // missing newline / oversized // after let line = serde_json::to_string(&request)?; assert!(line.len() <= MAX_CONTROL_BYTES); stream.write_all(line.as_bytes())?; stream.write_all(b"\n")?;
Defensive patterns
Strategy: validation
Validate before calling
let line = serde_json::to_string(&request)?;
if line.len() + 1 > MAX_CONTROL_BYTES {
return Err("control request too large");
}
assert!(line.contains('\n') == false); Try / catch
match write_request(stream, &req).await {
Err(e) if e.to_string().contains("exceeds limit") => trim_payload_and_retry(),
other => other,
} Prevention
- Write each request as one newline-terminated compact JSON line
- Keep payloads well under MAX_CONTROL_BYTES
- Handle server EOF: reconnect before sending again
- Keep MAX_CONTROL_BYTES in sync between client and service
When it happens
Trigger: `service_loop` reads from the control connection with `take(MAX_CONTROL_BYTES + 1).read_line()`, and either the peer closed the connection (`read == 0`) or the received line exceeds MAX_CONTROL_BYTES, causing `bail!`.
Common situations: A client sends a huge or malformed request to the control socket; a truncated write leaves a line without a newline so read_line hits the cap; a client connects and disconnects without sending anything; a probe/writer bug double-encodes payloads.
Understand the failure class
Background: payload too large / request exceeds maximum size: why libraries cap bytes and how to fix oversize payloads — this error's family across 50 libraries.
Related errors
- detached FUSE service exceeded the startup response size
- FUSE callback probe correlation mismatch
- FUSE control request exceeds the bounded frame size
- FUSE service control response exceeds the bounded frame size
- daemon closed the response stream before the final marker
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/7ed7b26afeb300a9.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-storage-provider-fuse/src/service.rs:123
async fn service_loop(
listener: &tokio::net::UnixListener,
launch: &StorageProviderServiceLaunchV1,
session: &mut Option<FuseBackgroundSession>,
) -> Result<()> {
let mut poll = tokio::time::interval(SERVICE_POLL);
loop {
tokio::select! {
accepted = listener.accept() => {
let (mut stream, _) = accepted.context("accept FUSE service control")?;
let mut line = String::new();
let reader = tokio::io::BufReader::new(&mut stream);
let read = reader
.take((MAX_CONTROL_BYTES + 1) as u64)
.read_line(&mut line)
.await
.context("read FUSE service control request")?;
if read == 0 || line.len() > MAX_CONTROL_BYTES {
bail!("FUSE service control request exceeds limit");
}
let request: KernelControlRequest = serde_json::from_str(&line)
.context("decode FUSE service control request")?;
let (response, stop) = match request {
KernelControlRequest::Status { token } if token == launch.parent.token => {
(KernelControlResponse::Ready, false)
},
KernelControlRequest::Stop { token } if token == launch.parent.token => {
let response = match session.take() {
Some(session) => match tokio::task::spawn_blocking(|| session.umount_and_join()).await {
Ok(Ok(())) => KernelControlResponse::Stopped,
Ok(Err(error)) => failure_response("unmount", &error.to_string()),
Err(error) => failure_response("unmount", &error.to_string()),
},
None => KernelControlResponse::Stopped,
};
if matches!(&response, KernelControlResponse::Stopped) {
let _ = local_transport::remove_endpoint(&launch.control_path);View on GitHub (pinned to affd8760f4)