astrid-runtime/astrid · error
WinFsp service control request exceeds limit
Error message
WinFsp service control request exceeds limit
What it means
read_service_control reads one newline-delimited JSON control request from the local stream, capped at SERVICE_MAX_CONTROL_BYTES (64 KiB). If the line is empty (EOF) or longer than 64 KiB, the request is rejected to bound memory use and prevent abuse of the control socket.
Solutions
- Split large control payloads into smaller requests or use the file-based control channel instead of the socket line protocol.
- Ensure the control client appends a newline and keeps each JSON request under 64 KiB.
- Fix health-check/probe clients to send a minimal valid request or close cleanly without partial writes.
- Check the client for binary/UTF-8-invalid output that prevents the line terminator from appearing.
Example fix
// before
let giant = serde_json::to_string(&huge_request)?; // > 64 KiB
stream.write_all(giant.as_bytes()).await?;
// after
let compact = serde_json::to_string(&request.trim_to_control_limit())?;
stream.write_all(format!("{compact}\n").as_bytes()).await?; Defensive patterns
Strategy: validation
Validate before calling
const SERVICE_MAX_CONTROL_BYTES: usize = 64 * 1024;
fn control_request_is_within_limit(json: &str) -> bool {
!json.is_empty() && json.len() <= SERVICE_MAX_CONTROL_BYTES && json.ends_with('\n')
} Try / catch
match send_control(&stream, &request).await {
Err(e) if e.to_string().contains("exceeds limit") => {
// shrink or split the control request below 64 KiB and retry
Err(e)
},
other => other,
} Prevention
- Keep control JSON small and always newline-terminated.
- Never pipe binary data into the control socket.
- Exclude control sockets from naive TCP health checks that open/close without writing.
When it happens
Trigger: A control client connects to the control socket and sends a line longer than 65535 bytes, or closes the connection before sending any data (read == 0).
Common situations: A custom tool writing oversized JSON control commands; a client that dumps a blob without newlines so the whole payload exceeds the cap; connecting and disconnecting without writing (health-check probes); binary data sent to the socket with no newline.
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
- daemon closed the response stream before the final marker
- daemon rejected status request
- daemon returned an unexpected status response
- detached FUSE service exceeded the startup response size
- FSKit callback probe correlation mismatch
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/893288fbc7901038.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-storage-provider-winfsp/src/win.rs:434
return Ok(());
}
},
}
}
}
async fn read_service_control(
stream: &mut local_transport::LocalStream,
) -> Result<ServiceControlRequest> {
let mut line = String::new();
let reader = tokio::io::BufReader::new(stream);
let read = reader
.take((SERVICE_MAX_CONTROL_BYTES + 1) as u64)
.read_line(&mut line)
.await
.context("read WinFsp service control request")?;
if read == 0 || line.len() > SERVICE_MAX_CONTROL_BYTES {
bail!("WinFsp service control request exceeds limit");
}
serde_json::from_str(&line).context("decode WinFsp service control request")
}
async fn write_service_control(
stream: &mut local_transport::LocalStream,
response: &ServiceControlResponse,
) -> Result<()> {
let bytes = serde_json::to_vec(response)?;
stream.write_all(&bytes).await?;
stream.write_all(b"\n").await?;
stream.flush().await.context("flush WinFsp service control")
}
fn parent_is_alive(
parent: &astrid_core::storage_filesystem::StorageProviderParentLifetimeV1,
) -> bool {
// SAFETY: OpenProcess/GetExitCodeProcess/CloseHandle are called with aView on GitHub (pinned to affd8760f4)