engineyard-serverside 1.5.33 → 1.5.35.pre.1

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.
@@ -8,7 +8,7 @@ else
8
8
  end
9
9
 
10
10
  $LOAD_PATH.unshift File.expand_path('vendor/thor/lib', File.dirname(__FILE__))
11
- $LOAD_PATH.unshift File.expand_path('vendor/open4/lib', File.dirname(__FILE__))
11
+ $LOAD_PATH.unshift File.expand_path('vendor/systemu/lib', File.dirname(__FILE__))
12
12
  $LOAD_PATH.unshift File.expand_path('vendor/escape/lib', File.dirname(__FILE__))
13
13
  $LOAD_PATH.unshift File.expand_path('vendor/json_pure/lib', File.dirname(__FILE__))
14
14
 
@@ -193,11 +193,10 @@ To fix this problem, commit your Gemfile.lock to your repository and redeploy.
193
193
  # create it on all the servers that will need it.
194
194
  # TODO - This logic likely fails when people change deploy keys.
195
195
  def ssh_executable
196
- path = ssh_wrapper_path
197
196
  roles :app_master, :app, :solo, :util do
198
197
  run(generate_ssh_wrapper)
199
198
  end
200
- path
199
+ ssh_wrapper_path
201
200
  end
202
201
 
203
202
  # We specify 'IdentitiesOnly' to avoid failures on systems with > 5 private keys available.
@@ -206,16 +205,12 @@ To fix this problem, commit your Gemfile.lock to your repository and redeploy.
206
205
  # Learned this at http://lists.mindrot.org/pipermail/openssh-unix-dev/2009-February/027271.html
207
206
  # (Thanks Jim L.)
208
207
  def generate_ssh_wrapper
209
- path = ssh_wrapper_path
210
208
  identity_file = "~/.ssh/#{c.app}-deploy-key"
