diff options
author | Bartek IwaĆczuk <biwanczuk@gmail.com> | 2023-03-05 08:19:34 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-03-05 12:19:34 +0000 |
commit | de0d148d933520e7ee519576c83e4ca282ee9021 (patch) | |
tree | d54f8fd691691ef5df08b61d3c558273f08bcab8 /runtime/js | |
parent | d4807f458e852e6a8385a852e7caf9dd4a5b54f7 (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')
-rw-r--r-- | runtime/js/40_process.js | 321 | ||||
-rw-r--r-- | runtime/js/40_spawn.js | 326 | ||||
-rw-r--r-- | runtime/js/90_deno_ns.js | 5 |
3 files changed, 318 insertions, 334 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 }; diff --git a/runtime/js/40_spawn.js b/runtime/js/40_spawn.js deleted file mode 100644 index 173f596cc..000000000 --- a/runtime/js/40_spawn.js +++ /dev/null @@ -1,326 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -const core = globalThis.Deno.core; -const ops = core.ops; -const primordials = globalThis.__bootstrap.primordials; -import { pathFromURL } from "internal:runtime/06_util.js"; -import { add, remove } from "internal:deno_web/03_abort_signal.js"; -const { - ArrayPrototypeMap, - ObjectEntries, - ObjectPrototypeIsPrototypeOf, - String, - TypeError, - PromisePrototypeThen, - SafePromiseAll, - SymbolFor, - Symbol, -} = primordials; -import { - readableStreamCollectIntoUint8Array, - readableStreamForRidUnrefable, - readableStreamForRidUnrefableRef, - readableStreamForRidUnrefableUnref, - ReadableStreamPrototype, - writableStreamForRid, -} from "internal:deno_web/06_streams.js"; - -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?.[add](onAbort); - - const waitPromise = core.opAsync("op_spawn_wait", this.#rid); - this.#waitPromiseId = waitPromise[promiseIdSymbol]; - this.#status = PromisePrototypeThen(waitPromise, (res) => { - this.#rid = null; - signal?.[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 }; diff --git a/runtime/js/90_deno_ns.js b/runtime/js/90_deno_ns.js index 39a1def90..f83695952 100644 --- a/runtime/js/90_deno_ns.js +++ b/runtime/js/90_deno_ns.js @@ -22,7 +22,6 @@ import * as fsEvents from "internal:runtime/40_fs_events.js"; import * as process from "internal:runtime/40_process.js"; import * as signals from "internal:runtime/40_signals.js"; import * as tty from "internal:runtime/40_tty.js"; -import * as spawn from "internal:runtime/40_spawn.js"; // TODO(bartlomieju): this is funky we have two `http` imports import * as httpRuntime from "internal:runtime/40_http.js"; @@ -148,9 +147,9 @@ const denoNs = { consoleSize: tty.consoleSize, gid: os.gid, uid: os.uid, - Command: spawn.Command, + Command: process.Command, // TODO(bartlomieju): why is this exported? - ChildProcess: spawn.ChildProcess, + ChildProcess: process.ChildProcess, }; const denoNsUnstable = { |