Class: Cute::Execute

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

Constant Summary collapse

EXECDEBUG =
false
@@forkmutex =
Mutex.new

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*cmd) ⇒ Execute

Returns a new instance of Execute.



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/cute/execute.rb', line 9

def initialize(*cmd)
  @command = *cmd

  @exec_pid = nil

  @stdout = nil
  @stderr = nil
  @status = nil
  @run_thread = nil
  @killed = false

  @child_io = nil
  @parent_io = nil
  @lock = Mutex.new
  @emptypipes = false
end

Instance Attribute Details

#commandObject (readonly)

Returns the value of attribute command.



6
7
8
# File 'lib/cute/execute.rb', line 6

def command
  @command
end

#emptypipesObject (readonly)

Returns the value of attribute emptypipes.



6
7
8
# File 'lib/cute/execute.rb', line 6

def emptypipes
  @emptypipes
end

#exec_pidObject (readonly)

Returns the value of attribute exec_pid.



6
7
8
# File 'lib/cute/execute.rb', line 6

def exec_pid
  @exec_pid
end

#statusObject (readonly)

Returns the value of attribute status.



6
7
8
# File 'lib/cute/execute.rb', line 6

def status
  @status
end

#stderrObject (readonly)

Returns the value of attribute stderr.



6
7
8
# File 'lib/cute/execute.rb', line 6

def stderr
  @stderr
end

#stdoutObject (readonly)

Returns the value of attribute stdout.



6
7
8
# File 'lib/cute/execute.rb', line 6

def stdout
  @stdout
end

Class Method Details

.[](*cmd) ⇒ Object

Same as new function



34
35
36
# File 'lib/cute/execute.rb', line 34

def self.[](*cmd)
  self.new(*cmd)
end

.init_ios(opts = {:stdin => false}) ⇒ Object

Initialize the pipes and return one array for parent and one array for child.



39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/cute/execute.rb', line 39

def self.init_ios(opts={:stdin => false})
  if opts[:stdin]
    in_r, in_w = IO::pipe
    in_w.sync = true
  else
    in_r, in_w = [nil,nil]
  end

  out_r, out_w = opts[:stdout] == false ? [nil,nil] : IO::pipe
  err_r, err_w = opts[:stderr] == false ? [nil,nil] : IO::pipe

  [ [in_r,out_w,err_w], [in_w,out_r,err_r] ]
end

.kill_recursive(pid) ⇒ Object

kill a tree of processes. The killing is done in three steps: 1) STOP the target process 2) recursively kill all children 3) KILL the target process



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/cute/execute.rb', line 160

def self.kill_recursive(pid)
  puts "Killing PID #{pid} from PID #{$$}" if EXECDEBUG

  # SIGSTOPs the process to avoid it creating new children
  begin
    Process.kill('STOP',pid)
  rescue Errno::ESRCH # "no such process". The process was already killed, return.
    puts "got ESRCH on STOP" if EXECDEBUG
    return
  end
  # Gather the list of children before killing the parent in order to
  # be able to kill children that will be re-attached to init
  children = `ps --ppid #{pid} -o pid=`.split("\n").collect!{|p| p.strip.to_i}
  children.compact!
  puts "Children: #{children}" if EXECDEBUG
  # Check that the process still exists
  # Directly kill the process not to generate <defunct> children
  children.each do |cpid|
    kill_recursive(cpid)
  end if children

  begin
    Process.kill('KILL',pid)
  rescue Errno::ESRCH # "no such process". The process was already killed, return.
    puts "got ESRCH on KILL" if EXECDEBUG
    return
  end
end

Instance Method Details

#close_stdinObject

Close stdin of programme if it opened.



112
113
114
115
116
117
118
119
# File 'lib/cute/execute.rb', line 112

def close_stdin()
  @lock.synchronize do
    if @parent_io and @parent_io[0] and !@parent_io[0].closed?
      @parent_io[0].close
      @parent_io[0] = nil
    end
  end
end

#freeObject

Free the command, stdout stderr string.



27
28
29
30
31
# File 'lib/cute/execute.rb', line 27

def free
  @command = nil
  @stdout = nil
  @stderr = nil
end

#killObject

Kill the launched process.



190
191
192
# File 'lib/cute/execute.rb', line 190

def kill()
  @lock.synchronize{ kill! }
end

#run(opts = {:stdin => false}) ⇒ Object

Launch the command provided by the constructor

Parameters:

  • opts (Hash) (defaults to: {:stdin => false})

    run options

Options Hash (opts):

  • :stdin (Boolean)

    enable or disable pipe in stdin

  • :stdout (Boolean)

    enable or disable pipe in stdout

  • :stderr (Boolean)

    enable or disable pipe in stderr

  • :stdout_size (Fixnum)

    number to limit the number of byte read by execute stdout

  • :stderr_size (Fixnum)

    number to limit the number of byte read by execute stderr



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/cute/execute.rb', line 60

def run(opts={:stdin => false})
  @lock.synchronize do
    if @run_thread
      raise "Already launched"
    else
      begin
      ensure #We can't interrupt this process here before run was launched.
        @child_io, @parent_io = Execute.init_ios(opts)
        @@forkmutex.synchronize do
          @exec_pid = fork do
            run_fork()
          end
        end
        @run_thread = Thread.new do
          @child_io.each do |io|
            io.close if io and !io.closed?
          end
          @child_io = nil
          emptypipes = true

          @stdout,emptypipes = read_parent_io(1,opts[:stdout_size],emptypipes)
          @stderr,emptypipes = read_parent_io(2,opts[:stderr_size],emptypipes)

          _, @status = Process.wait2(@exec_pid)
          @exec_pid = nil

          @parent_io.each do |io|
            io.close if io and !io.closed?
          end

          @parent_io = nil
          @emptypipes = emptypipes
        end
      end
    end
  end
  [@exec_pid, *@parent_io]
end

#run!(opts = {:stdin => false}) ⇒ Object

Run the command and return the Execute object.

Parameters:

  • opts (Hash) (defaults to: {:stdin => false})


123
124
125
126
# File 'lib/cute/execute.rb', line 123

def run!(opts={:stdin => false})
  run(opts)
  self
end

#wait(opts = {:checkstatus => true}) ⇒ Array

Wait the end of process

Parameters:

  • opts (Hash) (defaults to: {:checkstatus => true})

    wait options

Options Hash (opts):

  • :checkstatus (Boolean)

    if it is true at end of process it raises an exception if the result is not null.

Returns:

  • (Array)

    Process::Status, stdout String, stderr String, emptypipe



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/cute/execute.rb', line 133

def wait(opts={:checkstatus => true})
  begin
    wkilled=true
    close_stdin()
    @run_thread.join
    wkilled=false
  ensure
    @lock.synchronize do
      if wkilled && !@killed
        kill!()
      end
    end
    @run_thread.join
    if !@killed
      # raise SignalException if the process was terminated by a signal and the kill function was not called.
      raise SignalException.new(@status.termsig) if @status and @status.signaled?
      raise "Command #{@command.inspect} exited with status #{@status.exitstatus}" if opts[:checkstatus] and !@status.success?
    end
  end
  [ @status, @stdout, @stderr, @emptypipes ]
end

#write_stdin(str) ⇒ Object

Write to stdin

Parameters:

  • str (String)

    string passed to process stdin.



101
102
103
104
105
106
107
108
109
# File 'lib/cute/execute.rb', line 101

def write_stdin(str)
  @lock.synchronize do
    if @parent_io and @parent_io[0] and !@parent_io[0].closed?
      @parent_io[0].write(str)
    else
      raise "Stdin is closed"
    end
  end
end