feat(runtime): two-tier subprocess API (#11618)

This commit is contained in:
Leo Kettmeir 2022-04-21 00:20:33 +02:00 committed by GitHub
parent 8b25807054
commit 8a7539cab3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 1323 additions and 5 deletions

206
runtime/js/40_spawn.js Normal file
View file

@ -0,0 +1,206 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
"use strict";
((window) => {
const core = window.Deno.core;
const { pathFromURL } = window.__bootstrap.util;
const { illegalConstructorKey } = window.__bootstrap.webUtil;
const {
ArrayPrototypeMap,
ObjectEntries,
String,
TypeError,
Uint8Array,
PromiseAll,
} = window.__bootstrap.primordials;
const { readableStreamForRid, writableStreamForRid } =
window.__bootstrap.streamUtils;
function spawnChild(command, {
args = [],
cwd = undefined,
clearEnv = false,
env = {},
uid = undefined,
gid = undefined,
stdin = "null",
stdout = "piped",
stderr = "piped",
} = {}) {
const child = core.opSync("op_spawn_child", {
cmd: pathFromURL(command),
args: ArrayPrototypeMap(args, String),
cwd: pathFromURL(cwd),
clearEnv,
env: ObjectEntries(env),
uid,
gid,
stdin,
stdout,
stderr,
});
return new Child(illegalConstructorKey, child);
}
async function collectOutput(readableStream) {
if (!(readableStream instanceof ReadableStream)) {
return null;
}
const bufs = [];
let size = 0;
for await (const chunk of readableStream) {
bufs.push(chunk);
size += chunk.byteLength;
}
const buffer = new Uint8Array(size);
let offset = 0;
for (const chunk of bufs) {
buffer.set(chunk, offset);
offset += chunk.byteLength;
}
return buffer;
}
class Child {
#rid;
#pid;
get pid() {
return this.#pid;
}
#stdinRid;
#stdin = null;
get stdin() {
return this.#stdin;
}
#stdoutRid;
#stdout = null;
get stdout() {
return this.#stdout;
}
#stderrRid;
#stderr = null;
get stderr() {
return this.#stderr;
}
constructor(key = null, {
rid,
pid,
stdinRid,
stdoutRid,
stderrRid,
} = null) {
if (key !== illegalConstructorKey) {
throw new TypeError("Illegal constructor.");
}
this.#rid = rid;
this.#pid = pid;
if (stdinRid !== null) {
this.#stdinRid = stdinRid;
this.#stdin = writableStreamForRid(stdinRid);
}
if (stdoutRid !== null) {
this.#stdoutRid = stdoutRid;
this.#stdout = readableStreamForRid(stdoutRid);
}
if (stderrRid !== null) {
this.#stderrRid = stderrRid;
this.#stderr = readableStreamForRid(stderrRid);
}
this.#status = core.opAsync("op_spawn_wait", this.#rid).then((res) => {
this.#rid = null;
return res;
});
}
#status;
get status() {
return this.#status;
}
async output() {
if (this.#rid === null) {
throw new TypeError("Child process has already terminated.");
}
if (this.#stdout?.locked) {
throw new TypeError(
"Can't collect output because stdout is locked",
);
}
if (this.#stderr?.locked) {
throw new TypeError(
"Can't collect output because stderr is locked",
);
}
const [status, stdout, stderr] = await PromiseAll([
this.#status,
collectOutput(this.#stdout),
collectOutput(this.#stderr),
]);
return {
status,
stdout,
stderr,
};
}
kill(signo) {
if (this.#rid === null) {
throw new TypeError("Child process has already terminated.");
}
core.opSync("op_kill", this.#pid, signo);
}
}
function spawn(command, options) { // TODO(@crowlKats): more options (like input)?
return spawnChild(command, {
...options,
stdin: "null",
stdout: "piped",
stderr: "piped",
}).output();
}
function spawnSync(command, {
args = [],
cwd = undefined,
clearEnv = false,
env = {},
uid = undefined,
gid = undefined,
} = {}) { // TODO(@crowlKats): more options (like input)?
return core.opSync("op_spawn_sync", {
cmd: pathFromURL(command),
args: ArrayPrototypeMap(args, String),
cwd: pathFromURL(cwd),
clearEnv,
env: ObjectEntries(env),
uid,
gid,
stdin: "null",
stdout: "piped",
stderr: "piped",
});
}
window.__bootstrap.spawn = {
Child,
spawnChild,
spawn,
spawnSync,
};
})(this);

View file

@ -151,5 +151,9 @@
funlockSync: __bootstrap.fs.funlockSync,
refTimer: __bootstrap.timers.refTimer,
unrefTimer: __bootstrap.timers.unrefTimer,
Child: __bootstrap.spawn.Child,
spawnChild: __bootstrap.spawn.spawnChild,
spawn: __bootstrap.spawn.spawn,
spawnSync: __bootstrap.spawn.spawnSync,
};
})(this);