Imported as a linear vendor commit so forge rebases do not replay the upstream root tree. git-subtree-dir: third_party/pi git-subtree-split: 71023aec1546d6d677a65669c5665c0927ce0dd0
5.1 KiB
5.1 KiB
Semantics
The reference is Durable's NodeExecutionEnv (packages/durable/src/env/node.ts) on the remote machine. Durable's
env conformance suite and test/differential.test.ts check each rule below.
Split of work
| Concern | Where |
|---|---|
Path resolution: ~ (remote home), file://, relative to cwd; on Windows drive-relative paths against the remote's working directories |
client, the remote system's path rules |
| Abort checkpoints of each method | client, as NodeExecutionEnv |
Error code mapping (ENOENT → not_found, ...) |
client, as NodeExecutionEnv's toFileError |
readFile's read sizes, size limit and decoding (StringDecoder, byte-order mark kept) over the daemon's open(path, "r") |
client |
writeFile in 512 KiB writes with an abort check before each; appendFile without checks |
client, over one open file |
| Line reader | client, positional reads and Durable's StreamDecoder |
| Watching: snapshots, native events, polling | daemon, a port of Durable's NodeFileWatcher |
| Timeout validation, result precedence (callback error, timeout, abort, spill failure, exit code) | client |
| System calls, processes, spill files, line scans | daemon |
| Command output decoding: per stream, WHATWG UTF-8, only a leading byte-order mark dropped | daemon (encoding_rs) |
Rules the standard libraries do not follow
- Recursive
mkdir: an existing directory is fine, an existing file fails withEEXIST, a file in the way of a parent fails withENOTDIR. Writes create missing parents this way. rm: a missing path fails unlessforce; a directory withoutrecursivefails withERR_FS_EISDIR(FileErrorcodeunknown).mkdtemp:mkdtemp(3)of<tmpdir>/<prefix>XXXXXX; the prefix is joined with POSIXpath.join, so../xworks.tmpdir:TMPDIR,TMP,TEMP, then/tmp(Termux:$PREFIX/tmp), trailing slash removed.listDir: names in byte order, like libuv'sscandir; fails if any entry cannot be lstat'ed.openDirReaderkeeps directory order, skips entries removed meanwhile, and fills each page after skipping. Names that are not UTF-8 are decoded with replacement characters and lstat'ed under that name, so they fail withENOENTas in Node.openBinaryReader: nonblocking open; directories fail withis_directory, other non-regular files withinvalid;noFollowrefuses a final symbolic link (O_NOFOLLOW).readBinaryFile,readTextFileandopenTextLineReaderopen any file likeopen(path, "r"):/dev/nullreads empty, a FIFO blocks until it has a writer, and a directory fails when read (a line reader atreadLine). A regular file is read up to its size at open; other files to their end.listFileshas noNodeExecutionEnvcounterpart: the daemon implements it for remote and local environments alike (LocalExecutionEnvruns a daemon on the same machine), so both list with the same code. Unlikefd, symbolic links are not followed; unlikegit ls-files, a tracked file that matches an ignore rule is left out.readBinaryFilereturns aBuffer, reader reads a plainUint8Array, as Node does.- Windows: error codes are libuv's translations;
rmof a symbolic link or junction to a directory removes the link; creating a file where a directory exists fails withEISDIR; modification times before 1970 are negative.
Commands
- A string runs through the shell: a configured
shellPaththat does not exist (following links) fails withshell_unavailable; otherwise/bin/bash,which bash, thensh, with-c. An argv array runs its program directly; on Windows it is searched like libuv does (working directory, thenPATH, adding.comand.exe), and batch files are refused. - The working directory must exist (
spawn_error). - Environment: with
inheritEnv(default), the daemon's environment, thenshellEnv, thenenv; without it, onlyenv. - Each command gets a new session (process group), default signal dispositions and an empty signal mask. Timeout,
abort and
cleanup()kill the group withSIGKILL. - After the process exits, output is still collected until both pipes end, or 100 ms pass without output; the timeout still applies meanwhile. The pipes are closed when the command settles, even if a descendant keeps them open.
- An abort settles as
aborted;cleanup()kills without aborting, so the command settles with exit code 137. - A process killed by a signal reports
128 + signal. - Spill: once the output crosses
spill.afterBytesorspill.afterLines(counted over raw chunks in arrival order, as Node counts), the complete raw output goes topi-output-<uuid>.login a freshtmp-directory.
Watching
- The daemon runs Durable's
NodeFileWatchernext to the files: native events (inotify, FSEvents) only trigger a debounced rescan, and changes are the difference between snapshots plus the event paths. Reads are not events. - Windows and network or FUSE file systems poll, as
NodeExecutionEnvdoes; running out of native watches switches to polling and reportsoverflow. - When the connection is lost, the client opens the watcher again once a new daemon starts and reports
overflow.