Skip to content

Commit 553636c

Browse files
westgatewestgate
authored andcommitted
fix(btsp): keep connection alive for NDJSON after JSON-line handshake
After a JSON-line BTSP handshake completes, both the pure JSON-RPC server and daemon CLI were falling through to the binary BTSP framing loop (read_frame/write_frame with 4-byte length prefix). Clients that performed a JSON-line handshake expect NDJSON on the same connection, so the first JSON-RPC call hit a protocol mismatch and got reset. Now both handlers enter NDJSON mode (newline-delimited JSON-RPC) after a successful JSON-line BTSP handshake, matching the plaintext path. The tarpc path is unaffected — it correctly uses LengthDelimitedCodec after handshake. Fixes "connection reset" / "Broken pipe" on toadstool.liveness and health:Node:compute reported by primalSpring. Made-with: Cursor
1 parent 3edd813 commit 553636c

2 files changed

Lines changed: 27 additions & 10 deletions

File tree

crates/cli/src/daemon/jsonrpc_server.rs

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -312,12 +312,14 @@ async fn handle_btsp_daemon_connection(
312312
btsp_info.cipher.as_str(),
313313
btsp_info.session_id
314314
);
315-
stream
316-
} else {
317315
let (reader, mut writer) = stream.into_split();
318316
let mut reader = BufReader::new(reader);
319-
let mut line = first_line;
317+
let mut line = String::new();
320318
loop {
319+
let n = reader.read_line(&mut line).await?;
320+
if n == 0 {
321+
break;
322+
}
321323
if !line.trim().is_empty() {
322324
let response = dispatch_or_parse_error(line.as_bytes(), &state).await;
323325
let response_json = serde_json::to_string(&response)?;
@@ -326,13 +328,27 @@ async fn handle_btsp_daemon_connection(
326328
writer.flush().await?;
327329
}
328330
line.clear();
329-
let n = reader.read_line(&mut line).await?;
330-
if n == 0 {
331-
break;
332-
}
333331
}
334332
return Ok(());
335333
}
334+
let (reader, mut writer) = stream.into_split();
335+
let mut reader = BufReader::new(reader);
336+
let mut line = first_line;
337+
loop {
338+
if !line.trim().is_empty() {
339+
let response = dispatch_or_parse_error(line.as_bytes(), &state).await;
340+
let response_json = serde_json::to_string(&response)?;
341+
writer.write_all(response_json.as_bytes()).await?;
342+
writer.write_all(b"\n").await?;
343+
writer.flush().await?;
344+
}
345+
line.clear();
346+
let n = reader.read_line(&mut line).await?;
347+
if n == 0 {
348+
break;
349+
}
350+
}
351+
return Ok(());
336352
} else {
337353
let family_seed = resolve_daemon_family_seed()?;
338354
let mut wrapped = btsp::framing::PrependByte::new(first[0], stream);

crates/server/src/pure_jsonrpc/connection/unix.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -304,12 +304,13 @@ pub(super) async fn handle_btsp_connection(
304304
info.cipher.as_str(),
305305
info.session_id
306306
);
307-
stream
308-
} else {
309307
let (reader, mut writer) = stream.into_split();
310308
let mut reader = BufReader::new(reader);
311-
return handle_ndjson_unix(handler, &mut reader, &mut writer, first_line).await;
309+
return handle_ndjson_unix(handler, &mut reader, &mut writer, String::new()).await;
312310
}
311+
let (reader, mut writer) = stream.into_split();
312+
let mut reader = BufReader::new(reader);
313+
return handle_ndjson_unix(handler, &mut reader, &mut writer, first_line).await;
313314
} else {
314315
let family_seed = resolve_family_seed()?;
315316
let mut wrapped = btsp::framing::PrependByte::new(first[0], stream);

0 commit comments

Comments
 (0)