Class: RubyLLM::Toolbox::ManagedProcess
- Inherits:
-
Object
- Object
- RubyLLM::Toolbox::ManagedProcess
- Defined in:
- lib/ruby_llm/toolbox/process_registry.rb
Overview
A single managed background process. Its stdout and stderr are drained continuously by reader threads into bounded buffers, so a chatty child can’t deadlock on a full pipe or grow memory without limit. Output is read incrementally (each read returns only what’s new since the last one). The child runs in its own process group so it — and any children it spawns —can be killed together.
Constant Summary collapse
- MAX_BUFFER =
retain at most this many unread bytes per stream
256 * 1024
Instance Attribute Summary collapse
-
#argv ⇒ Object
readonly
Returns the value of attribute argv.
-
#id ⇒ Object
readonly
Returns the value of attribute id.
-
#name ⇒ Object
readonly
Returns the value of attribute name.
-
#pid ⇒ Object
readonly
Returns the value of attribute pid.
-
#started_at ⇒ Object
readonly
Returns the value of attribute started_at.
Instance Method Summary collapse
- #age ⇒ Object
- #exit_code ⇒ Object
-
#initialize(id:, argv:, env:, chdir:, name:, rlimits: {}) ⇒ ManagedProcess
constructor
A new instance of ManagedProcess.
-
#kill(grace: 2.0) ⇒ Object
SIGTERM the whole tree, escalate to SIGKILL after a grace period.
-
#read_new ⇒ Object
Returns and clears the output accumulated since the previous call.
- #running? ⇒ Boolean
- #status ⇒ Object
Constructor Details
#initialize(id:, argv:, env:, chdir:, name:, rlimits: {}) ⇒ ManagedProcess
Returns a new instance of ManagedProcess.
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/ruby_llm/toolbox/process_registry.rb', line 18 def initialize(id:, argv:, env:, chdir:, name:, rlimits: {}) @id = id @argv = argv @name = name @started_at = Time.now @mutex = Mutex.new @out = +"" @err = +"" @out_dropped = false @err_dropped = false opts = { unsetenv_others: true, pgroup: true } opts[:chdir] = chdir if chdir && !chdir.to_s.empty? opts.merge!(rlimits) if rlimits && !rlimits.empty? stdin, stdout, stderr, @wait_thr = Open3.popen3(env, *argv, **opts) @pid = @wait_thr.pid begin stdin.close rescue StandardError nil end @readers = [drain(stdout, :out), drain(stderr, :err)] end |
Instance Attribute Details
#argv ⇒ Object (readonly)
Returns the value of attribute argv.
16 17 18 |
# File 'lib/ruby_llm/toolbox/process_registry.rb', line 16 def argv @argv end |
#id ⇒ Object (readonly)
Returns the value of attribute id.
16 17 18 |
# File 'lib/ruby_llm/toolbox/process_registry.rb', line 16 def id @id end |
#name ⇒ Object (readonly)
Returns the value of attribute name.
16 17 18 |
# File 'lib/ruby_llm/toolbox/process_registry.rb', line 16 def name @name end |
#pid ⇒ Object (readonly)
Returns the value of attribute pid.
16 17 18 |
# File 'lib/ruby_llm/toolbox/process_registry.rb', line 16 def pid @pid end |
#started_at ⇒ Object (readonly)
Returns the value of attribute started_at.
16 17 18 |
# File 'lib/ruby_llm/toolbox/process_registry.rb', line 16 def started_at @started_at end |
Instance Method Details
#age ⇒ Object
60 61 62 |
# File 'lib/ruby_llm/toolbox/process_registry.rb', line 60 def age Time.now - @started_at end |
#exit_code ⇒ Object
52 53 54 55 56 57 58 |
# File 'lib/ruby_llm/toolbox/process_registry.rb', line 52 def exit_code return nil if running? @wait_thr.value.exitstatus rescue StandardError nil end |
#kill(grace: 2.0) ⇒ Object
SIGTERM the whole tree, escalate to SIGKILL after a grace period. Descendants are collected up front (before the parent dies and they get reparented), then signalled both via the process group and individually — so cleanup is reliable even in sandboxes that don’t deliver process-group signals to non-leader members.
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/ruby_llm/toolbox/process_registry.rb', line 81 def kill(grace: 2.0) return unless @wait_thr.alive? targets = [@pid] + descendants(@pid) signal_group("TERM") targets.each { |pid| signal_pid(pid, "TERM") } deadline = Time.now + grace sleep(0.05) while @wait_thr.alive? && Time.now < deadline signal_group("KILL") targets.each { |pid| signal_pid(pid, "KILL") } begin @wait_thr.value rescue StandardError nil end end |
#read_new ⇒ Object
Returns and clears the output accumulated since the previous call.
65 66 67 68 69 70 71 72 73 74 |
# File 'lib/ruby_llm/toolbox/process_registry.rb', line 65 def read_new @mutex.synchronize do data = { out: @out.dup, err: @err.dup, out_dropped: @out_dropped, err_dropped: @err_dropped } @out = +"" @err = +"" @out_dropped = false @err_dropped = false data end end |
#running? ⇒ Boolean
44 45 46 |
# File 'lib/ruby_llm/toolbox/process_registry.rb', line 44 def running? @wait_thr.alive? end |
#status ⇒ Object
48 49 50 |
# File 'lib/ruby_llm/toolbox/process_registry.rb', line 48 def status running? ? :running : :exited end |