open4 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.
- data/README +62 -1
- data/lib/open4-0.5.0.rb +311 -0
- data/lib/open4.rb +171 -35
- data/sample/stdin_timeout.rb +9 -0
- data/sample/timeout.rb +37 -0
- metadata +5 -4
- data/lib/open4-0.4.0.rb +0 -175
- data/open4-0.4.0.gem +0 -0
data/README
CHANGED
@@ -5,10 +5,45 @@ URIS
|
|
5
5
|
|
6
6
|
SYNOPSIS
|
7
7
|
|
8
|
-
open child process with handles on pid, stdin, stdout, and stderr
|
8
|
+
open child process with handles on pid, stdin, stdout, and stderr: manage
|
9
|
+
child processes and their io handles easily.
|
9
10
|
|
10
11
|
HISTORY
|
11
12
|
|
13
|
+
0.5.0:
|
14
|
+
- on the suggestion of tim pease (thanks tim!), i added timeout features
|
15
|
+
to open4. the command run may have an overall timeout and individual
|
16
|
+
timeouts set for each of the io handles. for example
|
17
|
+
|
18
|
+
cmd = 'command_that_produce_out_at_one_second_intervals'
|
19
|
+
|
20
|
+
open4.spawn cmd, :stdout_timeout => 2
|
21
|
+
|
22
|
+
or
|
23
|
+
|
24
|
+
cmd = 'command_that_should_complete_in_about_one_minute'
|
25
|
+
|
26
|
+
open4.spawn cmd, :timeout => 60
|
27
|
+
|
28
|
+
or
|
29
|
+
|
30
|
+
cmd = 'consumes_input_at_one_line_per_second_rate'
|
31
|
+
|
32
|
+
input = %w( 42 forty-two 42.0 )
|
33
|
+
|
34
|
+
open4.spawn cmd, :stdin=>input, :stdin_timeout=>1
|
35
|
+
|
36
|
+
- added 'open4' alias so one can write
|
37
|
+
|
38
|
+
open4.spawn vs Open4.spawn
|
39
|
+
|
40
|
+
or even
|
41
|
+
|
42
|
+
open4(cmd) do |pid,i,o,e|
|
43
|
+
end
|
44
|
+
|
45
|
+
- added signal info to SpawnError
|
46
|
+
|
12
47
|
0.4.0:
|
13
48
|
- improved error handling contributed by jordan breeding.
|
14
49
|
- introduction of background/bg method
|
@@ -185,6 +220,32 @@ SAMPLES
|
|
185
220
|
---
|
186
221
|
stdout: "42\n"
|
187
222
|
|
223
|
+
----------------------------------------------------------------------------
|
224
|
+
the timeout methods can be used to ensure execution is preceding at the
|
225
|
+
desired interval. note also how to setup a 'pipeline'
|
226
|
+
----------------------------------------------------------------------------
|
227
|
+
|
228
|
+
harp: > cat sample/stdin_timeout.rb
|
229
|
+
require 'open4'
|
230
|
+
|
231
|
+
producer = 'ruby -e" STDOUT.sync = true; loop{sleep(rand+rand) and puts 42} "'
|
232
|
+
|
233
|
+
consumer = 'ruby -e" STDOUT.sync = true; STDIN.each{|line| puts line} "'
|
234
|
+
|
235
|
+
open4(producer) do |pid, i, o, e|
|
236
|
+
|
237
|
+
open4.spawn consumer, :stdin=>o, :stdout=>STDOUT, :stdin_timeout => 1.4
|
238
|
+
|
239
|
+
end
|
240
|
+
|
241
|
+
|
242
|
+
harp: > ruby sample/stdin_timeout.rb
|
243
|
+
42
|
244
|
+
42
|
245
|
+
42
|
246
|
+
42
|
247
|
+
42
|
248
|
+
/dmsp/reference/ruby-1.8.1//lib/ruby/1.8/timeout.rb:42:in `relay': execution expired (Timeout::Error)
|
188
249
|
|
189
250
|
AUTHOR
|
190
251
|
|
data/lib/open4-0.5.0.rb
ADDED
@@ -0,0 +1,311 @@
|
|
1
|
+
require 'fcntl'
|
2
|
+
require 'timeout'
|
3
|
+
require 'thread'
|
4
|
+
|
5
|
+
module Open4
|
6
|
+
#--{{{
|
7
|
+
VERSION = '0.5.0'
|
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
|
+
|
80
|
+
|
81
|
+
class SpawnError < Error
|
82
|
+
#--{{{
|
83
|
+
attr 'cmd'
|
84
|
+
attr 'status'
|
85
|
+
attr 'signals'
|
86
|
+
def exitstatus
|
87
|
+
@status.exitstatus
|
88
|
+
end
|
89
|
+
def initialize cmd, status
|
90
|
+
@cmd, @status = cmd, status
|
91
|
+
@signals = {}
|
92
|
+
if status.signaled?
|
93
|
+
@signals['termsig'] = status.termsig
|
94
|
+
@signals['stopsig'] = status.stopsig
|
95
|
+
end
|
96
|
+
sigs = @signals.map{|k,v| "#{ k }:#{ v.inspect }"}.join(' ')
|
97
|
+
super "cmd <#{ cmd }> failed with status <#{ exitstatus.inspect }> signals <#{ sigs }>"
|
98
|
+
end
|
99
|
+
#--}}}
|
100
|
+
end
|
101
|
+
|
102
|
+
class ThreadEnsemble
|
103
|
+
#--{{{
|
104
|
+
def initialize
|
105
|
+
@group = ThreadGroup.new
|
106
|
+
@kill = Queue.new
|
107
|
+
@killer = Thread.new{ k = @kill.pop and others.map{|t| t.kill unless t == k rescue next}}
|
108
|
+
@running = false
|
109
|
+
end
|
110
|
+
|
111
|
+
def add_thread *a, &b
|
112
|
+
raise if @running
|
113
|
+
@group.add(Thread.new(*a) do |*a|
|
114
|
+
Thread.stop
|
115
|
+
begin
|
116
|
+
b[*a]
|
117
|
+
ensure
|
118
|
+
kill if $!
|
119
|
+
end
|
120
|
+
end)
|
121
|
+
end
|
122
|
+
|
123
|
+
def kill
|
124
|
+
@kill.push Thread.current
|
125
|
+
end
|
126
|
+
|
127
|
+
def others
|
128
|
+
threads - [Thread.current]
|
129
|
+
end
|
130
|
+
|
131
|
+
def threads
|
132
|
+
@group.list
|
133
|
+
end
|
134
|
+
|
135
|
+
def value
|
136
|
+
others.map{|t| t.value}
|
137
|
+
end
|
138
|
+
|
139
|
+
def run
|
140
|
+
@running = true
|
141
|
+
others.map{|t| t.wakeup}
|
142
|
+
value
|
143
|
+
end
|
144
|
+
#--}}}
|
145
|
+
end
|
146
|
+
|
147
|
+
def to timeout = nil
|
148
|
+
#--{{{
|
149
|
+
Timeout.timeout(timeout){ yield }
|
150
|
+
#--}}}
|
151
|
+
end
|
152
|
+
module_function :to
|
153
|
+
|
154
|
+
def new_thread *a, &b
|
155
|
+
#--{{{
|
156
|
+
cur = Thread.current
|
157
|
+
Thread.new(*a) do |*a|
|
158
|
+
begin
|
159
|
+
b[*a]
|
160
|
+
rescue Exception => e
|
161
|
+
cur.raise e
|
162
|
+
end
|
163
|
+
end
|
164
|
+
#--}}}
|
165
|
+
end
|
166
|
+
module_function :new_thread
|
167
|
+
|
168
|
+
def getopts opts = {}
|
169
|
+
#--{{{
|
170
|
+
lambda do |*args|
|
171
|
+
keys, default, ignored = args
|
172
|
+
catch('opt') do
|
173
|
+
[keys].flatten.each do |key|
|
174
|
+
[key, key.to_s, key.to_s.intern].each do |key|
|
175
|
+
throw 'opt', opts[key] if opts.has_key?(key)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
default
|
179
|
+
end
|
180
|
+
end
|
181
|
+
#--}}}
|
182
|
+
end
|
183
|
+
module_function :getopts
|
184
|
+
|
185
|
+
def relay src, dst = nil, t = nil
|
186
|
+
#--{{{
|
187
|
+
if src.respond_to? :gets
|
188
|
+
while buf = to(t){ src.gets }
|
189
|
+
dst << buf if dst
|
190
|
+
end
|
191
|
+
|
192
|
+
elsif src.respond_to? :each
|
193
|
+
q = Queue.new
|
194
|
+
th = nil
|
195
|
+
|
196
|
+
timer_set = lambda do |t|
|
197
|
+
th = new_thread{ to(t){ q.pop } }
|
198
|
+
end
|
199
|
+
|
200
|
+
timer_cancel = lambda do |t|
|
201
|
+
th.kill if th rescue nil
|
202
|
+
end
|
203
|
+
|
204
|
+
timer_set[t]
|
205
|
+
begin
|
206
|
+
src.each do |buf|
|
207
|
+
timer_cancel[t]
|
208
|
+
dst << buf if dst
|
209
|
+
timer_set[t]
|
210
|
+
end
|
211
|
+
ensure
|
212
|
+
timer_cancel[t]
|
213
|
+
end
|
214
|
+
|
215
|
+
elsif src.respond_to? :read
|
216
|
+
buf = to(t){ src.read }
|
217
|
+
dst << buf if dst
|
218
|
+
|
219
|
+
else
|
220
|
+
buf = to(t){ src.to_s }
|
221
|
+
dst << buf if dst
|
222
|
+
end
|
223
|
+
#--}}}
|
224
|
+
end
|
225
|
+
module_function :relay
|
226
|
+
|
227
|
+
def spawn cmd, opts = {}
|
228
|
+
#--{{{
|
229
|
+
getopt = getopts opts
|
230
|
+
|
231
|
+
ignore_exit_failure = getopt[ 'ignore_exit_failure', getopt['quiet', false] ]
|
232
|
+
ignore_exec_failure = getopt[ 'ignore_exec_failure', !getopt['raise', true] ]
|
233
|
+
exitstatus = getopt[ %w( exitstatus exit_status status ), 0 ]
|
234
|
+
stdin = getopt[ %w( stdin in i 0 ) << 0 ]
|
235
|
+
stdout = getopt[ %w( stdout out o 1 ) << 1 ]
|
236
|
+
stderr = getopt[ %w( stderr err e 2 ) << 2 ]
|
237
|
+
pid = getopt[ 'pid' ]
|
238
|
+
timeout = getopt[ %w( timeout spawn_timeout ) ]
|
239
|
+
stdin_timeout = getopt[ %w( stdin_timeout ) ]
|
240
|
+
stdout_timeout = getopt[ %w( stdout_timeout io_timeout ) ]
|
241
|
+
stderr_timeout = getopt[ %w( stderr_timeout ) ]
|
242
|
+
|
243
|
+
stdin ||= '' if stdin_timeout
|
244
|
+
stdout ||= '' if stdout_timeout
|
245
|
+
stderr ||= '' if stderr_timeout
|
246
|
+
|
247
|
+
started = false
|
248
|
+
|
249
|
+
status =
|
250
|
+
begin
|
251
|
+
Timeout::timeout(timeout) do
|
252
|
+
popen4(cmd) do |c, i, o, e|
|
253
|
+
started = true
|
254
|
+
|
255
|
+
%w( pid= << push update ).each do |msg|
|
256
|
+
break(pid.send(msg, c)) if pid.respond_to? msg
|
257
|
+
end
|
258
|
+
|
259
|
+
te = ThreadEnsemble.new
|
260
|
+
|
261
|
+
te.add_thread(i, stdin) do |i, stdin|
|
262
|
+
relay stdin, i, stdin_timeout if stdin
|
263
|
+
i.close rescue nil
|
264
|
+
end
|
265
|
+
|
266
|
+
te.add_thread(o, stdout) do |o, stdout|
|
267
|
+
relay o, stdout, stdout_timeout if stdout
|
268
|
+
end
|
269
|
+
|
270
|
+
te.add_thread(e, stderr) do |o, stderr|
|
271
|
+
relay e, stderr, stderr_timeout if stderr
|
272
|
+
end
|
273
|
+
|
274
|
+
te.run
|
275
|
+
end
|
276
|
+
end
|
277
|
+
rescue
|
278
|
+
raise unless(not started and ignore_exec_failure)
|
279
|
+
end
|
280
|
+
|
281
|
+
raise SpawnError.new(cmd, status) unless
|
282
|
+
(ignore_exit_failure or (status.nil? and ignore_exec_failure) or (status.exitstatus == exitstatus))
|
283
|
+
|
284
|
+
status
|
285
|
+
#--}}}
|
286
|
+
end
|
287
|
+
module_function :spawn
|
288
|
+
|
289
|
+
def background cmd, opts = {}
|
290
|
+
#--{{{
|
291
|
+
require 'thread'
|
292
|
+
q = Queue.new
|
293
|
+
opts['pid'] = opts[:pid] = q
|
294
|
+
thread = Thread.new(cmd, opts){|cmd, opts| spawn cmd, opts}
|
295
|
+
pid = q.pop
|
296
|
+
sc = class << thread; self; end
|
297
|
+
sc.module_eval {
|
298
|
+
define_method(:pid){ pid }
|
299
|
+
define_method(:spawn_status){ @spawn_status ||= value }
|
300
|
+
define_method(:exitstatus){ spawn_status.exitstatus }
|
301
|
+
}
|
302
|
+
thread
|
303
|
+
#--}}}
|
304
|
+
end
|
305
|
+
alias bg background
|
306
|
+
module_function :background
|
307
|
+
module_function :bg
|
308
|
+
#--}}}
|
309
|
+
end
|
310
|
+
|
311
|
+
def open4(*cmd, &b) cmd.size == 0 ? Open4 : Open4::popen4(*cmd, &b) end
|
data/lib/open4.rb
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
require 'fcntl'
|
2
|
+
require 'timeout'
|
3
|
+
require 'thread'
|
2
4
|
|
3
5
|
module Open4
|
4
6
|
#--{{{
|
5
|
-
|
6
|
-
|
7
|
-
|
7
|
+
VERSION = '0.5.0'
|
8
|
+
def self.version() VERSION end
|
9
|
+
|
10
|
+
class Error < ::StandardError; end
|
8
11
|
|
9
|
-
def popen4(*cmd)
|
12
|
+
def popen4(*cmd, &b)
|
10
13
|
#--{{{
|
11
14
|
pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe
|
12
15
|
|
@@ -32,7 +35,7 @@ module Open4
|
|
32
35
|
|
33
36
|
begin
|
34
37
|
exec(*cmd)
|
35
|
-
raise
|
38
|
+
raise 'forty-two'
|
36
39
|
rescue Exception => e
|
37
40
|
Marshal.dump(e, ps.last)
|
38
41
|
ps.last.flush
|
@@ -57,9 +60,9 @@ module Open4
|
|
57
60
|
|
58
61
|
pi = [pw.last, pr.first, pe.first]
|
59
62
|
|
60
|
-
if
|
63
|
+
if b
|
61
64
|
begin
|
62
|
-
|
65
|
+
b[cid, *pi]
|
63
66
|
Process.waitpid2(cid).last
|
64
67
|
ensure
|
65
68
|
pi.each{|fd| fd.close unless fd.closed?}
|
@@ -73,23 +76,98 @@ module Open4
|
|
73
76
|
module_function :popen4
|
74
77
|
module_function :open4
|
75
78
|
|
76
|
-
|
79
|
+
|
80
|
+
|
77
81
|
class SpawnError < Error
|
78
82
|
#--{{{
|
79
83
|
attr 'cmd'
|
80
84
|
attr 'status'
|
85
|
+
attr 'signals'
|
81
86
|
def exitstatus
|
82
87
|
@status.exitstatus
|
83
88
|
end
|
84
89
|
def initialize cmd, status
|
85
90
|
@cmd, @status = cmd, status
|
86
|
-
|
91
|
+
@signals = {}
|
92
|
+
if status.signaled?
|
93
|
+
@signals['termsig'] = status.termsig
|
94
|
+
@signals['stopsig'] = status.stopsig
|
95
|
+
end
|
96
|
+
sigs = @signals.map{|k,v| "#{ k }:#{ v.inspect }"}.join(' ')
|
97
|
+
super "cmd <#{ cmd }> failed with status <#{ exitstatus.inspect }> signals <#{ sigs }>"
|
87
98
|
end
|
88
99
|
#--}}}
|
89
100
|
end
|
90
|
-
|
101
|
+
|
102
|
+
class ThreadEnsemble
|
103
|
+
#--{{{
|
104
|
+
def initialize
|
105
|
+
@group = ThreadGroup.new
|
106
|
+
@kill = Queue.new
|
107
|
+
@killer = Thread.new{ k = @kill.pop and others.map{|t| t.kill unless t == k rescue next}}
|
108
|
+
@running = false
|
109
|
+
end
|
110
|
+
|
111
|
+
def add_thread *a, &b
|
112
|
+
raise if @running
|
113
|
+
@group.add(Thread.new(*a) do |*a|
|
114
|
+
Thread.stop
|
115
|
+
begin
|
116
|
+
b[*a]
|
117
|
+
ensure
|
118
|
+
kill if $!
|
119
|
+
end
|
120
|
+
end)
|
121
|
+
end
|
122
|
+
|
123
|
+
def kill
|
124
|
+
@kill.push Thread.current
|
125
|
+
end
|
126
|
+
|
127
|
+
def others
|
128
|
+
threads - [Thread.current]
|
129
|
+
end
|
130
|
+
|
131
|
+
def threads
|
132
|
+
@group.list
|
133
|
+
end
|
134
|
+
|
135
|
+
def value
|
136
|
+
others.map{|t| t.value}
|
137
|
+
end
|
138
|
+
|
139
|
+
def run
|
140
|
+
@running = true
|
141
|
+
others.map{|t| t.wakeup}
|
142
|
+
value
|
143
|
+
end
|
144
|
+
#--}}}
|
145
|
+
end
|
146
|
+
|
147
|
+
def to timeout = nil
|
148
|
+
#--{{{
|
149
|
+
Timeout.timeout(timeout){ yield }
|
150
|
+
#--}}}
|
151
|
+
end
|
152
|
+
module_function :to
|
153
|
+
|
154
|
+
def new_thread *a, &b
|
155
|
+
#--{{{
|
156
|
+
cur = Thread.current
|
157
|
+
Thread.new(*a) do |*a|
|
158
|
+
begin
|
159
|
+
b[*a]
|
160
|
+
rescue Exception => e
|
161
|
+
cur.raise e
|
162
|
+
end
|
163
|
+
end
|
164
|
+
#--}}}
|
165
|
+
end
|
166
|
+
module_function :new_thread
|
167
|
+
|
168
|
+
def getopts opts = {}
|
91
169
|
#--{{{
|
92
|
-
|
170
|
+
lambda do |*args|
|
93
171
|
keys, default, ignored = args
|
94
172
|
catch('opt') do
|
95
173
|
[keys].flatten.each do |key|
|
@@ -100,45 +178,101 @@ module Open4
|
|
100
178
|
default
|
101
179
|
end
|
102
180
|
end
|
181
|
+
#--}}}
|
182
|
+
end
|
183
|
+
module_function :getopts
|
184
|
+
|
185
|
+
def relay src, dst = nil, t = nil
|
186
|
+
#--{{{
|
187
|
+
if src.respond_to? :gets
|
188
|
+
while buf = to(t){ src.gets }
|
189
|
+
dst << buf if dst
|
190
|
+
end
|
191
|
+
|
192
|
+
elsif src.respond_to? :each
|
193
|
+
q = Queue.new
|
194
|
+
th = nil
|
195
|
+
|
196
|
+
timer_set = lambda do |t|
|
197
|
+
th = new_thread{ to(t){ q.pop } }
|
198
|
+
end
|
199
|
+
|
200
|
+
timer_cancel = lambda do |t|
|
201
|
+
th.kill if th rescue nil
|
202
|
+
end
|
203
|
+
|
204
|
+
timer_set[t]
|
205
|
+
begin
|
206
|
+
src.each do |buf|
|
207
|
+
timer_cancel[t]
|
208
|
+
dst << buf if dst
|
209
|
+
timer_set[t]
|
210
|
+
end
|
211
|
+
ensure
|
212
|
+
timer_cancel[t]
|
213
|
+
end
|
214
|
+
|
215
|
+
elsif src.respond_to? :read
|
216
|
+
buf = to(t){ src.read }
|
217
|
+
dst << buf if dst
|
218
|
+
|
219
|
+
else
|
220
|
+
buf = to(t){ src.to_s }
|
221
|
+
dst << buf if dst
|
222
|
+
end
|
223
|
+
#--}}}
|
224
|
+
end
|
225
|
+
module_function :relay
|
226
|
+
|
227
|
+
def spawn cmd, opts = {}
|
228
|
+
#--{{{
|
229
|
+
getopt = getopts opts
|
103
230
|
|
104
231
|
ignore_exit_failure = getopt[ 'ignore_exit_failure', getopt['quiet', false] ]
|
105
232
|
ignore_exec_failure = getopt[ 'ignore_exec_failure', !getopt['raise', true] ]
|
106
233
|
exitstatus = getopt[ %w( exitstatus exit_status status ), 0 ]
|
107
|
-
stdin = getopt[
|
108
|
-
stdout = getopt[
|
109
|
-
stderr = getopt[
|
234
|
+
stdin = getopt[ %w( stdin in i 0 ) << 0 ]
|
235
|
+
stdout = getopt[ %w( stdout out o 1 ) << 1 ]
|
236
|
+
stderr = getopt[ %w( stderr err e 2 ) << 2 ]
|
110
237
|
pid = getopt[ 'pid' ]
|
238
|
+
timeout = getopt[ %w( timeout spawn_timeout ) ]
|
239
|
+
stdin_timeout = getopt[ %w( stdin_timeout ) ]
|
240
|
+
stdout_timeout = getopt[ %w( stdout_timeout io_timeout ) ]
|
241
|
+
stderr_timeout = getopt[ %w( stderr_timeout ) ]
|
242
|
+
|
243
|
+
stdin ||= '' if stdin_timeout
|
244
|
+
stdout ||= '' if stdout_timeout
|
245
|
+
stderr ||= '' if stderr_timeout
|
111
246
|
|
112
247
|
started = false
|
113
248
|
|
114
249
|
status =
|
115
250
|
begin
|
116
|
-
|
117
|
-
|
251
|
+
Timeout::timeout(timeout) do
|
252
|
+
popen4(cmd) do |c, i, o, e|
|
253
|
+
started = true
|
118
254
|
|
119
|
-
|
120
|
-
|
121
|
-
|
255
|
+
%w( pid= << push update ).each do |msg|
|
256
|
+
break(pid.send(msg, c)) if pid.respond_to? msg
|
257
|
+
end
|
258
|
+
|
259
|
+
te = ThreadEnsemble.new
|
122
260
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
stdin.each{|buf| i << buf}
|
127
|
-
elsif stdin.respond_to? :read
|
128
|
-
i << stdin.read
|
129
|
-
else
|
130
|
-
i << stdin.to_s
|
261
|
+
te.add_thread(i, stdin) do |i, stdin|
|
262
|
+
relay stdin, i, stdin_timeout if stdin
|
263
|
+
i.close rescue nil
|
131
264
|
end
|
132
|
-
end
|
133
|
-
i.close
|
134
|
-
end
|
135
265
|
|
136
|
-
|
137
|
-
|
266
|
+
te.add_thread(o, stdout) do |o, stdout|
|
267
|
+
relay o, stdout, stdout_timeout if stdout
|
268
|
+
end
|
138
269
|
|
139
|
-
|
140
|
-
|
141
|
-
|
270
|
+
te.add_thread(e, stderr) do |o, stderr|
|
271
|
+
relay e, stderr, stderr_timeout if stderr
|
272
|
+
end
|
273
|
+
|
274
|
+
te.run
|
275
|
+
end
|
142
276
|
end
|
143
277
|
rescue
|
144
278
|
raise unless(not started and ignore_exec_failure)
|
@@ -173,3 +307,5 @@ module Open4
|
|
173
307
|
module_function :bg
|
174
308
|
#--}}}
|
175
309
|
end
|
310
|
+
|
311
|
+
def open4(*cmd, &b) cmd.size == 0 ? Open4 : Open4::popen4(*cmd, &b) end
|
@@ -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
|
data/sample/timeout.rb
ADDED
@@ -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
|
+
}
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
|
|
3
3
|
specification_version: 1
|
4
4
|
name: open4
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.
|
7
|
-
date: 2006-
|
6
|
+
version: 0.5.0
|
7
|
+
date: 2006-08-07 00:00:00.000000 -06:00
|
8
8
|
summary: open4
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -36,14 +36,15 @@ files:
|
|
36
36
|
- build
|
37
37
|
- install
|
38
38
|
- gemspec.rb
|
39
|
-
- open4-0.4.0.gem
|
40
39
|
- sample/block.rb
|
41
40
|
- sample/simple.rb
|
42
41
|
- sample/exception.rb
|
43
42
|
- sample/spawn.rb
|
44
43
|
- sample/bg.rb
|
45
|
-
-
|
44
|
+
- sample/timeout.rb
|
45
|
+
- sample/stdin_timeout.rb
|
46
46
|
- lib/open4.rb
|
47
|
+
- lib/open4-0.5.0.rb
|
47
48
|
test_files: []
|
48
49
|
rdoc_options: []
|
49
50
|
extra_rdoc_files: []
|
data/lib/open4-0.4.0.rb
DELETED
@@ -1,175 +0,0 @@
|
|
1
|
-
require 'fcntl'
|
2
|
-
|
3
|
-
module Open4
|
4
|
-
#--{{{
|
5
|
-
def self.version
|
6
|
-
'0.4.0'
|
7
|
-
end
|
8
|
-
|
9
|
-
def popen4(*cmd)
|
10
|
-
#--{{{
|
11
|
-
pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe
|
12
|
-
|
13
|
-
verbose = $VERBOSE
|
14
|
-
begin
|
15
|
-
$VERBOSE = nil
|
16
|
-
ps.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
17
|
-
|
18
|
-
cid = fork {
|
19
|
-
pw.last.close
|
20
|
-
STDIN.reopen pw.first
|
21
|
-
pw.first.close
|
22
|
-
|
23
|
-
pr.first.close
|
24
|
-
STDOUT.reopen pr.last
|
25
|
-
pr.last.close
|
26
|
-
|
27
|
-
pe.first.close
|
28
|
-
STDERR.reopen pe.last
|
29
|
-
pe.last.close
|
30
|
-
|
31
|
-
STDOUT.sync = STDERR.sync = true
|
32
|
-
|
33
|
-
begin
|
34
|
-
exec(*cmd)
|
35
|
-
raise "exec failed!"
|
36
|
-
rescue Exception => e
|
37
|
-
Marshal.dump(e, ps.last)
|
38
|
-
ps.last.flush
|
39
|
-
end
|
40
|
-
ps.last.close unless (ps.last.closed?)
|
41
|
-
exit!
|
42
|
-
}
|
43
|
-
ensure
|
44
|
-
$VERBOSE = verbose
|
45
|
-
end
|
46
|
-
|
47
|
-
[pw.first, pr.last, pe.last, ps.last].each{|fd| fd.close}
|
48
|
-
|
49
|
-
begin
|
50
|
-
e = Marshal.load ps.first
|
51
|
-
raise(Exception === e ? e : "unknown failure!")
|
52
|
-
rescue EOFError # If we get an EOF error, then the exec was successful
|
53
|
-
42
|
54
|
-
end
|
55
|
-
|
56
|
-
pw.last.sync = true
|
57
|
-
|
58
|
-
pi = [pw.last, pr.first, pe.first]
|
59
|
-
|
60
|
-
if defined? yield
|
61
|
-
begin
|
62
|
-
yield(cid, *pi)
|
63
|
-
Process.waitpid2(cid).last
|
64
|
-
ensure
|
65
|
-
pi.each{|fd| fd.close unless fd.closed?}
|
66
|
-
end
|
67
|
-
else
|
68
|
-
[cid, pw.last, pr.first, pe.first]
|
69
|
-
end
|
70
|
-
#--}}}
|
71
|
-
end
|
72
|
-
alias open4 popen4
|
73
|
-
module_function :popen4
|
74
|
-
module_function :open4
|
75
|
-
|
76
|
-
class Error < ::StandardError; end
|
77
|
-
class SpawnError < Error
|
78
|
-
#--{{{
|
79
|
-
attr 'cmd'
|
80
|
-
attr 'status'
|
81
|
-
def exitstatus
|
82
|
-
@status.exitstatus
|
83
|
-
end
|
84
|
-
def initialize cmd, status
|
85
|
-
@cmd, @status = cmd, status
|
86
|
-
super "cmd <#{ cmd }> failed with <#{ exitstatus }>"
|
87
|
-
end
|
88
|
-
#--}}}
|
89
|
-
end
|
90
|
-
def spawn cmd, opts = {}
|
91
|
-
#--{{{
|
92
|
-
getopt = lambda do |*args|
|
93
|
-
keys, default, ignored = args
|
94
|
-
catch('opt') do
|
95
|
-
[keys].flatten.each do |key|
|
96
|
-
[key, key.to_s, key.to_s.intern].each do |key|
|
97
|
-
throw 'opt', opts[key] if opts.has_key?(key)
|
98
|
-
end
|
99
|
-
end
|
100
|
-
default
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
ignore_exit_failure = getopt[ 'ignore_exit_failure', getopt['quiet', false] ]
|
105
|
-
ignore_exec_failure = getopt[ 'ignore_exec_failure', !getopt['raise', true] ]
|
106
|
-
exitstatus = getopt[ %w( exitstatus exit_status status ), 0 ]
|
107
|
-
stdin = getopt[ ['stdin', 'in', '0', 0] ]
|
108
|
-
stdout = getopt[ ['stdout', 'out', '1', 1] ]
|
109
|
-
stderr = getopt[ ['stderr', 'err', '2', 2] ]
|
110
|
-
pid = getopt[ 'pid' ]
|
111
|
-
|
112
|
-
started = false
|
113
|
-
|
114
|
-
status =
|
115
|
-
begin
|
116
|
-
popen4(cmd) do |c, i, o, e|
|
117
|
-
started = true
|
118
|
-
|
119
|
-
if pid.respond_to? '<<'
|
120
|
-
pid << c
|
121
|
-
end
|
122
|
-
|
123
|
-
it = Thread.new(i,stdin) do |i,stdin|
|
124
|
-
if stdin
|
125
|
-
if stdin.respond_to? :each
|
126
|
-
stdin.each{|buf| i << buf}
|
127
|
-
elsif stdin.respond_to? :read
|
128
|
-
i << stdin.read
|
129
|
-
else
|
130
|
-
i << stdin.to_s
|
131
|
-
end
|
132
|
-
end
|
133
|
-
i.close
|
134
|
-
end
|
135
|
-
|
136
|
-
ot = Thread.new(o,stdout){|o,stdout| o.each{|buf| stdout << buf if stdout}}
|
137
|
-
et = Thread.new(e,stderr){|e,stderr| e.each{|buf| stderr << buf if stderr}}
|
138
|
-
|
139
|
-
it.join
|
140
|
-
ot.join if ot
|
141
|
-
et.join if et
|
142
|
-
end
|
143
|
-
rescue
|
144
|
-
raise unless(not started and ignore_exec_failure)
|
145
|
-
end
|
146
|
-
|
147
|
-
raise SpawnError.new(cmd, status) unless
|
148
|
-
(ignore_exit_failure or (status.nil? and ignore_exec_failure) or (status.exitstatus == exitstatus))
|
149
|
-
|
150
|
-
status
|
151
|
-
#--}}}
|
152
|
-
end
|
153
|
-
module_function :spawn
|
154
|
-
|
155
|
-
def background cmd, opts = {}
|
156
|
-
#--{{{
|
157
|
-
require 'thread'
|
158
|
-
q = Queue.new
|
159
|
-
opts['pid'] = opts[:pid] = q
|
160
|
-
thread = Thread.new(cmd, opts){|cmd, opts| spawn cmd, opts}
|
161
|
-
pid = q.pop
|
162
|
-
sc = class << thread; self; end
|
163
|
-
sc.module_eval {
|
164
|
-
define_method(:pid){ pid }
|
165
|
-
define_method(:spawn_status){ @spawn_status ||= value }
|
166
|
-
define_method(:exitstatus){ spawn_status.exitstatus }
|
167
|
-
}
|
168
|
-
thread
|
169
|
-
#--}}}
|
170
|
-
end
|
171
|
-
alias bg background
|
172
|
-
module_function :background
|
173
|
-
module_function :bg
|
174
|
-
#--}}}
|
175
|
-
end
|
data/open4-0.4.0.gem
DELETED
File without changes
|