Class: AgentsControl::Executor
- Inherits:
-
Object
- Object
- AgentsControl::Executor
- Defined in:
- lib/agents_control/executor.rb
Overview
The single point through which the utility talks to the outside world.
Exists for the "adapter isolation" rule: osascript can hang dead when iTerm2 shows a beachball, and without a hard timeout that takes the whole daemon down with it. No adapter calls Open3 directly.
Tests swap this for FakeExecutor — no real terminal needed in CI.
Defined Under Namespace
Classes: Result
Constant Summary collapse
- DEFAULT_TIMEOUT =
10- TIMEOUT_STATUS =
status 124 — coreutils timeout(1) convention, 127 — "command not found".
124- NOT_FOUND_STATUS =
127
Instance Method Summary collapse
-
#initialize(timeout: DEFAULT_TIMEOUT) ⇒ Executor
constructor
A new instance of Executor.
-
#run(*argv, stdin: nil, timeout: @timeout) ⇒ Object
Always returns a Result — never raises.
Constructor Details
#initialize(timeout: DEFAULT_TIMEOUT) ⇒ Executor
Returns a new instance of Executor.
26 27 28 |
# File 'lib/agents_control/executor.rb', line 26 def initialize(timeout: DEFAULT_TIMEOUT) @timeout = timeout end |
Instance Method Details
#run(*argv, stdin: nil, timeout: @timeout) ⇒ Object
Always returns a Result — never raises. A failing external command must not take the daemon down with it.
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/agents_control/executor.rb', line 32 def run(*argv, stdin: nil, timeout: @timeout) Open3.popen3(*argv) do |input, output, errors, wait_thread| write_stdin(input, stdin) # Read on separate threads: otherwise a process that fills the # stderr buffer blocks waiting to be read, while we block waiting # for it to exit. report_on_exception is off on purpose: on a # timeout, the reader threads find popen3's pipe already closed # and raise IOError. That's expected and handled below, but Ruby # prints that trace to stderr by default, where it looks like a # real crash in the utility's console. out_reader = Thread.new { output.read } err_reader = Thread.new { errors.read } [out_reader, err_reader].each { |thread| thread.report_on_exception = false } return kill(wait_thread, out_reader, err_reader, timeout) unless wait_thread.join(timeout) Result.new( stdout: out_reader.value.to_s, stderr: err_reader.value.to_s, status: wait_thread.value.exitstatus || -1 ) end rescue Errno::ENOENT, Errno::EACCES => e Result.new(stdout: "", stderr: e., status: NOT_FOUND_STATUS) end |