alib 0.4.0 → 0.5.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,379 @@
1
+ require 'fcntl'
2
+ require 'timeout'
3
+ require 'thread'
4
+
5
+ module Open4
6
+ #--{{{
7
+ VERSION = '0.9.1'
8
+ def self.version() VERSION end
9
+
10
+ class Error < ::StandardError; end
11
+
12
+ def popen4(*cmd, &b)
13
+ #--{{{
14
+ pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe
15
+
16
+ verbose = $VERBOSE
17
+ begin
18
+ $VERBOSE = nil
19
+ ps.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
20
+
21
+ cid = fork {
22
+ pw.last.close
23
+ STDIN.reopen pw.first
24
+ pw.first.close
25
+
26
+ pr.first.close
27
+ STDOUT.reopen pr.last
28
+ pr.last.close
29
+
30
+ pe.first.close
31
+ STDERR.reopen pe.last
32
+ pe.last.close
33
+
34
+ STDOUT.sync = STDERR.sync = true
35
+
36
+ begin
37
+ exec(*cmd)
38
+ raise 'forty-two'
39
+ rescue Exception => e
40
+ Marshal.dump(e, ps.last)
41
+ ps.last.flush
42
+ end
43
+ ps.last.close unless (ps.last.closed?)
44
+ exit!
45
+ }
46
+ ensure
47
+ $VERBOSE = verbose
48
+ end
49
+
50
+ [pw.first, pr.last, pe.last, ps.last].each{|fd| fd.close}
51
+
52
+ begin
53
+ e = Marshal.load ps.first
54
+ raise(Exception === e ? e : "unknown failure!")
55
+ rescue EOFError # If we get an EOF error, then the exec was successful
56
+ 42
57
+ end
58
+
59
+ pw.last.sync = true
60
+
61
+ pi = [pw.last, pr.first, pe.first]
62
+
63
+ if b
64
+ begin
65
+ b[cid, *pi]
66
+ Process.waitpid2(cid).last
67
+ ensure
68
+ pi.each{|fd| fd.close unless fd.closed?}
69
+ end
70
+ else
71
+ [cid, pw.last, pr.first, pe.first]
72
+ end
73
+ #--}}}
74
+ end
75
+ alias open4 popen4
76
+ module_function :popen4
77
+ module_function :open4
78
+
79
+ class SpawnError < Error
80
+ #--{{{
81
+ attr 'cmd'
82
+ attr 'status'
83
+ attr 'signals'
84
+ def exitstatus
85
+ @status.exitstatus
86
+ end
87
+ def initialize cmd, status
88
+ @cmd, @status = cmd, status
89
+ @signals = {}
90
+ if status.signaled?
91
+ @signals['termsig'] = status.termsig
92
+ @signals['stopsig'] = status.stopsig
93
+ end
94
+ sigs = @signals.map{|k,v| "#{ k }:#{ v.inspect }"}.join(' ')
95
+ super "cmd <#{ cmd }> failed with status <#{ exitstatus.inspect }> signals <#{ sigs }>"
96
+ end
97
+ #--}}}
98
+ end
99
+
100
+ class ThreadEnsemble
101
+ #--{{{
102
+ attr 'threads'
103
+
104
+ def initialize cid
105
+ @cid, @threads, @argv, @done, @running = cid, [], [], Queue.new, false
106
+ @killed = false
107
+ end
108
+
109
+ def add_thread *a, &b
110
+ @running ? raise : (@argv << [a, b])
111
+ end
112
+
113
+ #
114
+ # take down process more nicely
115
+ #
116
+ def killall
117
+ c = Thread.critical
118
+ return nil if @killed
119
+ Thread.critical = true
120
+ (@threads - [Thread.current]).each{|t| t.kill rescue nil}
121
+ @killed = true
122
+ ensure
123
+ Thread.critical = c
124
+ end
125
+
126
+ def run
127
+ @running = true
128
+
129
+ begin
130
+ @argv.each do |a, b|
131
+ @threads << Thread.new(*a) do |*a|
132
+ begin
133
+ b[*a]
134
+ ensure
135
+ killall rescue nil if $!
136
+ @done.push Thread.current
137
+ end
138
+ end
139
+ end
140
+ rescue
141
+ killall
142
+ raise
143
+ ensure
144
+ all_done
145
+ end
146
+
147
+ @threads.map{|t| t.value}
148
+ end
149
+
150
+ def all_done
151
+ @threads.size.times{ @done.pop }
152
+ end
153
+ #--}}}
154
+ end
155
+
156
+ def to timeout = nil
157
+ #--{{{
158
+ Timeout.timeout(timeout){ yield }
159
+ #--}}}
160
+ end
161
+ module_function :to
162
+
163
+ def new_thread *a, &b
164
+ #--{{{
165
+ cur = Thread.current
166
+ Thread.new(*a) do |*a|
167
+ begin
168
+ b[*a]
169
+ rescue Exception => e
170
+ cur.raise e
171
+ end
172
+ end
173
+ #--}}}
174
+ end
175
+ module_function :new_thread
176
+
177
+ def getopts opts = {}
178
+ #--{{{
179
+ lambda do |*args|
180
+ keys, default, ignored = args
181
+ catch('opt') do
182
+ [keys].flatten.each do |key|
183
+ [key, key.to_s, key.to_s.intern].each do |key|
184
+ throw 'opt', opts[key] if opts.has_key?(key)
185
+ end
186
+ end
187
+ default
188
+ end
189
+ end
190
+ #--}}}
191
+ end
192
+ module_function :getopts
193
+
194
+ def relay src, dst = nil, t = nil
195
+ #--{{{
196
+ unless src.nil?
197
+ if src.respond_to? :gets
198
+ while buf = to(t){ src.gets }
199
+ dst << buf if dst
200
+ end
201
+
202
+ elsif src.respond_to? :each
203
+ q = Queue.new
204
+ th = nil
205
+
206
+ timer_set = lambda do |t|
207
+ th = new_thread{ to(t){ q.pop } }
208
+ end
209
+
210
+ timer_cancel = lambda do |t|
211
+ th.kill if th rescue nil
212
+ end
213
+
214
+ timer_set[t]
215
+ begin
216
+ src.each do |buf|
217
+ timer_cancel[t]
218
+ dst << buf if dst
219
+ timer_set[t]
220
+ end
221
+ ensure
222
+ timer_cancel[t]
223
+ end
224
+
225
+ elsif src.respond_to? :read
226
+ buf = to(t){ src.read }
227
+ dst << buf if dst
228
+
229
+ else
230
+ buf = to(t){ src.to_s }
231
+ dst << buf if dst
232
+ end
233
+ end
234
+ #--}}}
235
+ end
236
+ module_function :relay
237
+
238
+ def spawn arg, *argv
239
+ #--{{{
240
+ argv.unshift(arg)
241
+ opts = ((argv.size > 1 and Hash === argv.last) ? argv.pop : {})
242
+ argv.flatten!
243
+ cmd = argv.join(' ')
244
+
245
+
246
+ getopt = getopts opts
247
+
248
+ ignore_exit_failure = getopt[ 'ignore_exit_failure', getopt['quiet', false] ]
249
+ ignore_exec_failure = getopt[ 'ignore_exec_failure', !getopt['raise', true] ]
250
+ exitstatus = getopt[ %w( exitstatus exit_status status ) ]
251
+ stdin = getopt[ %w( stdin in i 0 ) << 0 ]
252
+ stdout = getopt[ %w( stdout out o 1 ) << 1 ]
253
+ stderr = getopt[ %w( stderr err e 2 ) << 2 ]
254
+ pid = getopt[ 'pid' ]
255
+ timeout = getopt[ %w( timeout spawn_timeout ) ]
256
+ stdin_timeout = getopt[ %w( stdin_timeout ) ]
257
+ stdout_timeout = getopt[ %w( stdout_timeout io_timeout ) ]
258
+ stderr_timeout = getopt[ %w( stderr_timeout ) ]
259
+ status = getopt[ %w( status ) ]
260
+ cwd = getopt[ %w( cwd dir ), Dir.pwd ]
261
+
262
+ exitstatus =
263
+ case exitstatus
264
+ when TrueClass, FalseClass
265
+ ignore_exit_failure = true if exitstatus
266
+ [0]
267
+ else
268
+ [*(exitstatus || 0)].map{|i| Integer i}
269
+ end
270
+
271
+ stdin ||= '' if stdin_timeout
272
+ stdout ||= '' if stdout_timeout
273
+ stderr ||= '' if stderr_timeout
274
+
275
+ started = false
276
+
277
+ status =
278
+ begin
279
+ Dir.chdir(cwd) do
280
+ Timeout::timeout(timeout) do
281
+ popen4(*argv) do |c, i, o, e|
282
+ started = true
283
+
284
+ %w( replace pid= << push update ).each do |msg|
285
+ break(pid.send(msg, c)) if pid.respond_to? msg
286
+ end
287
+
288
+ te = ThreadEnsemble.new c
289
+
290
+ te.add_thread(i, stdin) do |i, stdin|
291
+ relay stdin, i, stdin_timeout
292
+ i.close rescue nil
293
+ end
294
+
295
+ te.add_thread(o, stdout) do |o, stdout|
296
+ relay o, stdout, stdout_timeout
297
+ end
298
+
299
+ te.add_thread(e, stderr) do |o, stderr|
300
+ relay e, stderr, stderr_timeout
301
+ end
302
+
303
+ te.run
304
+ end
305
+ end
306
+ end
307
+ rescue
308
+ raise unless(not started and ignore_exec_failure)
309
+ end
310
+
311
+ raise SpawnError.new(cmd, status) unless
312
+ (ignore_exit_failure or (status.nil? and ignore_exec_failure) or exitstatus.include?(status.exitstatus))
313
+
314
+ status
315
+ #--}}}
316
+ end
317
+ module_function :spawn
318
+
319
+ def background arg, *argv
320
+ #--{{{
321
+ require 'thread'
322
+ q = Queue.new
323
+ opts['pid'] = opts[:pid] = q
324
+ thread = Thread.new(arg, argv){|arg, argv| spawn arg, *argv}
325
+ pid = q.pop
326
+ sc = class << thread; self; end
327
+ sc.module_eval {
328
+ define_method(:pid){ pid }
329
+ define_method(:spawn_status){ @spawn_status ||= value }
330
+ define_method(:exitstatus){ spawn_status.exitstatus }
331
+ }
332
+ thread
333
+ #--}}}
334
+ end
335
+ alias bg background
336
+ module_function :background
337
+ module_function :bg
338
+
339
+ def maim pid, opts = {}
340
+ #--{{{
341
+ getopt = getopts opts
342
+ sigs = getopt[ 'signals', %w(SIGTERM SIGQUIT SIGKILL) ]
343
+ suspend = getopt[ 'suspend', 4 ]
344
+ pid = Integer pid
345
+ existed = false
346
+ sigs.each do |sig|
347
+ begin
348
+ Process.kill sig, pid
349
+ existed = true
350
+ rescue Errno::ESRCH
351
+ return(existed ? nil : true)
352
+ end
353
+ return true unless alive? pid
354
+ sleep suspend
355
+ return true unless alive? pid
356
+ end
357
+ return(not alive?(pid))
358
+ #--}}}
359
+ end
360
+ module_function :maim
361
+
362
+ def alive pid
363
+ #--{{{
364
+ pid = Integer pid
365
+ begin
366
+ Process.kill 0, pid
367
+ true
368
+ rescue Errno::ESRCH
369
+ false
370
+ end
371
+ #--}}}
372
+ end
373
+ alias alive? alive
374
+ module_function :alive
375
+ module_function :'alive?'
376
+ #--}}}
377
+ end
378
+
379
+ def open4(*cmd, &b) cmd.size == 0 ? Open4 : Open4::popen4(*cmd, &b) end
@@ -0,0 +1,379 @@
1
+ require 'fcntl'
2
+ require 'timeout'
3
+ require 'thread'
4
+
5
+ module Open4
6
+ #--{{{
7
+ VERSION = '0.9.1'
8
+ def self.version() VERSION end
9
+
10
+ class Error < ::StandardError; end
11
+
12
+ def popen4(*cmd, &b)
13
+ #--{{{
14
+ pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe
15
+
16
+ verbose = $VERBOSE
17
+ begin
18
+ $VERBOSE = nil
19
+ ps.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
20
+
21
+ cid = fork {
22
+ pw.last.close
23
+ STDIN.reopen pw.first
24
+ pw.first.close
25
+
26
+ pr.first.close
27
+ STDOUT.reopen pr.last
28
+ pr.last.close
29
+
30
+ pe.first.close
31
+ STDERR.reopen pe.last
32
+ pe.last.close
33
+
34
+ STDOUT.sync = STDERR.sync = true
35
+
36
+ begin
37
+ exec(*cmd)
38
+ raise 'forty-two'
39
+ rescue Exception => e
40
+ Marshal.dump(e, ps.last)
41
+ ps.last.flush
42
+ end
43
+ ps.last.close unless (ps.last.closed?)
44
+ exit!
45
+ }
46
+ ensure
47
+ $VERBOSE = verbose
48
+ end
49
+
50
+ [pw.first, pr.last, pe.last, ps.last].each{|fd| fd.close}
51
+
52
+ begin
53
+ e = Marshal.load ps.first
54
+ raise(Exception === e ? e : "unknown failure!")
55
+ rescue EOFError # If we get an EOF error, then the exec was successful
56
+ 42
57
+ end
58
+
59
+ pw.last.sync = true
60
+
61
+ pi = [pw.last, pr.first, pe.first]
62
+
63
+ if b
64
+ begin
65
+ b[cid, *pi]
66
+ Process.waitpid2(cid).last
67
+ ensure
68
+ pi.each{|fd| fd.close unless fd.closed?}
69
+ end
70
+ else
71
+ [cid, pw.last, pr.first, pe.first]
72
+ end
73
+ #--}}}
74
+ end
75
+ alias open4 popen4
76
+ module_function :popen4
77
+ module_function :open4
78
+
79
+ class SpawnError < Error
80
+ #--{{{
81
+ attr 'cmd'
82
+ attr 'status'
83
+ attr 'signals'
84
+ def exitstatus
85
+ @status.exitstatus
86
+ end
87
+ def initialize cmd, status
88
+ @cmd, @status = cmd, status
89
+ @signals = {}
90
+ if status.signaled?
91
+ @signals['termsig'] = status.termsig
92
+ @signals['stopsig'] = status.stopsig
93
+ end
94
+ sigs = @signals.map{|k,v| "#{ k }:#{ v.inspect }"}.join(' ')
95
+ super "cmd <#{ cmd }> failed with status <#{ exitstatus.inspect }> signals <#{ sigs }>"
96
+ end
97
+ #--}}}
98
+ end
99
+
100
+ class ThreadEnsemble
101
+ #--{{{
102
+ attr 'threads'
103
+
104
+ def initialize cid
105
+ @cid, @threads, @argv, @done, @running = cid, [], [], Queue.new, false
106
+ @killed = false
107
+ end
108
+
109
+ def add_thread *a, &b
110
+ @running ? raise : (@argv << [a, b])
111
+ end
112
+
113
+ #
114
+ # take down process more nicely
115
+ #
116
+ def killall
117
+ c = Thread.critical
118
+ return nil if @killed
119
+ Thread.critical = true
120
+ (@threads - [Thread.current]).each{|t| t.kill rescue nil}
121
+ @killed = true
122
+ ensure
123
+ Thread.critical = c
124
+ end
125
+
126
+ def run
127
+ @running = true
128
+
129
+ begin
130
+ @argv.each do |a, b|
131
+ @threads << Thread.new(*a) do |*a|
132
+ begin
133
+ b[*a]
134
+ ensure
135
+ killall rescue nil if $!
136
+ @done.push Thread.current
137
+ end
138
+ end
139
+ end
140
+ rescue
141
+ killall
142
+ raise
143
+ ensure
144
+ all_done
145
+ end
146
+
147
+ @threads.map{|t| t.value}
148
+ end
149
+
150
+ def all_done
151
+ @threads.size.times{ @done.pop }
152
+ end
153
+ #--}}}
154
+ end
155
+
156
+ def to timeout = nil
157
+ #--{{{
158
+ Timeout.timeout(timeout){ yield }
159
+ #--}}}
160
+ end
161
+ module_function :to
162
+
163
+ def new_thread *a, &b
164
+ #--{{{
165
+ cur = Thread.current
166
+ Thread.new(*a) do |*a|
167
+ begin
168
+ b[*a]
169
+ rescue Exception => e
170
+ cur.raise e
171
+ end
172
+ end
173
+ #--}}}
174
+ end
175
+ module_function :new_thread
176
+
177
+ def getopts opts = {}
178
+ #--{{{
179
+ lambda do |*args|
180
+ keys, default, ignored = args
181
+ catch('opt') do
182
+ [keys].flatten.each do |key|
183
+ [key, key.to_s, key.to_s.intern].each do |key|
184
+ throw 'opt', opts[key] if opts.has_key?(key)
185
+ end
186
+ end
187
+ default
188
+ end
189
+ end
190
+ #--}}}
191
+ end
192
+ module_function :getopts
193
+
194
+ def relay src, dst = nil, t = nil
195
+ #--{{{
196
+ unless src.nil?
197
+ if src.respond_to? :gets
198
+ while buf = to(t){ src.gets }
199
+ dst << buf if dst
200
+ end
201
+
202
+ elsif src.respond_to? :each
203
+ q = Queue.new
204
+ th = nil
205
+
206
+ timer_set = lambda do |t|
207
+ th = new_thread{ to(t){ q.pop } }
208
+ end
209
+
210
+ timer_cancel = lambda do |t|
211
+ th.kill if th rescue nil
212
+ end
213
+
214
+ timer_set[t]
215
+ begin
216
+ src.each do |buf|
217
+ timer_cancel[t]
218
+ dst << buf if dst
219
+ timer_set[t]
220
+ end
221
+ ensure
222
+ timer_cancel[t]
223
+ end
224
+
225
+ elsif src.respond_to? :read
226
+ buf = to(t){ src.read }
227
+ dst << buf if dst
228
+
229
+ else
230
+ buf = to(t){ src.to_s }
231
+ dst << buf if dst
232
+ end
233
+ end
234
+ #--}}}
235
+ end
236
+ module_function :relay
237
+
238
+ def spawn arg, *argv
239
+ #--{{{
240
+ argv.unshift(arg)
241
+ opts = ((argv.size > 1 and Hash === argv.last) ? argv.pop : {})
242
+ argv.flatten!
243
+ cmd = argv.join(' ')
244
+
245
+
246
+ getopt = getopts opts
247
+
248
+ ignore_exit_failure = getopt[ 'ignore_exit_failure', getopt['quiet', false] ]
249
+ ignore_exec_failure = getopt[ 'ignore_exec_failure', !getopt['raise', true] ]
250
+ exitstatus = getopt[ %w( exitstatus exit_status status ) ]
251
+ stdin = getopt[ %w( stdin in i 0 ) << 0 ]
252
+ stdout = getopt[ %w( stdout out o 1 ) << 1 ]
253
+ stderr = getopt[ %w( stderr err e 2 ) << 2 ]
254
+ pid = getopt[ 'pid' ]
255
+ timeout = getopt[ %w( timeout spawn_timeout ) ]
256
+ stdin_timeout = getopt[ %w( stdin_timeout ) ]
257
+ stdout_timeout = getopt[ %w( stdout_timeout io_timeout ) ]
258
+ stderr_timeout = getopt[ %w( stderr_timeout ) ]
259
+ status = getopt[ %w( status ) ]
260
+ cwd = getopt[ %w( cwd dir ), Dir.pwd ]
261
+
262
+ exitstatus =
263
+ case exitstatus
264
+ when TrueClass, FalseClass
265
+ ignore_exit_failure = true if exitstatus
266
+ [0]
267
+ else
268
+ [*(exitstatus || 0)].map{|i| Integer i}
269
+ end
270
+
271
+ stdin ||= '' if stdin_timeout
272
+ stdout ||= '' if stdout_timeout
273
+ stderr ||= '' if stderr_timeout
274
+
275
+ started = false
276
+
277
+ status =
278
+ begin
279
+ Dir.chdir(cwd) do
280
+ Timeout::timeout(timeout) do
281
+ popen4(*argv) do |c, i, o, e|
282
+ started = true
283
+
284
+ %w( replace pid= << push update ).each do |msg|
285
+ break(pid.send(msg, c)) if pid.respond_to? msg
286
+ end
287
+
288
+ te = ThreadEnsemble.new c
289
+
290
+ te.add_thread(i, stdin) do |i, stdin|
291
+ relay stdin, i, stdin_timeout
292
+ i.close rescue nil
293
+ end
294
+
295
+ te.add_thread(o, stdout) do |o, stdout|
296
+ relay o, stdout, stdout_timeout
297
+ end
298
+
299
+ te.add_thread(e, stderr) do |o, stderr|
300
+ relay e, stderr, stderr_timeout
301
+ end
302
+
303
+ te.run
304
+ end
305
+ end
306
+ end
307
+ rescue
308
+ raise unless(not started and ignore_exec_failure)
309
+ end
310
+
311
+ raise SpawnError.new(cmd, status) unless
312
+ (ignore_exit_failure or (status.nil? and ignore_exec_failure) or exitstatus.include?(status.exitstatus))
313
+
314
+ status
315
+ #--}}}
316
+ end
317
+ module_function :spawn
318
+
319
+ def background arg, *argv
320
+ #--{{{
321
+ require 'thread'
322
+ q = Queue.new
323
+ opts['pid'] = opts[:pid] = q
324
+ thread = Thread.new(arg, argv){|arg, argv| spawn arg, *argv}
325
+ pid = q.pop
326
+ sc = class << thread; self; end
327
+ sc.module_eval {
328
+ define_method(:pid){ pid }
329
+ define_method(:spawn_status){ @spawn_status ||= value }
330
+ define_method(:exitstatus){ spawn_status.exitstatus }
331
+ }
332
+ thread
333
+ #--}}}
334
+ end
335
+ alias bg background
336
+ module_function :background
337
+ module_function :bg
338
+
339
+ def maim pid, opts = {}
340
+ #--{{{
341
+ getopt = getopts opts
342
+ sigs = getopt[ 'signals', %w(SIGTERM SIGQUIT SIGKILL) ]
343
+ suspend = getopt[ 'suspend', 4 ]
344
+ pid = Integer pid
345
+ existed = false
346
+ sigs.each do |sig|
347
+ begin
348
+ Process.kill sig, pid
349
+ existed = true
350
+ rescue Errno::ESRCH
351
+ return(existed ? nil : true)
352
+ end
353
+ return true unless alive? pid
354
+ sleep suspend
355
+ return true unless alive? pid
356
+ end
357
+ return(not alive?(pid))
358
+ #--}}}
359
+ end
360
+ module_function :maim
361
+
362
+ def alive pid
363
+ #--{{{
364
+ pid = Integer pid
365
+ begin
366
+ Process.kill 0, pid
367
+ true
368
+ rescue Errno::ESRCH
369
+ false
370
+ end
371
+ #--}}}
372
+ end
373
+ alias alive? alive
374
+ module_function :alive
375
+ module_function :'alive?'
376
+ #--}}}
377
+ end
378
+
379
+ def open4(*cmd, &b) cmd.size == 0 ? Open4 : Open4::popen4(*cmd, &b) end