Class: Execute

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

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(cmd, options = nil) ⇒ Execute

Returns a new instance of Execute.



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/execute.rb', line 14

def initialize(cmd, options=nil)
 initialize_defaults if(@@default_options.nil?)
 self[:output] = ''
 self[:error] = ''
 
 #1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL
 #5) SIGTRAP      6) SIGABRT      7) SIGBUS       8) SIGFPE
 #9) SIGKILL     10) SIGUSR1     11) SIGSEGV     12) SIGUSR2
 #13) SIGPIPE     14) SIGALRM     15) SIGTERM     17) SIGCHLD
 #18) SIGCONT     19) SIGSTOP     20) SIGTSTP     21) SIGTTIN
 #22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
 #26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO
 #30) SIGPWR      31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1
 #36) SIGRTMIN+2  37) SIGRTMIN+3  38) SIGRTMIN+4  39) SIGRTMIN+5
 #40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8  43) SIGRTMIN+9
 #44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
 #48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13
 #52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9
 #56) SIGRTMAX-8  57) SIGRTMAX-7  58) SIGRTMAX-6  59) SIGRTMAX-5
 #60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2  63) SIGRTMAX-1
 #64) SIGRTMAX
 self[:timeout_signal] = 2
 self[:timeout_raise_error] = true
 
 @@default_options.each { |key, value| self[key] = value}
 options.each { |key, value| self[key] = value} unless(options.nil?)
 self[:command]=cmd
end

Class Method Details

.default_options(hash) ⇒ Object



44
45
46
# File 'lib/execute.rb', line 44

def self.default_options(hash)
	hash.each { |key, value| @@default_options[key] = value}
end

Instance Method Details

#[](key) ⇒ Object



187
188
189
190
191
192
# File 'lib/execute.rb', line 187

def [](key)
  value = nil
  mutex = Mutex.new
	mutex.synchronize { value = super(key) }
	return value
end

#[]=(key, value) ⇒ Object



182
183
184
185
# File 'lib/execute.rb', line 182

def []=(key,value)
  mutex = Mutex.new
	mutex.synchronize { super(key, value) }
end

#call_captureObject



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/execute.rb', line 152

def call_capture
	begin
    if(key?(:timeout))
	    start_time = Time.now
		  Thread.new do
		    while !key?(:exit_code) do
		      sleep(0.1)			  
			    if((Time.now - start_time).to_f > self[:timeout])
			      self[:timed_out] = true
			      interrupt
			      sleep(0.1)
			    break
			    end
		    end
      end
	  end

		self[:output] = self[:error] = ''
	  self[:output], self[:error], status = Open3.capture3(self[:command])
	  self[:exit_code] = status.to_i	
	  
	  puts self[:output] if(self[:echo_output] && !self[:output].empty?)
	  
	  raise TimeoutException.new("Command '#{self[:command]}' timed out after #{self[:timeout]} seconds") if(key?(:timed_out) && self[:timeout_raise_error])
	rescue Exception => e
	  self[:error] = "#{self[:error]}\nException: #{e.to_s}"
	  self[:exit_code]=1 unless(!self[:exit_code].nil? || (self[:exit_code] == 0))
	end
end

#call_popenObject



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/execute.rb', line 76

