Open4 Ruby Library

repository·master·Indexed 41 days ago

https://github.com/ahoward/open4

A Ruby library for simplifying child process management. It provides tools for spawning processes with access to PIDs and I/O handles (stdin, stdout, stderr) via methods like popen4, pfork4, and spawn. Features include background process execution with the bg method, fine-grained file descriptor control with popen4ext, process termination via maim, and built-in timeout and exit status validation.

Tokens
4.1K
Snippets
19
Records
20
Agent score
51%

What's inside Open4

  1. Handle exit status validation in spawn

    master

    When using spawn, you can control how the library reacts to the child process's exit status using the following options:

    • :status => true: Returns the Process::Status object without raising an error on non-zero exits.
    • :status => integer: Raises an error if the exit status is not equal to the provided integer.
    • :status => [int1, int2, ...]: Raises an error if the exit status is not in the provided list.
    • :exitstatus => [int1, int2, ...]: (Older versions) Similar to :status with an array of acceptable statuses.
    include Open4
    
    # Raises error if exit status is not 0 or 42
    spawn 'ruby -e "exit 42"', :exitstatus => [0, 42]
    
    # Returns status object instead of raising error
    status = spawn 'ruby -e "exit 42"', :status => true
  2. Configure timeouts in Open4

    master

    Open4 supports several timeout configurations to ensure processes do not run indefinitely or hang on I/O:

    • :timeout: An overall timeout for the command execution.
    • :stdout_timeout: A timeout specifically for the stdout stream.
    • :stdin_timeout: A timeout specifically for the stdin stream.
    # Overall timeout
    open4.spawn cmd, :timeout => 60
    
    # Individual I/O timeouts
    open4.spawn cmd, :stdout_timeout => 2
    open4.spawn cmd, :stdin_timeout => 1
  3. Use Open4::popen4 in block form

    master

    When calling Open4::popen4 with a block, the child process is automatically waited for once the block finishes. The method returns the Process::Status object of the child process.

    require 'open4'
    
    status = Open4::popen4("sh") do |pid, stdin, stdout, stderr|
      stdin.puts "echo 42.out"
      stdin.puts "echo 42.err 1>&2"
      stdin.close
    
      puts "pid        : #{ pid }"
      puts "stdout     : #{ stdout.read.strip }"
      puts "stderr     : #{ stderr.read.strip }"
    end
    
    puts "status     : #{ status.inspect }"
    puts "exitstatus : #{ status.exitstatus }"
  4. Check if a process is alive

    master

    Use alive?(pid) (or the alias alive) to check if a process with the given PID is currently running. It returns true if the process exists and false otherwise.

    if Open4.alive?(1234)
      puts "Process is running"
    end
  5. Run processes in the background with Open4.bg

    master

    The bg (background) method runs a process in a separate thread. The returned thread object has dynamic methods added to it, including .pid and .exitstatus (which is a blocking call).

    require 'yaml'
    require 'open4'
    include Open4
    
    stdin = '42'
    stdout = ''
    stderr = ''
    
    # Returns a thread
    t = bg 'ruby -e"sleep 4; puts ARGF.read"', 0=>stdin, 1=>stdout, 2=>stderr
    
    # t.exitstatus is a blocking call
    waiter = Thread.new{ y t.pid => t.exitstatus }
    
    while((status = t.status))
      y "status" => status
      sleep 1
    end
    
    waiter.join
    y "stdout" => stdout
  6. Run background processes with background (bg)

    master

    The background method (aliased as bg) spawns a process in a separate thread so the parent can continue execution immediately. It returns a special thread object that allows you to query the process status.

    Returned Object Methods:

    • pid: Returns the PID of the spawned process.
    • spawn_status: Returns the Process::Status object.
    • exitstatus: Returns the exit status of the process.
    proc = Open4.background('sleep', '10')
    puts "Started process with PID: #{proc.pid}"
    # ... later
    puts "Exit status: #{proc.exitstatus}"
  7. Use Open4.bg for background processes

    master

    The bg (background) method is similar to spawn, but it automatically runs the process in a new Ruby thread. The returned thread object has dynamic methods added to it, including .pid and .exitstatus (which is a blocking call). You can also check the current state of the process using .status.

    require 'yaml'
    require 'open4'
    include Open4
    
    stdin = '42'
    stdout = ''
    stderr = ''
    
    # Returns a thread
    t = bg 'ruby -e"sleep 4; puts ARGF.read"', 0=>stdin, 1=>stdout, 2=>stderr
    
    # t.exitstatus is a blocking call
    waiter = Thread.new{ y t.pid => t.exitstatus }
    
    while((status = t.status))
      y "status" => status
      sleep 1
    end
    
    waiter.join
    p stdout
  8. Use Open4::popen4 for manual process management

    master

    The Open4::popen4 method opens a child process and provides handles for the process ID (pid), standard input (stdin), standard output (stdout), and standard error (stderr).

    In its basic form, you are responsible for waiting for the process and reading the IO handles.

    require "open4"
    
    pid, stdin, stdout, stderr = Open4::popen4 "sh"
    
    stdin.puts "echo 42.out"
    stdin.puts "echo 42.err 1>&2"
    stdin.close
    
    # You must manually wait for the process
    ignored, status = Process::waitpid2 pid
    
    puts "pid        : #{ pid }"
    puts "stdout     : #{ stdout.read.strip }"
    puts "stderr     : #{ stderr.read.strip }"
    puts "status     : #{ status.inspect }"
    puts "exitstatus : #{ status.exitstatus }"
    require "open4"
    
    pid, stdin, stdout, stderr = Open4::popen4 "sh"
    
    stdin.puts "echo 42.out"
    stdin.puts "echo 42.err 1>&2"
    stdin.close
    
    ignored, status = Process::waitpid2 pid
    
    puts "pid        : #{ pid }"
    puts "stdout     : #{ stdout.read.strip }"
    puts "stderr     : #{ stderr.read.strip }"
    puts "status     : #{ status.inspect }"
    puts "exitstatus : #{ status.exitstatus }"
  9. Spawn processes with popen4ext

    master

    Use popen4ext if you need fine-grained control over file descriptor closing. It behaves similarly to popen4 but includes a closefds boolean parameter. If true, it attempts to close all file descriptors in the child process except for the standard ones (0, 1, 2) and the pipes used for communication.

    Open4.popen4ext(true, 'my_command', 'arg1') do |pid, stdin, stdout, stderr|
      # ...
    end
  10. Run Ruby code in a child process with Open4.pfork4

    master

    The pfork4 method is similar to popen4, but instead of executing an external command, it runs a Ruby lambda or proc in a child process. If the child process raises an exception, it is marshaled and propagated to the parent process.

    require 'open4'
    
    echo = lambda do
      $stdout.write $stdin.read
      raise 'finish implementing me'
    end
    
    org_message = "hello, world!"
    got_message = nil
    exception   = nil
    
    begin
      Open4.pfork4(echo) do |cid, stdin, stdout, stderr|
        stdin.write org_message
        stdin.close
        got_message = stdout.read
      end
    rescue RuntimeError => e
      exception = e.to_s
    end
    
    puts "org_message: #{org_message}"
    puts "got_message: #{got_message}"
    puts "exception  : #{exception}"
  11. Run code in a forked child with pfork4

    master

    The pfork4 method forks a child process to execute a block of code. It is designed to make Kernel#exit calls within the block behave as if they were normal process terminations for the child, rather than exiting the parent.

    Behavior:

    • If the block calls exit, the child process terminates normally.
    • If the block uses exit!, it bypasses the rescue logic.
    • The child process is automatically waited for by the parent.
    Open4.pfork4 do
      puts "I am in a child process"
      exit 0
    end