Class: RubyLLM::Toolbox::ManagedProcess

Inherits:
Object
  • Object
show all
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

Instance Method Summary collapse

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

#argvObject (readonly)

Returns the value of attribute argv.



16
17
18
# File 'lib/ruby_llm/toolbox/process_registry.rb', line 16

def argv
  @argv
end

#idObject (readonly)

Returns the value of attribute id.



16
17
18
# File 'lib/ruby_llm/toolbox/process_registry.rb', line 16

def id
  @id
end

#nameObject (readonly)

Returns the value of attribute name.



16
17
18
# File 'lib/ruby_llm/toolbox/process_registry.rb', line 16

def name
  @name
end

#pidObject (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_atObject (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

#ageObject



60
61
62
# File 'lib/ruby_llm/toolbox/process_registry.rb', line 60

def age
  Time.now - @started_at
end

#exit_codeObject



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_newObject

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

Returns:

  • (Boolean)


44
45
46
# File 'lib/ruby_llm/toolbox/process_registry.rb', line 44

def running?
  @wait_thr.alive?
end

#statusObject



48
49
50
# File 'lib/ruby_llm/toolbox/process_registry.rb', line 48

def status
  running? ? :running : :exited
end