diff options
author | Leo Kettmeir <crowlkats@toaxl.com> | 2022-04-21 00:20:33 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-04-21 00:20:33 +0200 |
commit | 8a7539cab36699465ec6e37455c54fa86f3c0cbe (patch) | |
tree | c3df15f3b673d1ec1a9c4ffada1a9274e3aca942 /runtime/js/40_spawn.js | |
parent | 8b258070542a81d217226fe832b26d81cf20113d (diff) |
feat(runtime): two-tier subprocess API (#11618)
Diffstat (limited to 'runtime/js/40_spawn.js')
-rw-r--r-- | runtime/js/40_spawn.js | 206 |
1 files changed, 206 insertions, 0 deletions
diff --git a/runtime/js/40_spawn.js b/runtime/js/40_spawn.js new file mode 100644 index 000000000..c55ce657d --- /dev/null +++ b/runtime/js/40_spawn.js @@ -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); |