211
- <<-WRAP
212
- [[ -x #{path} ]] || cat > #{path} <<'SSH'
213
- #!/bin/sh
214
- unset SSH_AUTH_SOCK
215
- ssh -o 'CheckHostIP no' -o 'StrictHostKeyChecking no' -o 'PasswordAuthentication no' -o 'LogLevel DEBUG' -o 'IdentityFile #{identity_file}' -o 'IdentitiesOnly yes' -o 'UserKnownHostsFile /dev/null' $*
216
- SSH
217
- chmod 0700 #{path}
218
- WRAP
209
+ %{ echo "#{wrapper_for(identity_file)}" > #{ssh_wrapper_path} && chmod 0700 #{ssh_wrapper_path} }
210
+ end
211
+
212
+ def wrapper_for(identity)
213
+ "#!/bin/sh\nunset SSH_AUTH_SOCK; ssh -o CheckHostIP=no -o StrictHostKeyChecking=no -o PasswordAuthentication=no -o LogLevel=DEBUG -o IdentityFile=#{identity} -o IdentitiesOnly=yes -o UserKnownHostsFile=/dev/null $*"
219
214
  end
220
215
 
221
216
  def ssh_wrapper_path
@@ -387,7 +382,7 @@ WRAP
387
382
  end
388
383
 
389
384
  def callback(what)
390
- @callbacks_reached ||= true
385
+ @callbacks_reached = true
391
386
  if File.exist?("#{c.release_path}/deploy/#{what}.rb")
392
387
  run Escape.shell_command(base_callback_command_for(what)) do |server, cmd|
393
388
  per_instance_args = [
@@ -1,4 +1,4 @@
1
- require 'open4'
1
+ require 'systemu'
2
2
 
3
3
  module EY
4
4
  module Serverside
@@ -62,10 +62,11 @@ module EY
62
62
  out = verbose? ? Tee.new($stdout, log) : log
63
63
  err = Tee.new($stderr, log) # we always want to see errors
64
64
 
65
+ cmd = "sh -c #{Escape.shell_command([cmd])}"
66
+ puts "running #{cmd}" if ENV['DEBUG']
65
67
  out << with_timestamp(":: running #{cmd}\n")
66
-
67
- # :quiet means don't raise an error on nonzero exit status
68
- status = Open4.spawn cmd, :stdin => '', :stdout => out, :quiet => true # leaving :stderr out for now
68
+ status = systemu cmd, 'stdout' => out, 'stderr' => err
69
+ puts "exit status= #{status.exitstatus}" if ENV['DEBUG']
69
70
  status.exitstatus == 0
70
71
  end
71
72
  end
@@ -83,12 +83,12 @@ module EY
83
83
  if local?
84
84
  logged_system(command)
85
85
  else
86
- logged_system(ssh_command + " " + Escape.shell_command(["#{user}@#{hostname}", command]))
86
+ logged_system("#{ssh_command} #{user}@#{hostname} #{command}")
87
87
  end
88
88
  end
89
89
 
90
90
  def ssh_command
91
- "ssh -i #{ENV['HOME']}/.ssh/internal -o StrictHostKeyChecking=no -o PasswordAuthentication=no"
91
+ "ssh -i #{ENV['HOME']}/.ssh/internal -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o PasswordAuthentication=no"
92
92
  end
93
93
 
94
94
  end
@@ -50,11 +50,11 @@ module EY
50
50
 
51
51
  private
52
52
 
53
- def run_on_roles(cmd, wrapper=%w[sh -l -c], &block)
53
+ def run_on_roles(cmd, &block)
54
54
  servers = EY::Serverside::Server.from_roles(@roles)
55
55
  futures = EY::Serverside::Future.call(servers, block_given?) do |server, exec_block|
56
56
  to_run = exec_block ? block.call(server, cmd.dup) : cmd
57
- server.run(Escape.shell_command(wrapper + [to_run]))
57
+ server.run(to_run)
58
58
  end
59
59
 
60
60
  unless EY::Serverside::Future.success?(futures)
@@ -1,5 +1,5 @@
1
1
  module EY
2
2
  module Serverside
3
- VERSION = '1.5.33'
3
+ VERSION = '1.5.35.pre.1'
4
4
  end
5
5
  end
@@ -0,0 +1,3 @@
1
+ same as Ruby's
2
+
3
+ http://www.ruby-lang.org/en/LICENSE.txt
@@ -0,0 +1,363 @@
1
+ # encoding: utf-8
2
+
3
+ require 'tmpdir'
4
+ require 'socket'
5
+ require 'fileutils'
6
+ require 'rbconfig'
7
+ require 'thread'
8
+
9
+ class Object
10
+ def systemu(*a, &b) SystemUniversal.new(*a, &b).systemu end
11
+ end
12
+
13
+ class SystemUniversal
14
+ #
15
+ # constants
16
+ #
17
+ SystemUniversal::VERSION = '2.5.0' unless SystemUniversal.send(:const_defined?, :VERSION)
18
+ def SystemUniversal.version() SystemUniversal::VERSION end
19
+ def version() SystemUniversal::VERSION end
20
+ #
21
+ # class methods
22
+ #
23
+
24
+ @host = Socket.gethostname
25
+ @ppid = Process.ppid
26
+ @pid = Process.pid
27
+ @turd = ENV['SYSTEMU_TURD']
28
+
29
+ c = begin; ::RbConfig::CONFIG; rescue NameError; ::Config::CONFIG; end
30
+ ruby = File.join(c['bindir'], c['ruby_install_name']) << c['EXEEXT']
31
+ @ruby = if system('%s -e 42' % ruby)
32
+ ruby
33
+ else
34
+ system('%s -e 42' % 'ruby') ? 'ruby' : warn('no ruby in PATH/CONFIG')
35
+ end
36
+
37
+ class << SystemUniversal
38
+ %w( host ppid pid ruby turd ).each{|a| attr_accessor a}
39
+
40
+ def quote(*words)
41
+ words.map{|word| word.inspect}.join(' ')
42
+ end
43
+ end
44
+
45
+ #
46
+ # instance methods
47
+ #
48
+
49
+ def initialize argv, opts = {}, &block
50
+ getopt = getopts opts
51
+
52
+ @argv = argv
53
+ @block = block
54
+
55
+ @stdin = getopt[ ['stdin', 'in', '0', 0] ]
56
+ @stdout = getopt[ ['stdout', 'out', '1', 1] ]
57
+ @stderr = getopt[ ['stderr', 'err', '2', 2] ]
58
+ @env = getopt[ 'env' ]
59
+ @cwd = getopt[ 'cwd' ]
60
+
61
+ @host = getopt[ 'host', self.class.host ]
62
+ @ppid = getopt[ 'ppid', self.class.ppid ]
63
+ @pid = getopt[ 'pid', self.class.pid ]
64
+ @ruby = getopt[ 'ruby', self.class.ruby ]
65
+ end
66
+
67
+ def systemu
68
+ tmpdir do |tmp|
69
+ c = child_setup tmp
70
+ status = nil
71
+
72
+ begin
73
+ thread = nil
74
+
75
+ quietly{
76
+ IO.popen "#{ quote(@ruby) } #{ quote(c['program']) }", 'r+' do |pipe|
77
+ line = pipe.gets
78
+ case line
79
+ when %r/^pid: \d+$/
80
+ cid = Integer line[%r/\d+/]
81
+ else
82
+ begin
83
+ buf = pipe.read
84
+ buf = "#{ line }#{ buf }"
85
+ e = Marshal.load buf
86
+ raise unless Exception === e
87
+ raise e
88
+ rescue
89
+ raise "wtf?\n#{ buf }\n"
90
+ end
91
+ end
92
+ thread = new_thread cid, @block if @block
93
+ pipe.read rescue nil
94
+ end
95
+ }
96
+ status = $?
97
+ ensure
98
+ if thread
99
+ begin
100
+ class << status
101
+ attr 'thread'
102
+ end
103
+ status.instance_eval{ @thread = thread }
104
+ rescue
105
+ 42
106
+ end
107
+ end
108
+ end
109
+
110
+ if @stdout or @stderr
111
+ open(c['stdout']){|f| relay f => @stdout} if @stdout
112
+ open(c['stderr']){|f| relay f => @stderr} if @stderr
113
+ status
114
+ else
115
+ [status, IO.read(c['stdout']), IO.read(c['stderr'])]
116
+ end
117
+ end
118
+ end
119
+
120
+ def quote *args, &block
121
+ SystemUniversal.quote(*args, &block)
122
+ end
123
+
124
+ def new_thread cid, block
125
+ q = Queue.new
126
+ Thread.new(cid) do |cid|
127
+ current = Thread.current
128
+ current.abort_on_exception = true
129
+ q.push current
130
+ block.call cid
131
+ end
132
+ q.pop
133
+ end
134
+
135
+ def child_setup tmp
136
+ stdin = File.expand_path(File.join(tmp, 'stdin'))
137
+ stdout = File.expand_path(File.join(tmp, 'stdout'))
138
+ stderr = File.expand_path(File.join(tmp, 'stderr'))
139
+ program = File.expand_path(File.join(tmp, 'program'))
140
+ config = File.expand_path(File.join(tmp, 'config'))
141
+
142
+ if @stdin
143
+ open(stdin, 'w'){|f| relay @stdin => f}
144
+ else
145
+ FileUtils.touch stdin
146
+ end
147
+ FileUtils.touch stdout
148
+ FileUtils.touch stderr
149
+
150
+ c = {}
151
+ c['argv'] = @argv
152
+ c['env'] = @env
153
+ c['cwd'] = @cwd
154
+ c['stdin'] = stdin
155
+ c['stdout'] = stdout
156
+ c['stderr'] = stderr
157
+ c['program'] = program
158
+ open(config, 'w'){|f| Marshal.dump(c, f)}
159
+
160
+ open(program, 'w'){|f| f.write child_program(config)}
161
+
162
+ c
163
+ end
164
+
165
+ def quietly
166
+ v = $VERBOSE
167
+ $VERBOSE = nil
168
+ yield
169
+ ensure
170
+ $VERBOSE = v
171
+ end
172
+
173
+ def child_program config
174
+ <<-program
175
+ # encoding: utf-8
176
+
177
+ PIPE = STDOUT.dup
178
+ begin
179
+ config = Marshal.load(IO.read('#{ config }'))
180
+
181
+ argv = config['argv']
182
+ env = config['env']
183
+ cwd = config['cwd']
184
+ stdin = config['stdin']
185
+ stdout = config['stdout']
186
+ stderr = config['stderr']
187
+
188
+ Dir.chdir cwd if cwd
189
+ env.each{|k,v| ENV[k.to_s] = v.to_s} if env
190
+
191
+ STDIN.reopen stdin
192
+ STDOUT.reopen stdout
193
+ STDERR.reopen stderr
194
+
195
+ PIPE.puts "pid: \#{ Process.pid }"
196
+ PIPE.flush ### the process is ready yo!
197
+ PIPE.close
198
+ if RUBY_VERSION >= "1.9"
199
+ exec *argv
200
+ else
201
+ exec argv
202
+ end
203
+ rescue Exception => e
204
+ PIPE.write Marshal.dump(e) rescue nil
205
+ exit 42
206
+ end
207
+ program
208
+ end
209
+
210
+ def relay srcdst
211
+ src, dst, ignored = srcdst.to_a.first
212
+ if src.respond_to? 'read'
213
+ while((buf = src.read(8192))); dst << buf; end
214
+ else
215
+ if src.respond_to?(:each_line)
216
+ src.each_line{|buf| dst << buf}
217
+ else
218
+ src.each{|buf| dst << buf}
219
+ end
220
+ end
221
+ end
222
+
223
+ def tmpdir d = Dir.tmpdir, max = 42, &b
224
+ i = -1 and loop{
225
+ i += 1
226
+
227
+ tmp = File.join d, "systemu_#{ @host }_#{ @ppid }_#{ @pid }_#{ rand }_#{ i += 1 }"
228
+
229
+ begin
230
+ Dir.mkdir tmp
231
+ rescue Errno::EEXIST
232
+ raise if i >= max
233
+ next
234
+ end
235
+
236
+ break(
237
+ if b
238
+ begin
239
+ b.call tmp
240
+ ensure
241
+ FileUtils.rm_rf tmp unless SystemU.turd
242
+ end
243
+ else
244
+ tmp
245
+ end
246
+ )
247
+ }
248
+ end
249
+
250
+ def getopts opts = {}
251
+ lambda do |*args|
252
+ keys, default, ignored = args
253
+ catch(:opt) do
254
+ [keys].flatten.each do |key|
255
+ [key, key.to_s, key.to_s.intern].each do |key|
256
+ throw :opt, opts[key] if opts.has_key?(key)
257
+ end
258
+ end
259
+ default
260
+ end
261
+ end
262
+ end
263
+ end
264
+
265
+ # some monkeypatching for JRuby
266
+ if defined? JRUBY_VERSION
267
+ require 'jruby'
268
+ java_import org.jruby.RubyProcess
269
+
270
+ class SystemUniversal
271
+ def systemu
272
+ split_argv = JRuby::PathHelper.smart_split_command @argv
273
+ process = java.lang.Runtime.runtime.exec split_argv.to_java(:string)
274
+
275
+ stdout, stderr = [process.input_stream, process.error_stream].map do |stream|
276
+ StreamReader.new(stream)
277
+ end
278
+
279
+ exit_code = process.wait_for
280
+ field = process.get_class.get_declared_field("pid")
281
+ field.set_accessible(true)
282
+ pid = field.get(process)
283
+ [
284
+ RubyProcess::RubyStatus.new_process_status(JRuby.runtime, exit_code, pid),
285
+ stdout.join,
286
+ stderr.join
287
+ ]
288
+ end
289
+
290
+ class StreamReader
291
+ def initialize(stream)
292
+ @data = ""
293
+ @thread = Thread.new do
294
+ reader = java.io.BufferedReader.new java.io.InputStreamReader.new(stream)
295
+
296
+ while line = reader.read_line
297
+ @data << line << "\n"
298
+ end
299
+ end
300
+ end
301
+
302
+ def join
303
+ @thread.join
304
+ @data
305
+ end
306
+ end
307
+ end
308
+ end
309
+
310
+
311
+
312
+ SystemU = SystemUniversal unless defined? SystemU
313
+ Systemu = SystemUniversal unless defined? Systemu
314
+
315
+
316
+
317
+
318
+
319
+
320
+
321
+
322
+
323
+
324
+
325
+
326
+
327
+ if $0 == __FILE__
328
+ #
329
+ # date
330
+ #
331
+ date = %q( ruby -e" t = Time.now; STDOUT.puts t; STDERR.puts t " )
332
+
333
+ status, stdout, stderr = systemu date
334
+ p [status, stdout, stderr]
335
+
336
+ status = systemu date, 1=>(stdout = '')
337
+ p [status, stdout]
338
+
339
+ status = systemu date, 2=>(stderr = '')
340
+ p [status, stderr]
341
+ #
342
+ # sleep
343
+ #
344
+ sleep = %q( ruby -e" p(sleep(1)) " )
345
+ status, stdout, stderr = systemu sleep
346
+ p [status, stdout, stderr]
347
+
348
+ sleep = %q( ruby -e" p(sleep(42)) " )
349
+ status, stdout, stderr = systemu(sleep){|cid| Process.kill 9, cid}
350
+ p [status, stdout, stderr]
351
+ #
352
+ # env
353
+ #
354
+ env = %q( ruby -e" p ENV['A'] " )
355
+ status, stdout, stderr = systemu env, :env => {'A' => 42}
356
+ p [status, stdout, stderr]
357
+ #
358
+ # cwd
359
+ #
360
+ env = %q( ruby -e" p Dir.pwd " )
361
+ status, stdout, stderr = systemu env, :cwd => Dir.tmpdir
362
+ p [status, stdout, stderr]
363
+ end
@@ -0,0 +1,45 @@
1
+ ## systemu.gemspec
2
+ #
3
+
4
+ Gem::Specification::new do |spec|
5
+ spec.name = "systemu"
6
+ spec.version = "2.5.0"
7
+ spec.platform = Gem::Platform::RUBY
8
+ spec.summary = "systemu"
9
+ spec.description = "description: systemu kicks the ass"
10
+
11
+ spec.files =
12
+ ["LICENSE",
13
+ "README",
14
+ "README.erb",
15
+ "Rakefile",
16
+ "lib",
17
+ "lib/systemu.rb",
18
+ "samples",
19
+ "samples/a.rb",
20
+ "samples/b.rb",
21
+ "samples/c.rb",
22
+ "samples/d.rb",
23
+ "samples/e.rb",
24
+ "samples/f.rb",
25
+ "systemu.gemspec",
26
+ "test",
27
+ "test/systemu_test.rb",
28
+ "test/testing.rb"]
29
+
30
+ spec.executables = []
31
+
32
+ spec.require_path = "lib"
33
+
34
+ spec.test_files = nil
35
+
36
+ ### spec.add_dependency 'lib', '>= version'
37
+ #### spec.add_dependency 'map'
38
+
39
+ spec.extensions.push(*[])
40
+
41
+ spec.rubyforge_project = "codeforpeople"
42
+ spec.author = "Ara T. Howard"
43
+ spec.email = "ara.t.howard@gmail.com"
44
+ spec.homepage = "https://github.com/ahoward/systemu"
45
+ end
data/spec/spec_helper.rb CHANGED
@@ -36,19 +36,6 @@ module EY
36
36
  old_info(*args)
37
37
  end
38
38
  end
39
-
40
- def logged_system(cmd)
41
- output = `#{cmd} 2>&1`
42
- successful = ($? == 0)
43
- if ENV['VERBOSE']
44
- if successful
45
- $stdout.puts "#{cmd}\n#{output.strip}".chomp
46
- else
47
- $stderr.puts "\nCommand `#{cmd}` exited with status #{$?.exitstatus}: '#{output.strip}'"
48
- end
49
- end
50
- successful
51
- end
52
39
  end
53
40
 
54
41
  class Strategies::Git
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: engineyard-serverside
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.33
5
- prerelease:
4
+ version: 1.5.35.pre.1
5
+ prerelease: 7
6
6
  platform: ruby
7
7
  authors:
8
8
  - EY Cloud Team
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-03-19 00:00:00.000000000 Z
12
+ date: 2012-03-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -279,7 +279,9 @@ files:
279
279
  - lib/vendor/json_pure/tools/fuzz.rb
280
280
  - lib/vendor/json_pure/tools/server.rb
281
281
  - lib/vendor/json_pure/VERSION
282
- - lib/vendor/open4/lib/open4.rb
282
+ - lib/vendor/systemu/lib/systemu.rb
283
+ - lib/vendor/systemu/LICENSE
284
+ - lib/vendor/systemu/systemu.gemspec
283
285
  - lib/vendor/thor/bin/rake2thor
284
286
  - lib/vendor/thor/bin/thor
285
287
  - lib/vendor/thor/CHANGELOG.rdoc
@@ -321,7 +323,6 @@ files:
321
323
  - spec/deploy_hook_spec.rb
322
324
  - spec/deprecation_spec.rb
323
325
  - spec/fixtures/gemfiles/1.0.21-rails-31-with-sqlite
324
- - spec/fixtures/gitrepo/bar
325
326
  - spec/fixtures/gitrepo/foo
326
327
  - spec/fixtures/gitrepo.tar.gz
327
328
  - spec/fixtures/invalid_hook.rb
@@ -363,9 +364,6 @@ required_ruby_version: !ruby/object:Gem::Requirement
363
364
  - - ! '>='
364
365
  - !ruby/object:Gem::Version
365
366
  version: '0'
366
- segments:
367
- - 0
368
- hash: -3202821505931209678
369
367
  required_rubygems_version: !ruby/object:Gem::Requirement
370
368
  none: false
371
369
  requirements:
@@ -385,7 +383,6 @@ test_files:
385
383
  - spec/deploy_hook_spec.rb
386
384
  - spec/deprecation_spec.rb
387
385
  - spec/fixtures/gemfiles/1.0.21-rails-31-with-sqlite
388
- - spec/fixtures/gitrepo/bar
389
386
  - spec/fixtures/gitrepo/foo
390
387
  - spec/fixtures/gitrepo.tar.gz
391
388
  - spec/fixtures/invalid_hook.rb
@@ -1,403 +0,0 @@
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.1.0'
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
- elsif dst.respond_to?(:<<)
204
- lambda{|buf| dst << buf }
205
- else
206
- lambda{|buf| buf }
207
- end
208
-
209
- unless src.nil?
210
- if src.respond_to? :gets
211
- while buf = to(t){ src.gets }
212
- send_dst[buf]
213
- end
214
-
215
- elsif src.respond_to? :each
216
- q = Queue.new
217
- th = nil
218
-
219
- timer_set = lambda do |t|
220
- th = new_thread{ to(t){ q.pop } }
221
- end
222
-
223
- timer_cancel = lambda do |t|
224
- th.kill if th rescue nil
225
- end
226
-
227
- timer_set[t]
228
- begin
229
- src.each do |buf|
230
- timer_cancel[t]
231
- send_dst[buf]
232
- timer_set[t]
233
- end
234
- ensure
235
- timer_cancel[t]
236
- end
237
-
238
- elsif src.respond_to? :read
239
- buf = to(t){ src.read }
240
- send_dst[buf]
241
-
242
- else
243
- buf = to(t){ src.to_s }
244
- send_dst[buf]
245
- end
246
- end
247
- #--}}}
248
- end
249
- module_function :relay
250
-
251
- def spawn arg, *argv
252
- #--{{{
253
- argv.unshift(arg)
254
- opts = ((argv.size > 1 and Hash === argv.last) ? argv.pop : {})
255
- argv.flatten!
256
- cmd = argv.join(' ')
257
-
258
-
259
- getopt = getopts opts
260
-
261
- ignore_exit_failure = getopt[ 'ignore_exit_failure', getopt['quiet', false] ]
262
- ignore_exec_failure = getopt[ 'ignore_exec_failure', !getopt['raise', true] ]
263
- exitstatus = getopt[ %w( exitstatus exit_status status ) ]
264
- stdin = getopt[ %w( stdin in i 0 ) << 0 ]
265
- stdout = getopt[ %w( stdout out o 1 ) << 1 ]
266
- stderr = getopt[ %w( stderr err e 2 ) << 2 ]
267
- pid = getopt[ 'pid' ]
268
- timeout = getopt[ %w( timeout spawn_timeout ) ]
269
- stdin_timeout = getopt[ %w( stdin_timeout ) ]
270
- stdout_timeout = getopt[ %w( stdout_timeout io_timeout ) ]
271
- stderr_timeout = getopt[ %w( stderr_timeout ) ]
272
- status = getopt[ %w( status ) ]
273
- cwd = getopt[ %w( cwd dir ) ]
274
-
275
- exitstatus =
276
- case exitstatus
277
- when TrueClass, FalseClass
278
- ignore_exit_failure = true if exitstatus
279
- [0]
280
- else
281
- [*(exitstatus || 0)].map{|i| Integer i}
282
- end
283
-
284
- stdin ||= '' if stdin_timeout
285
- stdout ||= '' if stdout_timeout
286
- stderr ||= '' if stderr_timeout
287
-
288
- started = false
289
-
290
- status =
291
- begin
292
- chdir(cwd) do
293
- Timeout::timeout(timeout) do
294
- popen4(*argv) do |c, i, o, e|
295
- started = true
296
-
297
- %w( replace pid= << push update ).each do |msg|
298
- break(pid.send(msg, c)) if pid.respond_to? msg
299
- end
300
-
301
- te = ThreadEnsemble.new c
302
-
303
- te.add_thread(i, stdin) do |i, stdin|
304
- relay stdin, i, stdin_timeout
305
- i.close rescue nil
306
- end
307
-
308
- te.add_thread(o, stdout) do |o, stdout|
309
- relay o, stdout, stdout_timeout
310
- end
311
-
312
- te.add_thread(e, stderr) do |o, stderr|
313
- relay e, stderr, stderr_timeout
314
- end
315
-
316
- te.run
317
- end
318
- end
319
- end
320
- rescue
321
- raise unless(not started and ignore_exec_failure)
322
- end
323
-
324
- raise SpawnError.new(cmd, status) unless
325
- (ignore_exit_failure or (status.nil? and ignore_exec_failure) or exitstatus.include?(status.exitstatus))
326
-
327
- status
328
- #--}}}
329
- end
330
- module_function :spawn
331
-
332
- def chdir cwd, &block
333
- return(block.call Dir.pwd) unless cwd
334
- Dir.chdir cwd, &block
335
- end
336
- module_function :chdir
337
-
338
- def background arg, *argv
339
- #--{{{
340
- require 'thread'
341
- q = Queue.new
342
- opts = { 'pid' => q, :pid => q }
343
- case argv.last
344
- when Hash
345
- argv.last.update opts
346
- else
347
- argv.push opts
348
- end
349
- thread = Thread.new(arg, argv){|arg, argv| spawn arg, *argv}
350
- sc = class << thread; self; end
351
- sc.module_eval {
352
- define_method(:pid){ @pid ||= q.pop }
353
- define_method(:spawn_status){ @spawn_status ||= value }
354
- define_method(:exitstatus){ @exitstatus ||= spawn_status.exitstatus }
355
- }
356
- thread
357
- #--}}}
358
- end
359
- alias bg background
360
- module_function :background
361
- module_function :bg
362
-
363
- def maim pid, opts = {}
364
- #--{{{
365
- getopt = getopts opts
366
- sigs = getopt[ 'signals', %w(SIGTERM SIGQUIT SIGKILL) ]
367
- suspend = getopt[ 'suspend', 4 ]
368
- pid = Integer pid
369
- existed = false
370
- sigs.each do |sig|
371
- begin
372
- Process.kill sig, pid
373
- existed = true
374
- rescue Errno::ESRCH
375
- return(existed ? nil : true)
376
- end
377
- return true unless alive? pid
378
- sleep suspend
379
- return true unless alive? pid
380
- end
381
- return(not alive?(pid))
382
- #--}}}
383
- end
384
- module_function :maim
385
-
386
- def alive pid
387
- #--{{{
388
- pid = Integer pid
389
- begin
390
- Process.kill 0, pid
391
- true
392
- rescue Errno::ESRCH
393
- false
394
- end
395
- #--}}}
396
- end
397
- alias alive? alive
398
- module_function :alive
399
- module_function :'alive?'
400
- #--}}}
401
- end
402
-
403
- def open4(*cmd, &b) cmd.size == 0 ? Open4 : Open4::popen4(*cmd, &b) end
File without changes