mirror of
https://github.com/denoland/deno.git
synced 2025-08-31 15:57:53 +00:00
feat(runtime): two-tier subprocess API (#11618)
This commit is contained in:
parent
8b25807054
commit
8a7539cab3
13 changed files with 1323 additions and 5 deletions
206
runtime/js/40_spawn.js
Normal file
206
runtime/js/40_spawn.js
Normal 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);
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue