class

Krikri::LocalExecutor

Inherits Reference < Object

Constants

DRAIN_GRACE_PERIOD = 200.milliseconds

How long to keep draining stdout/stderr after the process itself has already exited, before giving up on a pipe that hasn't reached EOF.

Passing output/error as a plain IO (as this used to) makes Process#wait itself block until BOTH the process exits AND its stdout/stderr pipes reach EOF - which requires every process holding a duplicate of the pipe's write end to close it, not just the direct child. A shell command shaped like sleep N && daemon & backgrounds a shell that blocks in its own wait() on daemon (a trailing & backgrounds the whole &&-list, and nohup only suppresses SIGHUP - it doesn't exempt a child from its parent's wait()), so if daemon never exits, neither does that shell, and the pipe it's still holding open never reaches EOF - hanging this call forever even though the actual process we spawned already finished.

Using Process::Redirect::Pipe instead and managing our own drain fibers lets us wait for the process's own exit independently of the pipes, then give any already-buffered output a short, bounded window to be read before force-closing the pipes and moving on - a real command's own output is already fully written into the pipe's kernel buffer by the time it exits, so this window only matters for draining that tail, not for waiting on an unrelated backgrounded process that was never supposed to be waited on in the first place.

LEADING_ENV_ASSIGNMENT = /\A[A-Za-z_][A-Za-z0-9_]*=/

A leading NAME=value token (no metacharacters of its own, so the regex above misses it) is shell env-assignment syntax, not part of argv[0] - DEBIAN_FRONTEND=noninteractive apt-get install ... (apt.cr's own real command, no export/; involved) would otherwise get argv-split with "DEBIAN_FRONTEND=noninteractive" as the executable name and crash with ENOENT. Only checked at the start of the string - = inside a later argument (--opt=value) is completely ordinary and not env-assignment syntax.

SHELL_METACHARACTERS = /[|<>&;`$*?\[\]{}~\\\n]/

Any of these anywhere in the command string means it actually needs shell semantics (pipes, redirection, substitution, globbing, home-dir expansion, escaping, sequencing) - real Ansible's own local/ssh connection plugins make the same call (_low_level_execute_command's executable handling). Absent all of these, splitting into argv and exec'ing directly is behaviorally identical to bash -c but skips forking a whole extra shell process per command.

Class methods

copy_file(src : String, dest : String) : Nil

Copy file locally

Source
dir_exists?(path : String) : Bool

Check if directory exists

Source
exec(command : String, force_shell : Bool = false) : NamedTuple(exit_code: Int32, stdout: String, stderr: String)

Execute command locally

Source
file_exists?(path : String) : Bool

Check if file exists

Source