Class: Roast::Helpers::TimeoutHandler

Inherits:
Object
  • Object
show all
Defined in:
lib/roast/helpers/timeout_handler.rb

Overview

Shared timeout handling logic for command-based tools

This class provides centralized timeout functionality for executing shell commands with proper process management and resource cleanup.

Examples:

Basic usage

output, status = TimeoutHandler.call("echo hello", timeout: 5)

With custom working directory

output, status = TimeoutHandler.call("pwd", timeout: 10, working_directory: "/tmp")

Constant Summary collapse

DEFAULT_TIMEOUT =
30
MAX_TIMEOUT =
300

Class Method Summary collapse

Class Method Details

.call(command, timeout: DEFAULT_TIMEOUT, working_directory: Dir.pwd) ⇒ Array<String, Integer>

Execute a command with timeout using Open3 with proper process cleanup

Parameters:

  • command (String)

    The command to execute

  • timeout (Integer) (defaults to: DEFAULT_TIMEOUT)

    Timeout in seconds

  • working_directory (String) (defaults to: Dir.pwd)

    Directory to execute in (default: Dir.pwd)

Returns:

  • (Array<String, Integer>)
    output, exit_status

Raises:

  • (Timeout::Error)

    When command exceeds timeout duration



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/roast/helpers/timeout_handler.rb', line 29

def call(command, timeout: DEFAULT_TIMEOUT, working_directory: Dir.pwd)
  timeout = validate_timeout(timeout)
  output = ""
  exit_status = nil
  wait_thr = nil

  begin
    Timeout.timeout(timeout) do
      stdin, stdout, stderr, wait_thr = Open3.popen3(command, chdir: working_directory)
      stdin.close # Prevent hanging on stdin-waiting commands
      output = stdout.read + stderr.read
      wait_thr.join
      exit_status = wait_thr.value.exitstatus

      [stdout, stderr].each(&:close)
    end
  rescue Timeout::Error
    # Clean up any remaining processes to prevent zombies
    cleanup_process(wait_thr) if wait_thr&.alive?
    raise Timeout::Error, "Command '#{command}' in '#{working_directory}' timed out after #{timeout} seconds"
  end

  [output, exit_status]
end

.validate_timeout(timeout) ⇒ Integer

Validate and normalize timeout value

Parameters:

  • timeout (Integer, nil)

    Raw timeout value

Returns:

  • (Integer)

    Validated timeout between 1 and MAX_TIMEOUT



57
58
59
60
61
# File 'lib/roast/helpers/timeout_handler.rb', line 57

def validate_timeout(timeout)
  return DEFAULT_TIMEOUT if timeout.nil? || timeout <= 0

  [timeout, MAX_TIMEOUT].min
end