class ProcessExecuter::Commands::SpawnWithTimeout

Spawns a subprocess, waits until it completes, and returns the result

Wraps ‘Process.spawn` to provide the core functionality for {ProcessExecuter.spawn_with_timeout}.

It accepts all [Process.spawn execution options](docs.ruby-lang.org/en/3.4/Process.html#module-Process-label-Execution+Options) plus the additional option ‘timeout_after`.

@api private

Attributes

command[R]

The command to be run in the subprocess @see Process.spawn @example

spawn.command #=> ['echo', 'hello']

@return [Array<String>]

elapsed_time[R]

The elapsed time in seconds that the command ran

@example

spawn.elapsed_time #=> 1.234

@return [Numeric]

options[R]

The options that were used to spawn the process @example

spawn.options #=> ProcessExecuter::Options::SpawnWithTimeoutOptions

@return [ProcessExecuter::Options::SpawnWithTimeoutOptions]

pid[R]

The process ID of the spawned subprocess

@example

spawn.pid #=> 12345

@return [Integer]

result[R]

The result of the completed subprocess

@example

spawn.result #=> ProcessExecuter::Result

@return [ProcessExecuter::Result]

status[R]

The status returned by Process.wait2

@example

spawn.status #=> #<Process::Status: pid 12345 exit 0>

@return [Process::Status]

timed_out[R]

Whether the process timed out

@example

spawn.timed_out? #=> true

@return [Boolean]

timed_out?[R]

Whether the process timed out

@example

spawn.timed_out? #=> true

@return [Boolean]

Public Class Methods

new(command, options) click to toggle source

Create a new SpawnWithTimeout instance

@example

options = ProcessExecuter::Options::SpawnWithTimeoutOptions.new(timeout_after: 5)
result = ProcessExecuter::Commands::SpawnWithTimeout.new('echo hello', options).call
result.success? # => true
result.exitstatus # => 0

@param command [Array<String>] The command to run in the subprocess @param options [ProcessExecuter::Options::SpawnWithTimeoutOptions] The options to use when spawning the process

# File lib/process_executer/commands/spawn_with_timeout.rb, line 30
def initialize(command, options)
  @command = command
  @options = options
end

Public Instance Methods

call() click to toggle source

Run a command and return the result

@example

options = ProcessExecuter::Options::SpawnWithTimeoutOptions.new(timeout_after: 5)
result = ProcessExecuter::Commands::SpawnWithTimeout.new('echo hello', options).call
result.success? # => true
result.exitstatus # => 0
result.timed_out? # => false

@raise [ProcessExecuter::SpawnError] ‘Process.spawn` raised an error before the

command was run

@return [ProcessExecuter::Result] The result of the completed subprocess

# File lib/process_executer/commands/spawn_with_timeout.rb, line 49
def call
  begin
    @pid = Process.spawn(*command, **options.spawn_options)
  rescue StandardError => e
    raise ProcessExecuter::SpawnError, "Failed to spawn process: #{e.message}"
  end

  wait_for_process
end

Private Instance Methods

create_result() click to toggle source

Create a result object that includes the status, command, and other details

@return [ProcessExecuter::Result] The result of the command

# File lib/process_executer/commands/spawn_with_timeout.rb, line 139
def create_result
  ProcessExecuter::Result.new(status, command:, options:, timed_out:, elapsed_time:)
end
wait_for_process() click to toggle source

Wait for process to terminate

If a ‘:timeout_after` is specified in options, terminate the process after the specified number of seconds.

@return [ProcessExecuter::Result] The result of the completed subprocess

# File lib/process_executer/commands/spawn_with_timeout.rb, line 128
def wait_for_process
  start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  @status, @timed_out = wait_for_process_raw
  @elapsed_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
  @result = create_result
end
wait_for_process_raw() click to toggle source

Wait for a process to terminate returning the status and timed out flag

@return [Array<Process::Status, Boolean>] an array containing the process status and a boolean

indicating whether the process timed out
# File lib/process_executer/commands/spawn_with_timeout.rb, line 147
def wait_for_process_raw
  timed_out = false

  process_status =
    begin
      Timeout.timeout(options.timeout_after) { Process.wait2(pid).last }
    rescue Timeout::Error
      Process.kill('KILL', pid)
      timed_out = true
      Process.wait2(pid).last
    end

  [process_status, timed_out]
end