class Pytty::Daemon::ProcessYield
Attributes
cmd[R]
id[R]
pid[R]
status[R]
stderr_path[R]
stdin[RW]
stdout_path[R]
Public Class Methods
new(cmd, id:nil, env:{})
click to toggle source
# File lib/pytty/daemon/process_yield.rb, line 9 def initialize(cmd, id:nil, env:{}) @cmd = cmd @env = env @pid = nil @status = nil @id = id || SecureRandom.uuid @stdouts = {} @stderrs = {} @stdout_path = File.join Pytty::Daemon.pytty_path, "#{@id}.stdout" @stderr_path = File.join Pytty::Daemon.pytty_path, "#{@id}.stderr" @stdin = Async::Queue.new end
Public Instance Methods
add_stderr(stderr)
click to toggle source
# File lib/pytty/daemon/process_yield.rb, line 35 def add_stderr(stderr) notification = Async::Notification.new @stderrs[notification] = stderr notification end
add_stdout(stdout)
click to toggle source
# File lib/pytty/daemon/process_yield.rb, line 30 def add_stdout(stdout) notification = Async::Notification.new @stdouts[notification] = stdout notification end
cleanup()
click to toggle source
# File lib/pytty/daemon/process_yield.rb, line 57 def cleanup @status = nil File.unlink @stdout_path if File.exist? @stdout_path File.unlink @stderr_path if File.exist? @stderr_path end
running?()
click to toggle source
# File lib/pytty/daemon/process_yield.rb, line 41 def running? # test by killing? !@pid.nil? end
signal(sig)
click to toggle source
# File lib/pytty/daemon/process_yield.rb, line 217 def signal(sig) return unless @pid Process.kill(sig.upcase, @pid) rescue Errno::ESRCH => ex raise ex unless ex.message == "No such process" end
spawn(tty: false, interactive: false)
click to toggle source
# File lib/pytty/daemon/process_yield.rb, line 64 def spawn(tty: false, interactive: false) return false if running? cleanup stdout_appender = Async::IO::Stream.new( File.open @stdout_path, "a" ) stderr_appender = Async::IO::Stream.new( File.open @stderr_path, "a" ) executable, *args = @cmd # @env.merge!({ # "TERM" => "xterm" # }) Async::Task.current.async do |task| real_stdout, real_stdin, real_stderr, @pid, wait_thr = begin if tty stderr_reader, stderr_writer = IO.pipe p ["spawn", "PTY"] #TODO: if bash is started, no prompt is written in stderr or stdout p_stdout, p_stdin, pid = PTY.spawn @env, executable, *args, err: stderr_writer [p_stdout, p_stdin, stderr_reader, pid] else p_stdin, p_stdout, p_stderr, p_wait_thr = Open3.popen3 @env, executable, *args, {} [p_stdout, p_stdin, p_stderr, p_wait_thr.pid, p_wait_thr] end rescue Errno::ENOENT => ex raise unless ex.message == "No such file or directory - #{executable}" end next unless @pid #command failed to spawn async_stdout = Async::IO::Generic.new real_stdout async_stdin = Async::IO::Generic.new real_stdin async_stderr = Async::IO::Generic.new real_stderr task_stdin_writer = if interactive task.async do |subtask| p ["task_stdin_writer", "started"] while c = @stdin.dequeue do async_stdin.write c end rescue Async::Stop => ex puts "async_stdin#write Async::Stop" rescue Exception => ex puts "async_stdin#write: #{ex.inspect}" ensure async_stdin.close p ["async_stdin", "closed"] end else nil end task_stderr_writer = task.async do p ["task_stderr_writer", "started"] while c = async_stderr.read(1) do stderr_appender.write c stderr_appender.flush @stderrs.each do |notification, stderr| begin stderr.write "2#{c}" rescue Async::HTTP::Body::Writable::Closed puts "signaling error" notification.signal @stderrs.delete notification rescue => ex raise ex end end end p ["task_stderr_writer", "async_stderr has no more read"] rescue Exception => ex p ["async_stderr ex:", ex] ensure stderr_appender.flush stderr_appender.close puts "stderr_appender closed" async_stderr.close puts "async_stderr closed" end task_stdout_writer = task.async do p ["task_stdout_writer", "started"] while c = async_stdout.read(1) stdout_appender.write c stdout_appender.flush @stdouts.each do |notification, stdout| begin stdout.write "1#{c}" rescue Errno::EPIPE, Errno::EPROTOTYPE => ex notification.signal @stdouts.delete notification end end end p ["task_stdout_writer", "stopped"] rescue Async::Stop signal "kill" rescue Exception => ex p ["async_stdout", ex] ensure process_status = if wait_thr wait_thr.value else begin Process.wait(@pid) $? rescue Errno::ECHILD => ex raise ex unless ex.message == "No child processes" puts "NO CHILD PROCESS" nil end end @status = if process_status && process_status.exitstatus process_status.exitstatus else Signal.signame process_status.termsig end puts "exited #{@id} with status: #{@status}" @pid = nil task_stdin_writer.stop if task_stdin_writer @stdouts.each do |notification, stdout| notification.signal @stdouts.delete notification end @stderrs.each do |notification, stderr| notification.signal @stdouts.delete notification end Pytty::Daemon.dump end end if @pid p ["spawned", id] return true else p ["failed to spawn"] @status = 127 return false end end
to_json(json_generator_state=nil)
click to toggle source
# File lib/pytty/daemon/process_yield.rb, line 46 def to_json(json_generator_state=nil) { id: @id, pid: @pid, status: @status, cmd: @cmd, env: @env, running: running? }.to_json end