summaryrefslogtreecommitdiff
path: root/runtime/js/40_process.js
diff options
context:
space:
mode:
authorBartek IwaƄczuk <biwanczuk@gmail.com>2023-03-05 08:19:34 -0400
committerGitHub <noreply@github.com>2023-03-05 12:19:34 +0000
commitde0d148d933520e7ee519576c83e4ca282ee9021 (patch)
treed54f8fd691691ef5df08b61d3c558273f08bcab8 /runtime/js/40_process.js
parentd4807f458e852e6a8385a852e7caf9dd4a5b54f7 (diff)
refactor(runtime): merge "spawn" into "process" (#18022)
This commit merges "runtime/js/40_spawn.js" into "runtime/js/40_process.js", and "runtime::ops::spawn" into "runtime::ops::process". It makes little sense to have them separated given that we want to factor out these APIs into a separate extension crate.
Diffstat (limited to 'runtime/js/40_process.js')
-rw-r--r--runtime/js/40_process.js321
1 files changed, 316 insertions, 5 deletions
diff --git a/runtime/js/40_process.js b/runtime/js/40_process.js
index bb224ae9c..9599b7b74 100644
--- a/runtime/js/40_process.js
+++ b/runtime/js/40_process.js
@@ -2,10 +2,6 @@
const core = globalThis.Deno.core;
const ops = core.ops;
-import { FsFile } from "internal:runtime/30_fs.js";
-import { readAll } from "internal:deno_io/12_io.js";
-import { pathFromURL } from "internal:runtime/06_util.js";
-import { assert } from "internal:deno_web/00_infra.js";
const primordials = globalThis.__bootstrap.primordials;
const {
ArrayPrototypeMap,
@@ -14,7 +10,25 @@ const {
ObjectEntries,
SafeArrayIterator,
String,
+ ObjectPrototypeIsPrototypeOf,
+ PromisePrototypeThen,
+ SafePromiseAll,
+ SymbolFor,
+ Symbol,
} = primordials;
+import { FsFile } from "internal:runtime/30_fs.js";
+import { readAll } from "internal:deno_io/12_io.js";
+import { pathFromURL } from "internal:runtime/06_util.js";
+import { assert } from "internal:deno_web/00_infra.js";
+import * as abortSignal from "internal:deno_web/03_abort_signal.js";
+import {
+ readableStreamCollectIntoUint8Array,
+ readableStreamForRidUnrefable,
+ readableStreamForRidUnrefableRef,
+ readableStreamForRidUnrefableUnref,
+ ReadableStreamPrototype,
+ writableStreamForRid,
+} from "internal:deno_web/06_streams.js";
function opKill(pid, signo, apiName) {
ops.op_kill(pid, signo, apiName);
@@ -130,4 +144,301 @@ function run({
return new Process(res);
}
-export { kill, Process, run };
+const illegalConstructorKey = Symbol("illegalConstructorKey");
+const promiseIdSymbol = SymbolFor("Deno.core.internalPromiseId");
+
+function spawnChildInner(opFn, command, apiName, {
+ args = [],
+ cwd = undefined,
+ clearEnv = false,
+ env = {},
+ uid = undefined,
+ gid = undefined,
+ stdin = "null",
+ stdout = "piped",
+ stderr = "piped",
+ signal = undefined,
+ windowsRawArguments = false,
+} = {}) {
+ const child = opFn({
+ cmd: pathFromURL(command),
+ args: ArrayPrototypeMap(args, String),
+ cwd: pathFromURL(cwd),
+ clearEnv,
+ env: ObjectEntries(env),
+ uid,
+ gid,
+ stdin,
+ stdout,
+ stderr,
+ windowsRawArguments,
+ }, apiName);
+ return new ChildProcess(illegalConstructorKey, {
+ ...child,
+ signal,
+ });
+}
+
+function spawnChild(command, options = {}) {
+ return spawnChildInner(
+ ops.op_spawn_child,
+ command,
+ "Deno.Command().spawn()",
+ options,
+ );
+}
+
+function collectOutput(readableStream) {
+ if (
+ !(ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, readableStream))
+ ) {
+ return null;
+ }
+
+ return readableStreamCollectIntoUint8Array(readableStream);
+}
+
+class ChildProcess {
+ #rid;
+ #waitPromiseId;
+ #unrefed = false;
+
+ #pid;
+ get pid() {
+ return this.#pid;
+ }
+
+ #stdin = null;
+ get stdin() {
+ if (this.#stdin == null) {
+ throw new TypeError("stdin is not piped");
+ }
+ return this.#stdin;
+ }
+
+ #stdoutRid;
+ #stdout = null;
+ get stdout() {
+ if (this.#stdout == null) {
+ throw new TypeError("stdout is not piped");
+ }
+ return this.#stdout;
+ }
+
+ #stderrRid;
+ #stderr = null;
+ get stderr() {
+ if (this.#stderr == null) {
+ throw new TypeError("stderr is not piped");
+ }
+ return this.#stderr;
+ }
+
+ constructor(key = null, {
+ signal,
+ rid,
+ pid,
+ stdinRid,
+ stdoutRid,
+ stderrRid,
+ } = null) {
+ if (key !== illegalConstructorKey) {
+ throw new TypeError("Illegal constructor.");
+ }
+
+ this.#rid = rid;
+ this.#pid = pid;
+
+ if (stdinRid !== null) {
+ this.#stdin = writableStreamForRid(stdinRid);
+ }
+
+ if (stdoutRid !== null) {
+ this.#stdoutRid = stdoutRid;
+ this.#stdout = readableStreamForRidUnrefable(stdoutRid);
+ }
+
+ if (stderrRid !== null) {
+ this.#stderrRid = stderrRid;
+ this.#stderr = readableStreamForRidUnrefable(stderrRid);
+ }
+
+ const onAbort = () => this.kill("SIGTERM");
+ signal?.[abortSignal.add](onAbort);
+
+ const waitPromise = core.opAsync("op_spawn_wait", this.#rid);
+ this.#waitPromiseId = waitPromise[promiseIdSymbol];
+ this.#status = PromisePrototypeThen(waitPromise, (res) => {
+ this.#rid = null;
+ signal?.[abortSignal.remove](onAbort);
+ return res;
+ });
+ }
+
+ #status;
+ get status() {
+ return this.#status;
+ }
+
+ async output() {
+ 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 { 0: status, 1: stdout, 2: stderr } = await SafePromiseAll([
+ this.#status,
+ collectOutput(this.#stdout),
+ collectOutput(this.#stderr),
+ ]);
+
+ return {
+ success: status.success,
+ code: status.code,
+ signal: status.signal,
+ get stdout() {
+ if (stdout == null) {
+ throw new TypeError("stdout is not piped");
+ }
+ return stdout;
+ },
+ get stderr() {
+ if (stderr == null) {
+ throw new TypeError("stderr is not piped");
+ }
+ return stderr;
+ },
+ };
+ }
+
+ kill(signo = "SIGTERM") {
+ if (this.#rid === null) {
+ throw new TypeError("Child process has already terminated.");
+ }
+ ops.op_kill(this.#pid, signo, "Deno.Child.kill()");
+ }
+
+ ref() {
+ this.#unrefed = false;
+ core.refOp(this.#waitPromiseId);
+ if (this.#stdout) readableStreamForRidUnrefableRef(this.#stdout);
+ if (this.#stderr) readableStreamForRidUnrefableRef(this.#stderr);
+ }
+
+ unref() {
+ this.#unrefed = true;
+ core.unrefOp(this.#waitPromiseId);
+ if (this.#stdout) readableStreamForRidUnrefableUnref(this.#stdout);
+ if (this.#stderr) readableStreamForRidUnrefableUnref(this.#stderr);
+ }
+}
+
+function spawn(command, options) {
+ if (options?.stdin === "piped") {
+ throw new TypeError(
+ "Piped stdin is not supported for this function, use 'Deno.Command().spawn()' instead",
+ );
+ }
+ return spawnChildInner(
+ ops.op_spawn_child,
+ command,
+ "Deno.Command().output()",
+ options,
+ )
+ .output();
+}
+
+function spawnSync(command, {
+ args = [],
+ cwd = undefined,
+ clearEnv = false,
+ env = {},
+ uid = undefined,
+ gid = undefined,
+ stdin = "null",
+ stdout = "piped",
+ stderr = "piped",
+ windowsRawArguments = false,
+} = {}) {
+ if (stdin === "piped") {
+ throw new TypeError(
+ "Piped stdin is not supported for this function, use 'Deno.Command().spawn()' instead",
+ );
+ }
+ const result = ops.op_spawn_sync({
+ cmd: pathFromURL(command),
+ args: ArrayPrototypeMap(args, String),
+ cwd: pathFromURL(cwd),
+ clearEnv,
+ env: ObjectEntries(env),
+ uid,
+ gid,
+ stdin,
+ stdout,
+ stderr,
+ windowsRawArguments,
+ });
+ return {
+ success: result.status.success,
+ code: result.status.code,
+ signal: result.status.signal,
+ get stdout() {
+ if (result.stdout == null) {
+ throw new TypeError("stdout is not piped");
+ }
+ return result.stdout;
+ },
+ get stderr() {
+ if (result.stderr == null) {
+ throw new TypeError("stderr is not piped");
+ }
+ return result.stderr;
+ },
+ };
+}
+
+class Command {
+ #command;
+ #options;
+
+ constructor(command, options) {
+ this.#command = command;
+ this.#options = options;
+ }
+
+ output() {
+ if (this.#options?.stdin === "piped") {
+ throw new TypeError(
+ "Piped stdin is not supported for this function, use 'Deno.Command.spawn()' instead",
+ );
+ }
+ return spawn(this.#command, this.#options);
+ }
+
+ outputSync() {
+ if (this.#options?.stdin === "piped") {
+ throw new TypeError(
+ "Piped stdin is not supported for this function, use 'Deno.Command.spawn()' instead",
+ );
+ }
+ return spawnSync(this.#command, this.#options);
+ }
+
+ spawn() {
+ const options = {
+ ...(this.#options ?? {}),
+ stdout: this.#options?.stdout ?? "inherit",
+ stderr: this.#options?.stderr ?? "inherit",
+ stdin: this.#options?.stdin ?? "inherit",
+ };
+ return spawnChild(this.#command, options);
+ }
+}
+
+export { ChildProcess, Command, kill, Process, run };