crazy_ivan 0.3.3 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,401 @@
1
+ # vim: ts=2:sw=2:sts=2:et:fdm=marker
2
+ require 'fcntl'
3
+ require 'timeout'
4
+ require 'thread'
5
+
6
+ module Open4
7
+ #--{{{
8
+ VERSION = '1.0.1'
9
+ def self.version() VERSION end
10
+
11
+ class Error < ::StandardError; end
12
+
13
+ def popen4(*cmd, &b)
14
+ #--{{{
15
+ pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe
16
+
17
+ verbose = $VERBOSE
18
+ begin
19
+ $VERBOSE = nil
20
+ ps.first.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
21
+ ps.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
22
+
23
+ cid = fork {
24
+ pw.last.close
25
+ STDIN.reopen pw.first
26
+ pw.first.close
27
+
28
+ pr.first.close
29
+ STDOUT.reopen pr.last
30
+ pr.last.close
31
+
32
+ pe.first.close
33
+ STDERR.reopen pe.last
34
+ pe.last.close
35
+
36
+ STDOUT.sync = STDERR.sync = true
37
+
38
+ begin
39
+ exec(*cmd)
40
+ raise 'forty-two'
41
+ rescue Exception => e
42
+ Marshal.dump(e, ps.last)
43
+ ps.last.flush
44
+ end
45
+ ps.last.close unless (ps.last.closed?)
46
+ exit!
47
+ }
48
+ ensure
49
+ $VERBOSE = verbose
50
+ end
51
+
52
+ [pw.first, pr.last, pe.last, ps.last].each{|fd| fd.close}
53
+
54
+ begin
55
+ e = Marshal.load ps.first
56
+ raise(Exception === e ? e : "unknown failure!")
57
+ rescue EOFError # If we get an EOF error, then the exec was successful
58
+ 42
59
+ ensure
60
+ ps.first.close
61
+ end
62
+
63
+ pw.last.sync = true
64
+
65
+ pi = [pw.last, pr.first, pe.first]
66
+
67
+ if b
68
+ begin
69
+ b[cid, *pi]
70
+ Process.waitpid2(cid).last
71
+ ensure
72
+ pi.each{|fd| fd.close unless fd.closed?}
73
+ end
74
+ else
75
+ [cid, pw.last, pr.first, pe.first]
76
+ end
77
+ #--}}}
78
+ end
79
+ alias open4 popen4
80
+ module_function :popen4
81
+ module_function :open4
82
+
83
+ class SpawnError < Error
84
+ #--{{{
85
+ attr 'cmd'
86
+ attr 'status'
87
+ attr 'signals'
88
+ def exitstatus
89
+ @status.exitstatus
90
+ end
91
+ def initialize cmd, status
92
+ @cmd, @status = cmd, status
93
+ @signals = {}
94
+ if status.signaled?
95
+ @signals['termsig'] = status.termsig
96
+ @signals['stopsig'] = status.stopsig
97
+ end
98
+ sigs = @signals.map{|k,v| "#{ k }:#{ v.inspect }"}.join(' ')
99
+ super "cmd <#{ cmd }> failed with status <#{ exitstatus.inspect }> signals <#{ sigs }>"
100
+ end
101
+ #--}}}
102
+ end
103
+
104
+ class ThreadEnsemble
105
+ #--{{{
106
+ attr 'threads'
107
+
108
+ def initialize cid
109
+ @cid, @threads, @argv, @done, @running = cid, [], [], Queue.new, false
110
+ @killed = false
111
+ end
112
+
113
+ def add_thread *a, &b
114
+ @running ? raise : (@argv << [a, b])
115
+ end
116
+
117
+ #
118
+ # take down process more nicely
119
+ #
120
+ def killall
121
+ c = Thread.critical
122
+ return nil if @killed
123
+ Thread.critical = true
124
+ (@threads - [Thread.current]).each{|t| t.kill rescue nil}
125
+ @killed = true
126
+ ensure
127
+ Thread.critical = c
128
+ end
129
+
130
+ def run
131
+ @running = true
132
+
133
+ begin
134
+ @argv.each do |a, b|
135
+ @threads << Thread.new(*a) do |*a|
136
+ begin
137
+ b[*a]
138
+ ensure
139
+ killall rescue nil if $!
140
+ @done.push Thread.current
141
+ end
142
+ end
143
+ end
144
+ rescue
145
+ killall
146
+ raise
147
+ ensure
148
+ all_done
149
+ end
150
+
151
+ @threads.map{|t| t.value}
152
+ end
153
+
154
+ def all_done
155
+ @threads.size.times{ @done.pop }
156
+ end
157
+ #--}}}
158
+ end
159
+
160
+ def to timeout = nil
161
+ #--{{{
162
+ Timeout.timeout(timeout){ yield }
163
+ #--}}}
164
+ end
165
+ module_function :to
166
+
167
+ def new_thread *a, &b
168
+ #--{{{
169
+ cur = Thread.current
170
+ Thread.new(*a) do |*a|
171
+ begin
172
+ b[*a]
173
+ rescue Exception => e
174
+ cur.raise e
175
+ end
176
+ end
177
+ #--}}}
178
+ end
179
+ module_function :new_thread
180
+
181
+ def getopts opts = {}
182
+ #--{{{
183
+ lambda do |*args|
184
+ keys, default, ignored = args
185
+ catch('opt') do
186
+ [keys].flatten.each do |key|
187
+ [key, key.to_s, key.to_s.intern].each do |key|
188
+ throw 'opt', opts[key] if opts.has_key?(key)
189
+ end
190
+ end
191
+ default
192
+ end
193
+ end
194
+ #--}}}
195
+ end
196
+ module_function :getopts
197
+
198
+ def relay src, dst = nil, t = nil
199
+ #--{{{
200
+ send_dst =
201
+ if dst.respond_to?(:call)
202
+ lambda{|buf| dst.call(buf)}
203
+ else
204
+ lambda{|buf| dst << buf}
205
+ end
206
+
207
+ unless src.nil?
208
+ if src.respond_to? :gets
209
+ while buf = to(t){ src.gets }
210
+ send_dst[buf]
211
+ end
212
+
213
+ elsif src.respond_to? :each
214
+ q = Queue.new
215
+ th = nil
216
+
217
+ timer_set = lambda do |t|
218
+ th = new_thread{ to(t){ q.pop } }
219
+ end
220
+
221
+ timer_cancel = lambda do |t|
222
+ th.kill if th rescue nil
223
+ end
224
+
225
+ timer_set[t]
226
+ begin
227
+ src.each do |buf|
228
+ timer_cancel[t]
229
+ send_dst[buf]
230
+ timer_set[t]
231
+ end
232
+ ensure
233
+ timer_cancel[t]
234
+ end
235
+
236
+ elsif src.respond_to? :read
237
+ buf = to(t){ src.read }
238
+ send_dst[buf]
239
+
240
+ else
241
+ buf = to(t){ src.to_s }
242
+ send_dst[buf]
243
+ end
244
+ end
245
+ #--}}}
246
+ end
247
+ module_function :relay
248
+
249
+ def spawn arg, *argv
250
+ #--{{{
251
+ argv.unshift(arg)
252
+ opts = ((argv.size > 1 and Hash === argv.last) ? argv.pop : {})
253
+ argv.flatten!
254
+ cmd = argv.join(' ')
255
+
256
+
257
+ getopt = getopts opts
258
+
259
+ ignore_exit_failure = getopt[ 'ignore_exit_failure', getopt['quiet', false] ]
260
+ ignore_exec_failure = getopt[ 'ignore_exec_failure', !getopt['raise', true] ]
261
+ exitstatus = getopt[ %w( exitstatus exit_status status ) ]
262
+ stdin = getopt[ %w( stdin in i 0 ) << 0 ]
263
+ stdout = getopt[ %w( stdout out o 1 ) << 1 ]
264
+ stderr = getopt[ %w( stderr err e 2 ) << 2 ]
265
+ pid = getopt[ 'pid' ]
266
+ timeout = getopt[ %w( timeout spawn_timeout ) ]
267
+ stdin_timeout = getopt[ %w( stdin_timeout ) ]
268
+ stdout_timeout = getopt[ %w( stdout_timeout io_timeout ) ]
269
+ stderr_timeout = getopt[ %w( stderr_timeout ) ]
270
+ status = getopt[ %w( status ) ]
271
+ cwd = getopt[ %w( cwd dir ) ]
272
+
273
+ exitstatus =
274
+ case exitstatus
275
+ when TrueClass, FalseClass
276
+ ignore_exit_failure = true if exitstatus
277
+ [0]
278
+ else
279
+ [*(exitstatus || 0)].map{|i| Integer i}
280
+ end
281
+
282
+ stdin ||= '' if stdin_timeout
283
+ stdout ||= '' if stdout_timeout
284
+ stderr ||= '' if stderr_timeout
285
+
286
+ started = false
287
+
288
+ status =
289
+ begin
290
+ chdir(cwd) do
291
+ Timeout::timeout(timeout) do
292
+ popen4(*argv) do |c, i, o, e|
293
+ started = true
294
+
295
+ %w( replace pid= << push update ).each do |msg|
296
+ break(pid.send(msg, c)) if pid.respond_to? msg
297
+ end
298
+
299
+ te = ThreadEnsemble.new c
300
+
301
+ te.add_thread(i, stdin) do |i, stdin|
302
+ relay stdin, i, stdin_timeout
303
+ i.close rescue nil
304
+ end
305
+
306
+ te.add_thread(o, stdout) do |o, stdout|
307
+ relay o, stdout, stdout_timeout
308
+ end
309
+
310
+ te.add_thread(e, stderr) do |o, stderr|
311
+ relay e, stderr, stderr_timeout
312
+ end
313
+
314
+ te.run
315
+ end
316
+ end
317
+ end
318
+ rescue
319
+ raise unless(not started and ignore_exec_failure)
320
+ end
321
+
322
+ raise SpawnError.new(cmd, status) unless
323
+ (ignore_exit_failure or (status.nil? and ignore_exec_failure) or exitstatus.include?(status.exitstatus))
324
+
325
+ status
326
+ #--}}}
327
+ end
328
+ module_function :spawn
329
+
330
+ def chdir cwd, &block
331
+ return(block.call Dir.pwd) unless cwd
332
+ Dir.chdir cwd, &block
333
+ end
334
+ module_function :chdir
335
+
336
+ def background arg, *argv
337
+ #--{{{
338
+ require 'thread'
339
+ q = Queue.new
340
+ opts = { 'pid' => q, :pid => q }
341
+ case argv.last
342
+ when Hash
343
+ argv.last.update opts
344
+ else
345
+ argv.push opts
346
+ end
347
+ thread = Thread.new(arg, argv){|arg, argv| spawn arg, *argv}
348
+ sc = class << thread; self; end
349
+ sc.module_eval {
350
+ define_method(:pid){ @pid ||= q.pop }
351
+ define_method(:spawn_status){ @spawn_status ||= value }
352
+ define_method(:exitstatus){ @exitstatus ||= spawn_status.exitstatus }
353
+ }
354
+ thread
355
+ #--}}}
356
+ end
357
+ alias bg background
358
+ module_function :background
359
+ module_function :bg
360
+
361
+ def maim pid, opts = {}
362
+ #--{{{
363
+ getopt = getopts opts
364
+ sigs = getopt[ 'signals', %w(SIGTERM SIGQUIT SIGKILL) ]
365
+ suspend = getopt[ 'suspend', 4 ]
366
+ pid = Integer pid
367
+ existed = false
368
+ sigs.each do |sig|
369
+ begin
370
+ Process.kill sig, pid
371
+ existed = true
372
+ rescue Errno::ESRCH
373
+ return(existed ? nil : true)
374
+ end
375
+ return true unless alive? pid
376
+ sleep suspend
377
+ return true unless alive? pid
378
+ end
379
+ return(not alive?(pid))
380
+ #--}}}
381
+ end
382
+ module_function :maim
383
+
384
+ def alive pid
385
+ #--{{{
386
+ pid = Integer pid
387
+ begin
388
+ Process.kill 0, pid
389
+ true
390
+ rescue Errno::ESRCH
391
+ false
392
+ end
393
+ #--}}}
394
+ end
395
+ alias alive? alive
396
+ module_function :alive
397
+ module_function :'alive?'
398
+ #--}}}
399
+ end
400
+
401
+ def open4(*cmd, &b) cmd.size == 0 ? Open4 : Open4::popen4(*cmd, &b) end
@@ -0,0 +1,28 @@
1
+ ## open4.gemspec
2
+ #
3
+
4
+ Gem::Specification::new do |spec|
5
+ spec.name = "open4"
6
+ spec.version = "1.0.1"
7
+ spec.platform = Gem::Platform::RUBY
8
+ spec.summary = "open4"
9
+
10
+ spec.description = "manage child processes and their io handles easily"
11
+
12
+ spec.files = ["lib", "lib/open4.rb", "open4.gemspec", "Rakefile", "README", "README.erb", "samples", "samples/bg.rb", "samples/block.rb", "samples/exception.rb", "samples/simple.rb", "samples/spawn.rb", "samples/stdin_timeout.rb", "samples/timeout.rb", "white_box", "white_box/leak.rb"]
13
+ spec.executables = []
14
+
15
+ spec.require_path = "lib"
16
+
17
+ spec.has_rdoc = true
18
+ spec.test_files = nil
19
+ #spec.add_dependency 'lib', '>= version'
20
+ #spec.add_dependency 'fattr'
21
+
22
+ spec.extensions.push(*[])
23
+
24
+ spec.rubyforge_project = "codeforpeople"
25
+ spec.author = "Ara T. Howard"
26
+ spec.email = "ara.t.howard@gmail.com"
27
+ spec.homepage = "http://github.com/ahoward/open4/tree/master"
28
+ end
@@ -0,0 +1,21 @@
1
+ require 'yaml'
2
+ require 'open4'
3
+ include Open4
4
+
5
+ stdin = '42'
6
+ stdout = ''
7
+ stderr = ''
8
+
9
+ t = bg 'ruby -e"sleep 4; puts ARGF.read"', 0=>stdin, 1=>stdout, 2=>stderr
10
+
11
+ waiter = Thread.new{ y t.pid => t.exitstatus } # t.exitstatus is a blocking call!
12
+
13
+ while((status = t.status))
14
+ y "status" => status
15
+ sleep 1
16
+ end
17
+
18
+ waiter.join
19
+
20
+ y "stdout" => stdout
21
+
@@ -0,0 +1,19 @@
1
+ require 'open4'
2
+ #
3
+ # when using block form the child process is automatically waited using
4
+ # waitpid2
5
+ #
6
+
7
+ status =
8
+ Open4::popen4("sh") do |pid, stdin, stdout, stderr|
9
+ stdin.puts "echo 42.out"
10
+ stdin.puts "echo 42.err 1>&2"
11
+ stdin.close
12
+
13
+ puts "pid : #{ pid }"
14
+ puts "stdout : #{ stdout.read.strip }"
15
+ puts "stderr : #{ stderr.read.strip }"
16
+ end
17
+
18
+ puts "status : #{ status.inspect }"
19
+ puts "exitstatus : #{ status.exitstatus }"
@@ -0,0 +1,3 @@
1
+ require "open4"
2
+
3
+ Open4::popen4 "noexist"
@@ -0,0 +1,15 @@
1
+ require "open4"
2
+
3
+ pid, stdin, stdout, stderr = Open4::popen4 "sh"
4
+
5
+ stdin.puts "echo 42.out"
6
+ stdin.puts "echo 42.err 1>&2"
7
+ stdin.close
8
+
9
+ ignored, status = Process::waitpid2 pid
10
+
11
+ puts "pid : #{ pid }"
12
+ puts "stdout : #{ stdout.read.strip }"
13
+ puts "stderr : #{ stderr.read.strip }"
14
+ puts "status : #{ status.inspect }"
15
+ puts "exitstatus : #{ status.exitstatus }"
@@ -0,0 +1,16 @@
1
+ require 'open4'
2
+ include Open4
3
+
4
+ cat = 'ruby -e" ARGF.each{|line| STDOUT << line} "'
5
+
6
+ stdout, stderr = '', ''
7
+ status = spawn cat, 'stdin' => '42', 'stdout' => stdout, 'stderr' => stderr
8
+ p status
9
+ p stdout
10
+ p stderr
11
+
12
+ stdout, stderr = '', ''
13
+ status = spawn cat, 0=>'42', 1=>stdout, 2=>stderr
14
+ p status
15
+ p stdout
16
+ p stderr
@@ -0,0 +1,9 @@
1
+ require 'open4'
2
+
3
+ producer = 'ruby -e" STDOUT.sync = true; loop{sleep(rand+rand) and puts 42} " 2>/dev/null'
4
+
5
+ consumer = 'ruby -e" STDOUT.sync = true; STDIN.each{|line| puts line} "'
6
+
7
+ open4(producer) do |pid, i, o, e|
8
+ open4.spawn consumer, 0=>o, 1=>STDOUT, :stdin_timeout => 1.4
9
+ end
@@ -0,0 +1,37 @@
1
+
2
+ require 'open4'
3
+
4
+ def show_failure
5
+ fork{ yield }
6
+ Process.wait
7
+ puts
8
+ end
9
+
10
+ #
11
+ # command timeout
12
+ #
13
+ show_failure{
14
+ open4.spawn 'sleep 42', 'timeout' => 1
15
+ }
16
+
17
+ #
18
+ # stdin timeout
19
+ #
20
+ show_failure{
21
+
22
+ producer = 'ruby -e" STDOUT.sync = true; loop{sleep(rand+rand) and puts 42} " 2>/dev/null'
23
+
24
+ consumer = 'ruby -e" STDOUT.sync = true; STDIN.each{|line| puts line} "'
25
+
26
+ open4(producer) do |pid, i, o, e|
27
+ open4.spawn consumer, 0=>o, 1=>STDOUT, :stdin_timeout => 1.4
28
+ end
29
+ }
30
+
31
+ #
32
+ # stdout timeout (stderr is similar)
33
+ #
34
+
35
+ show_failure{
36
+ open4.spawn 'ruby -e" sleep 2 and puts 42 "', 'stdout_timeout' => 1
37
+ }
@@ -0,0 +1,17 @@
1
+ require 'open4'
2
+
3
+ pid = Process.pid
4
+ fds = lambda{|pid| Dir["/proc/#{ pid }/fd/*"]}
5
+
6
+ loop do
7
+ before = fds[pid]
8
+ Open4.popen4 'ruby -e"buf = STDIN.read; STDOUT.puts buf; STDERR.puts buf "' do |p,i,o,e|
9
+ i.puts 42
10
+ i.close_write
11
+ o.read
12
+ e.read
13
+ end
14
+ after = fds[pid]
15
+ p(after - before)
16
+ puts
17
+ end
@@ -0,0 +1,10 @@
1
+ # Prefer gems to the bundled libs.
2
+ require 'rubygems'
3
+
4
+ begin
5
+ gem 'open4', '>= 1.0.1'
6
+ rescue LoadError
7
+ $:.unshift "#{File.dirname(__FILE__)}/open4-1.0.1"
8
+ end
9
+
10
+ require 'open4'
@@ -1,8 +1,8 @@
1
1
  module CrazyIvan
2
2
  module VERSION #:nodoc:
3
- MAJOR = 3
4
- MINOR = 2
5
- TINY = 3
3
+ MAJOR = 0
4
+ MINOR = 4
5
+ TINY = 0
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end