def call_popen
	begin
    output = ''
	  error = ''  

	  threads = []
	  mutex = Mutex.new
	  stop_threads = false
	  timeout = nil
	  timeout = self[:timeout] if(key?(:timeout))
	  
	  Open3.popen3(self[:command]) do |stdin, stdout, stderr, wait_thr|
      self[:pid] = wait_thr.pid 
		
		  unless(timeout.nil?)
		  start_time = Time.now
		  threads << Thread.new do 
		    begin
		      while wait_thr.alive? do
			    if((Time.now - start_time).to_f > timeout)
				      self[:timed_out] = true 
				      self[:pre_timeout_command].execute if(self.key?(:pre_timeout_command))
				      interrupt
		          mutex.synchronize {stop_threads = true }
			    else
			      sleep(1)
			      Thread.pass
			    end
		        break if(stop_threads)
			  end
			rescue Exception
			  mutex.synchronize { stop_threads = true }
			end
		  end
    end
		
  	{:output => stdout,:error => stderr}.each do |key, stream|
      threads << Thread.new do		    
		  	begin
			  	last_pass_time = Time.now
		      while wait_thr.alive? do
			    	while !stream.closed? && 
				          !(char = stream.getc).nil? do
			        case key
			          when :output
			            output << char
					        putc char if(self[:echo_output])
			          when :error
			            error << char
			        end
				  
			        if(wait_thr.alive? && ((Time.now - last_pass_time).to_i > 15))
					      last_pass_time = Time.now
					      Thread.pass
				      end
			      end
				    break if(stop_threads)
				    sleep(0.1)
			    end
			    mutex.synchronize { stop_threads = true }		      
			  rescue Exception
			    mutex.synchronize { stop_threads = true }
	      end
		  end
		end

		threads.each { |thr| thr.join }		
	    self[:output] = output unless(output.empty?)			    
		  self[:error] = error unless(error.empty?)
		  self[:exit_code] = wait_thr.value.to_i		
	  end
	rescue Exception => e
	  self[:error] = "#{self[:error]}\nException: #{e.to_s}"
    self[:exit_code]=1 unless(self[:exit_code].nil? || (self[:exit_code] == 0))
	end
end

#command_pidObject



194
195
196
197
198
199
200
201
202
# File 'lib/execute.rb', line 194

def command_pid
	Sys::ProcTable.ps do |p| 
 if(p.ppid == $$) 
return self[:pid] = p.pid
 end
	end

	raise "Failed to find child process for command: '#{self[:command]}'"
end

#executeObject

Raises:



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/execute.rb', line 48

def execute
  if(self[:quiet])
 self[:echo_output] = false
 self[:echo_command] = false
	end
	
	puts self[:command] if(self[:echo_command] || self[:debug])
	self[:elapsed_time] = Benchmark.realtime { call_popen }
	#self[:elapsed_time] = Benchmark.realtime { call_capture }
	
	if(self[:debug])
 puts "command: #{self[:command]}"
 puts "output: #{self[:output]}"
 puts "error: #{self[:error]}"
 puts "exit_code: #{self[:exit_code]}"
	end
	
	raise TimeoutException.new("Command '#{self[:command]}' timed out after #{self[:timeout]} seconds") if(key?(:timed_out) && self[:timeout_raise_error])

	if((self[:exit_code] != 0) && !self[:ignore_exit_code])
 exception_text = "Command: '#{self[:command]}'"
 exception_text = "#{exception_text}\nExit code: #{self[:exit_code]}"
 exception_text = "#{exception_text}\nError: '#{self[:error]}'"
 exception_text = "#{exception_text}\nOutput: '#{self[:output]}'"
 raise StandardError.new(exception_text) 
	end
end

#get_child_processes(pid) ⇒ Object



214
215
216
217
218
219
220
221
222
223
# File 'lib/execute.rb', line 214

def get_child_processes(pid)
	processes = []
	Sys::ProcTable.ps do |p| 
 if(p.ppid == pid) 
get_child_processes(p.pid).each { |cp| processes << cp }
processes << p
 end
	end
	return processes
end

#interruptObject



203
204
205
206
207
208
209
210
211
212
# File 'lib/execute.rb', line 203

def interrupt
	self[:pid] = command_pid unless(key?(:pid))
  raise "Do not have a process id for #{self[:pid]}" unless(key?(:pid))
  processes = get_child_processes(self[:pid])
	
	Process.kill(self[:timeout_signal], self[:pid])
	Process.waitpid(pid: self[:pid]) unless(Sys::ProcTable.ps(pid: self[:pid]).nil?) 
	
	processes.each { |p| Process.kill(self[:timeout_signal],p.pid) unless(Sys::ProcTable.ps(pid: p.pid).nil?) }
end