Skip to content

Commit b737487

Browse files
authored
fix(runtime): make readlines aboartable (#65)
1 parent 0d0044b commit b737487

File tree

4 files changed

+51
-7
lines changed

4 files changed

+51
-7
lines changed

src/runtime/deps.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export type {
1313
ParsedPath,
1414
} from "https://deno.land/[email protected]/path/mod.ts";
1515
export * as io from "https://deno.land/[email protected]/io/mod.ts";
16+
export { BufReader } from "https://deno.land/[email protected]/io/buffer.ts";
1617
export type {
1718
ReadableStreamFromReaderOptions as IOReadableStreamFromReaderOptions,
1819
ReadLineResult,
@@ -45,3 +46,4 @@ export type {
4546
} from "https://deno.land/[email protected]/flags/mod.ts";
4647
export { colors } from "https://deno.land/x/[email protected]/ansi/colors.ts";
4748
export { default as shq } from "https://esm.sh/[email protected]";
49+
export { concat } from "https://deno.land/[email protected]/bytes/mod.ts";

src/runtime/lib/readline.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { BufReader, concat } from "../deps.ts";
2+
3+
export async function* readLines(
4+
reader: Deno.Reader,
5+
isCanceld: () => boolean,
6+
): AsyncIterableIterator<string> {
7+
const bufReader = new BufReader(reader);
8+
let chunks: Uint8Array[] = [];
9+
const decoder = new TextDecoder();
10+
while (!isCanceld()) {
11+
const res = await bufReader.readLine();
12+
if (!res) {
13+
if (chunks.length > 0) {
14+
yield decoder.decode(concat(...chunks));
15+
}
16+
break;
17+
}
18+
chunks.push(res.line);
19+
if (!res.more) {
20+
yield decoder.decode(concat(...chunks));
21+
chunks = [];
22+
}
23+
}
24+
}

src/runtime/process.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/// <reference path="../../types.d.ts" />
22

33
import { Deferred, deferred } from "./deps.ts";
4+
import { readLines } from "./lib/readline.ts";
45
import { ProcessError } from "./process_error.ts";
56
import { ProcessOutput } from "./process_output.ts";
67

@@ -21,6 +22,7 @@ export class Process implements Promise<ProcessOutput> {
2122
#maxRetries = 0;
2223
#retries = 0;
2324
#throwErrors = true;
25+
#isKilled = false;
2426

2527
constructor(cmd: string, { errorContext }: ProcessOptions = {}) {
2628
this.#cmd = cmd;
@@ -66,6 +68,7 @@ export class Process implements Promise<ProcessOutput> {
6668
}
6769

6870
kill(signo: Deno.Signal): void {
71+
this.#isKilled = true;
6972
this.#process.kill(signo);
7073
}
7174

@@ -117,9 +120,19 @@ export class Process implements Promise<ProcessOutput> {
117120
const [status] = await Promise.all([
118121
this.#process.status(),
119122
this.#process.stdout &&
120-
read(this.#process.stdout, [stdout, combined], Deno.stdout),
123+
read(
124+
this.#process.stdout,
125+
[stdout, combined],
126+
Deno.stdout,
127+
() => this.#isKilled,
128+
),
121129
this.#process.stderr &&
122-
read(this.#process.stderr, [stderr, combined], Deno.stderr),
130+
read(
131+
this.#process.stderr,
132+
[stderr, combined],
133+
Deno.stderr,
134+
() => this.#isKilled,
135+
),
123136
]);
124137

125138
let output = new ProcessOutput({
@@ -169,8 +182,9 @@ async function read(
169182
reader: Deno.Reader,
170183
results: Array<Array<string>>,
171184
outputStream: Deno.Writer,
185+
isCanceld: () => boolean,
172186
): Promise<Error | void> {
173-
for await (const line of io.readLines(reader)) {
187+
for await (const line of readLines(reader, isCanceld)) {
174188
for (const result of results) {
175189
result.push(line + "\n");
176190
}

test.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,14 +125,18 @@ Deno.test({
125125
const start = Date.now();
126126
await assertRejects(
127127
async () => {
128+
// @TODO: kill is not working with old bash and pipefail -euo.
129+
const prefix = $.prefix;
130+
$.prefix = "";
128131
$.shell = "/bin/bash";
129132
const child = $`sleep 10`;
130-
child.kill("SIGKILL");
133+
setTimeout(() => child.kill("SIGKILL"), 10);
131134
await child;
135+
$.prefix = prefix;
132136
},
133137
ProcessError,
134138
);
135-
assert(Date.now() - start < 100, "process.kill() took too long");
139+
assert(Date.now() - start < 200, "process.kill() took too long");
136140
},
137141
});
138142

@@ -144,12 +148,12 @@ Deno.test({
144148
async () => {
145149
$.shell = "/bin/zsh";
146150
const child = $`sleep 10`;
147-
child.kill("SIGKILL");
151+
setTimeout(() => child.kill("SIGKILL"), 10);
148152
await child;
149153
},
150154
ProcessError,
151155
);
152-
assert(Date.now() - start < 100, "process.kill() took too long");
156+
assert(Date.now() - start < 200, "process.kill() took too long");
153157
},
154158
});
155159

0 commit comments

Comments
 (0)