childprocess Ruby Gem

repository·master·Indexed 20 days ago

https://github.com/enkessler/childprocess

A Ruby library for simple and reliable control of external background processes across different operating systems, including Linux, macOS, Windows, Solaris, BSD, Cygwin, and AIX. It provides tools to configure environment variables, working directories, and IO streams, as well as methods to manage the process lifecycle via start, stop, wait, and poll_for_exit. Supports Ruby 2.4+ and JRuby 9+.

Tokens
3.1K
Snippets
13
Records
21
Agent score
69%

What's inside childprocess

  1. Ensure entire process tree dies

    master

    By default, a child process does not create a new process group. If the child spawns its own sub-processes, killing the child might leave those sub-processes running. To ensure the entire process tree is killed, set process.leader = true before starting.

    process = ChildProcess.build(*args)
    process.leader = true
    process.start
  2. Install and use childprocess

    master

    The childprocess gem provides a reliable way to control external programs running in the background across different Ruby and OS combinations. It is a standalone library originally derived from selenium-webdriver.

    Requirements

    • Ruby 2.4+
    • JRuby 9+
  3. How to invoke shell commands and executables

    master

    Unlike Kernel#system, ChildProcess does not automatically invoke a shell. If you attempt to run a command that requires a shell (like a .bat file on Windows or a gem executable that relies on shell path resolution), you may encounter a ChildProcess::LaunchError.

    To fix this, explicitly invoke the interpreter:

    • For Windows commands: ChildProcess.build("cmd.exe", "/c", "bundle")
    • For Ruby gems: ChildProcess.build("ruby", "-S", "bundle")
  4. Basic usage of ChildProcess.build

    master

    Use ChildProcess.build to create a process object that implements ChildProcess::AbstractProcess. You can configure the environment, working directory, and IO streams before starting the process.

    Common Configuration Tasks

    • IO Streams: Use process.io.inherit! to inherit stdout/stderr from the parent, or assign an IO object (like a Tempfile) to process.io.stdout or process.io.stderr.
    • Environment: Modify the child's environment using process.environment["KEY"] = "VALUE". Setting a key to nil removes it.
    • Working Directory: Set the child's current working directory using process.cwd = '/path'.
    • Process Control: Use .start to launch, .alive? to check if it's running, .exited? to check if it has finished, and .wait to block until exit.
    • Exit Codes: Retrieve the exit status via .exit_code after the process has exited.
    process = ChildProcess.build("ruby", "-e", "sleep")
    
    # Configuration
    process.io.inherit!
    process.environment["a"] = "b"
    process.cwd = '/some/path'
    
    # Execution
    process.start
    
    # Status and Exit
    process.alive?    #=> true
    process.wait
    process.exit_code #=> 0
    
    # Polling and Force Quit
    begin
      process.poll_for_exit(10)
    rescue ChildProcess::TimeoutError
      process.stop # Tries increasingly harsh methods to kill the process
    end
  5. Configure the global logger

    master

    By default, errors and debugging information are logged to $stderr. You can redirect this to a custom logger using ChildProcess.logger=.

    logger = Logger.new('logfile.log')
    logger.level = Logger::DEBUG
    ChildProcess.logger = logger
  6. Troubleshooting JRuby environment issues

    master

    PATH issues on JRuby (Unix)

    Modifying ENV["PATH"] before using childprocess may cause 'Command not found' errors because JRuby cannot modify the environment used by java.lang.ProcessBuilder. Solution: Set ChildProcess.posix_spawn = true.

    JVM Access issues (JRuby on Java >= 9)

    The JVM may require specific permissions to allow JRuby to access necessary implementations. Solution: Add the following to your JAVA_OPTS environment variable: --add-opens java.base/java.io=org.jruby.dist --add-opens java.base/sun.nio.ch=org.jruby.dist

  7. Write to a process's stdin

    master

    To send input to a running process, set process.duplex = true. This sets up a pipe so that process.io.stdin becomes available after the process has started.

    process = ChildProcess.build("cat")
    
    out      = Tempfile.new("duplex")
    out.sync = true
    
    process.io.stdout = process.io.stderr = out
    process.duplex    = true # enables process.io.stdin after .start
    
    process.start
    process.io.stdin.puts "hello world"
    process.io.stdin.close
    
    process.poll_for_exit(10)
    
    out.rewind
    out.read #=> "hello world\n"
  8. Capture output via pipes

    master

    To read the output of a command in real-time, you can assign an IO.pipe write end to the process's stdout.

    Important: You must close the parent's copy of the write end of the pipe in the parent process. If you don't, the parent will not detect EOF when the child process finishes, as the parent still holds an open write handle.

    r, w = IO.pipe
    
    begin
      process = ChildProcess.build("sh" , "-c",
                                   "for i in {1..3}; do echo $i; sleep 1; done")
      process.io.stdout = w
      process.start
    
      # Close parent's copy of the write end so parent receives EOF
      w.close
    
      thread = Thread.new do
        begin
          loop do
            print r.readpartial(16384)
          end
        rescue EOFError
          # Child has closed the write end of the pipe
        end
      end
    
      process.wait
      thread.join
    ensure
      r.close
    end
  9. Pipe output from one ChildProcess to another

    master

    You can chain processes together by assigning the stdout of one process to the stdin of another.

    search           = ChildProcess.build("grep", '-E', %w(redis memcached).join('|'))
    search.duplex    = true
    search.io.stdout = $stdout
    search.start
    
    listing           = ChildProcess.build("ps", "aux")
    listing.io.stdout = search.io.stdin
    listing.start
    listing.wait
    
    search.io.stdin.close
    search.wait
  10. Enable experimental posix_spawn

    master

    You can enable experimental use of posix_spawn by setting ChildProcess.posix_spawn = true. Alternatively, you can enable it via the environment variable CHILDPROCESS_POSIX_SPAWN by setting it to 1 or true.

    ChildProcess.posix_spawn = true
    # OR
    ENV['CHILDPROCESS_POSIX_SPAWN'] = 'true'
  11. Configure child process execution options

    master

    When using a ChildProcess object, you can configure several attributes to control how the process behaves in the operating system:

    • cwd: Set the current working directory for the child process.
    • environment: A hash of environment variables to be passed to the child process.
    • detach: Set to true if you do not care about when or if the process quits (the parent will not wait for it).
    • duplex: Set to true if you want to write to the process's stdin via process.io.stdin.
    • leader: Set to true to make the child process the leader of a new process group. This is useful for ensuring that all grandchildren are killed when the child process dies.