Skip to content

Commit 61a034e

Browse files
authored
fix(repl): handle EOF gracefully in JSON REPL mode (#32108)
Closes the JSON REPL cleanly when the IPC pipe is closed (e.g. ctrl+d / EOF) instead of printing `error: unexpected end of file` and exiting with code 1.
1 parent 163e95d commit 61a034e

File tree

3 files changed

+21
-4
lines changed

3 files changed

+21
-4
lines changed

cli/tools/repl/mod.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -310,15 +310,25 @@ async fn run_json(mut repl_session: ReplSession) -> Result<i32, AnyError> {
310310

311311
loop {
312312
let mut line_fut = std::pin::pin!(async {
313-
let len = receiver.read_u32_le().await?;
314-
let mut buf = vec![0; len as _];
313+
let len = match receiver.read_u32_le().await {
314+
Ok(n) => n,
315+
// Treat the case of 0-byte input as "expected" EOF
316+
Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => {
317+
return Ok(None);
318+
}
319+
Err(e) => return Err(e),
320+
};
321+
let mut buf = vec![0; len as usize];
315322
receiver.read_exact(&mut buf).await?;
316-
Ok::<_, AnyError>(buf)
323+
Ok(Some(buf))
317324
});
318325
let mut poll_worker = true;
319326
let line = loop {
320327
tokio::select! {
321-
line = &mut line_fut => break line?,
328+
line = &mut line_fut => match line? {
329+
Some(line) => break line,
330+
None => return Ok(repl_session.worker.exit_code()),
331+
},
322332
_ = repl_session.run_event_loop(), if poll_worker => {
323333
poll_worker = false;
324334
continue;

tests/specs/repl/json/test.out

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
hello
33
{ type: "RunSuccess", output: "2" }
44
{ type: "RunFailure", text: "Uncaught" }
5+
0

tests/specs/repl/json/test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const {
33
getExtraPipeRids,
44
writableStreamForRid,
55
readableStreamForRid,
6+
core: { close },
67
} = Deno[Deno.internal];
78

89
const command = new Deno.Command(Deno.execPath(), {
@@ -99,3 +100,8 @@ const readable = readableStreamForRid(pipeRid);
99100
console.log(JSON.parse(new TextDecoder().decode(value)));
100101
reader.releaseLock();
101102
}
103+
104+
// Test EOF (ctrl+d): closing the underlying socket should cause a clean exit
105+
close(pipeRid);
106+
const status = await child.status;
107+
console.log(status.code);

0 commit comments

Comments
 (0)