Class: BackgroundProcess

Inherits:
Object
  • Object
show all
Defined in:
lib/background_process/background_process.rb

Direct Known Subclasses

PTYBackgroundProcess

Defined Under Namespace

Modules: IOHelpers

Instance Attribute Summary (collapse)

Class Method Summary (collapse)

Instance Method Summary (collapse)

Constructor Details

- (BackgroundProcess) initialize(pid, stdin, stdout, stderr = nil)

Initialize a BackgroundProcess task. Don't do this. Use BackgroundProcess.run or BackgroundProcess.run_pty instead



5
6
7
8
# File 'lib/background_process/background_process.rb', line 5

def initialize(pid, stdin, stdout, stderr = nil)
  @pid, @stdin, @stdout, @stderr = pid, stdin, stdout, stderr
  ObjectSpace.define_finalizer(self) { kill }
end

Instance Attribute Details

- (Object) pid (readonly)

Returns the value of attribute pid



2
3
4
# File 'lib/background_process/background_process.rb', line 2

def pid
  @pid
end

- (Object) stderr (readonly)

Returns the value of attribute stderr



2
3
4
# File 'lib/background_process/background_process.rb', line 2

def stderr
  @stderr
end

- (Object) stdin (readonly)

Returns the value of attribute stdin



2
3
4
# File 'lib/background_process/background_process.rb', line 2

def stdin
  @stdin
end

- (Object) stdout (readonly)

Returns the value of attribute stdout



2
3
4
# File 'lib/background_process/background_process.rb', line 2

def stdout
  @stdout
end

Class Method Details

+ (Object) run(*command_with_args)

Run a command, connecting it's IO streams (stdin, sterr, stdout) via IO pipes, which are not tty IO streams.

Because of this, some programs (like ruby) will buffer their output and only make it available when it's explicitely flushed (with IO#flush or when the buffer gets full). This behavior can be overridden by setting the streams to sync, like this:

STDOUT.sync, STDERR.sync = true, true

If you can't control the program and have it explicitly flush its output when it should, or you can't tell the streams to run in sync mode, see PTYBackgroundProcess.run for a workaround.



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/background_process/background_process.rb', line 24

def self.run(*command_with_args)
  command = sanitize_command(command_with_args)
  child_stdin, parent_stdin = IO::pipe
  parent_stdout, child_stdout = IO::pipe
  parent_stderr, child_stderr = IO::pipe

  pid = Kernel.fork do
    [parent_stdin, parent_stdout, parent_stderr].each { |io| io.close }

    STDIN.reopen(child_stdin)
    STDOUT.reopen(child_stdout)
    STDERR.reopen(child_stderr)

    [child_stdin, child_stdout, child_stderr].each { |io| io.close }

    exec command
  end

  [child_stdin, child_stdout, child_stderr].each { |io| io.close }
  parent_stdin.sync = true

  new(pid, parent_stdin, parent_stdout, parent_stderr)
end

Instance Method Details

- (Object) detect(which = :both, timeout = nil, &block)

Calls block each time a line is available in the specified output buffer(s) and returns the first non-false value By default, both stdout and stderr are monitored.

Args:

  • which: which streams to monitor. valid values are :stdout, :stderr, or :both.

  • timeout: Total time in seconds to run detect for. If result not found within this time, abort and return nil. Pass nil for no timeout.

  • &block: the block to call. If block takes two arguments, it will pass both the stream that received the input (an instance of IO, not the symbol), and the line read from the buffer.



95
96
97
98
# File 'lib/background_process/background_process.rb', line 95

def detect(which = :both, timeout = nil, &block)
  streams = select_streams(which)
  BackgroundProcess::IOHelpers.detect(streams, timeout, &block)
end

- (Object) exitstatus

Waits for the process to terminate, and then returns the exit status



84
85
86
# File 'lib/background_process/background_process.rb', line 84

def exitstatus
  wait && wait.exitstatus
end

- (Object) interrupt

Sends the interrupt signal to the process. The equivalent of pressing control-C in it.



58
59
60
# File 'lib/background_process/background_process.rb', line 58

def interrupt
  kill('INT')
end

- (Object) kill(signal = 'TERM')

send a signal to the process. If the processes and running, do nothing. Valid signals are those in Signal.list. Default is “TERM”



50
51
52
53
54
55
# File 'lib/background_process/background_process.rb', line 50

def kill(signal = 'TERM')
  if running?
    Process.kill(Signal.list[signal], @pid)
    true
  end
end

- (Boolean) running?

asks the operating system is the process still exists.

Returns:

  • (Boolean)


63
64
65
66
67
68
69
# File 'lib/background_process/background_process.rb', line 63

def running?
  return false unless @pid
  Process.getpgid(@pid)
  true
rescue Errno::ESRCH
  false
end

- (Object) wait(timeout = nil)

waits for the process to finish. Freeze the process so it can rest in peace. You should call this on every background job you create to avoid a flood of zombie processes. (Processes won't go away until they are waited on)



74
75
76
77
78
79
80
81
# File 'lib/background_process/background_process.rb', line 74

def wait(timeout = nil)
  @exit_status ||= Timeout.timeout(timeout) do
    Process.wait(@pid)
    $?
  end
rescue Timeout::Error
  nil
end