engineyard-serverside 1.5.23.ruby19.8 → 1.5.23.ruby19.9
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/lib/engineyard-serverside.rb +3 -1
- data/lib/engineyard-serverside/cli.rb +11 -19
- data/lib/engineyard-serverside/deploy.rb +3 -3
- data/lib/engineyard-serverside/future.rb +33 -0
- data/lib/engineyard-serverside/futures/celluloid.rb +25 -0
- data/lib/engineyard-serverside/futures/dataflow.rb +25 -0
- data/lib/engineyard-serverside/logged_output.rb +8 -3
- data/lib/engineyard-serverside/task.rb +9 -12
- data/lib/engineyard-serverside/version.rb +1 -1
- data/lib/vendor/celluloid/lib/celluloid.rb +261 -0
- data/lib/vendor/celluloid/lib/celluloid/actor.rb +242 -0
- data/lib/vendor/celluloid/lib/celluloid/actor_pool.rb +54 -0
- data/lib/vendor/celluloid/lib/celluloid/actor_proxy.rb +75 -0
- data/lib/vendor/celluloid/lib/celluloid/application.rb +78 -0
- data/lib/vendor/celluloid/lib/celluloid/calls.rb +94 -0
- data/lib/vendor/celluloid/lib/celluloid/core_ext.rb +14 -0
- data/lib/vendor/celluloid/lib/celluloid/events.rb +14 -0
- data/lib/vendor/celluloid/lib/celluloid/fiber.rb +33 -0
- data/lib/vendor/celluloid/lib/celluloid/fsm.rb +141 -0
- data/lib/vendor/celluloid/lib/celluloid/future.rb +60 -0
- data/lib/vendor/celluloid/lib/celluloid/links.rb +61 -0
- data/lib/vendor/celluloid/lib/celluloid/logger.rb +32 -0
- data/lib/vendor/celluloid/lib/celluloid/mailbox.rb +124 -0
- data/lib/vendor/celluloid/lib/celluloid/receivers.rb +66 -0
- data/lib/vendor/celluloid/lib/celluloid/registry.rb +33 -0
- data/lib/vendor/celluloid/lib/celluloid/responses.rb +26 -0
- data/lib/vendor/celluloid/lib/celluloid/rspec.rb +2 -0
- data/lib/vendor/celluloid/lib/celluloid/signals.rb +50 -0
- data/lib/vendor/celluloid/lib/celluloid/supervisor.rb +57 -0
- data/lib/vendor/celluloid/lib/celluloid/task.rb +73 -0
- data/lib/vendor/celluloid/lib/celluloid/tcp_server.rb +33 -0
- data/lib/vendor/celluloid/lib/celluloid/timers.rb +109 -0
- data/lib/vendor/celluloid/lib/celluloid/version.rb +4 -0
- data/lib/vendor/dataflow/dataflow.rb +124 -0
- data/lib/vendor/dataflow/dataflow/actor.rb +22 -0
- data/lib/vendor/dataflow/dataflow/equality.rb +44 -0
- data/lib/vendor/dataflow/dataflow/future_queue.rb +24 -0
- data/lib/vendor/dataflow/dataflow/port.rb +54 -0
- data/lib/vendor/open4/lib/open4.rb +432 -0
- data/lib/vendor/thor/lib/thor.rb +244 -0
- data/lib/vendor/thor/lib/thor/actions.rb +275 -0
- data/lib/vendor/thor/lib/thor/actions/create_file.rb +103 -0
- data/lib/vendor/thor/lib/thor/actions/directory.rb +91 -0
- data/lib/vendor/thor/lib/thor/actions/empty_directory.rb +134 -0
- data/lib/vendor/thor/lib/thor/actions/file_manipulation.rb +223 -0
- data/lib/vendor/thor/lib/thor/actions/inject_into_file.rb +104 -0
- data/lib/vendor/thor/lib/thor/base.rb +540 -0
- data/lib/vendor/thor/lib/thor/core_ext/file_binary_read.rb +9 -0
- data/lib/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb +75 -0
- data/lib/vendor/thor/lib/thor/core_ext/ordered_hash.rb +100 -0
- data/lib/vendor/thor/lib/thor/error.rb +30 -0
- data/lib/vendor/thor/lib/thor/group.rb +271 -0
- data/lib/vendor/thor/lib/thor/invocation.rb +180 -0
- data/lib/vendor/thor/lib/thor/parser.rb +4 -0
- data/lib/vendor/thor/lib/thor/parser/argument.rb +67 -0
- data/lib/vendor/thor/lib/thor/parser/arguments.rb +150 -0
- data/lib/vendor/thor/lib/thor/parser/option.rb +128 -0
- data/lib/vendor/thor/lib/thor/parser/options.rb +169 -0
- data/lib/vendor/thor/lib/thor/rake_compat.rb +66 -0
- data/lib/vendor/thor/lib/thor/runner.rb +314 -0
- data/lib/vendor/thor/lib/thor/shell.rb +83 -0
- data/lib/vendor/thor/lib/thor/shell/basic.rb +239 -0
- data/lib/vendor/thor/lib/thor/shell/color.rb +108 -0
- data/lib/vendor/thor/lib/thor/task.rb +102 -0
- data/lib/vendor/thor/lib/thor/util.rb +230 -0
- data/lib/vendor/thor/lib/thor/version.rb +3 -0
- metadata +70 -10
@@ -0,0 +1,109 @@
|
|
1
|
+
module Celluloid
|
2
|
+
# Low precision timers implemented in pure Ruby
|
3
|
+
class Timers
|
4
|
+
def initialize
|
5
|
+
@timers = []
|
6
|
+
end
|
7
|
+
|
8
|
+
# Call the given block after the given interval
|
9
|
+
def add(interval, &block)
|
10
|
+
Timer.new(self, interval, block)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Wait for the next timer and fire it
|
14
|
+
def wait
|
15
|
+
return if @timers.empty?
|
16
|
+
|
17
|
+
interval = wait_interval
|
18
|
+
sleep interval if interval >= Timer::QUANTUM
|
19
|
+
fire
|
20
|
+
end
|
21
|
+
|
22
|
+
# Interval to wait until when the next timer will fire
|
23
|
+
def wait_interval
|
24
|
+
@timers.first.time - Time.now unless empty?
|
25
|
+
end
|
26
|
+
|
27
|
+
# Fire all timers that are ready
|
28
|
+
def fire
|
29
|
+
return if @timers.empty?
|
30
|
+
|
31
|
+
time = Time.now + Timer::QUANTUM
|
32
|
+
while not empty? and time > @timers.first.time
|
33
|
+
timer = @timers.shift
|
34
|
+
timer.call
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Insert a timer into the active timers
|
39
|
+
def insert(timer)
|
40
|
+
@timers.insert(index(timer), timer)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Remove a given timer from the set we're monitoring
|
44
|
+
def cancel(timer)
|
45
|
+
@timers.delete timer
|
46
|
+
end
|
47
|
+
|
48
|
+
# Are there any timers pending?
|
49
|
+
def empty?
|
50
|
+
@timers.empty?
|
51
|
+
end
|
52
|
+
|
53
|
+
# Index where a timer would be located in the sorted timers array
|
54
|
+
def index(timer)
|
55
|
+
l, r = 0, @timers.size - 1
|
56
|
+
|
57
|
+
while l <= r
|
58
|
+
m = (r + l) / 2
|
59
|
+
if timer < @timers.at(m)
|
60
|
+
r = m - 1
|
61
|
+
else
|
62
|
+
l = m + 1
|
63
|
+
end
|
64
|
+
end
|
65
|
+
l
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# An individual timer set to fire a given proc at a given time
|
70
|
+
class Timer
|
71
|
+
include Comparable
|
72
|
+
|
73
|
+
# The timer system is guaranteed (at least by the specs) to be this precise
|
74
|
+
# during normal operation. Long blocking calls within actors will delay the
|
75
|
+
# firing of timers
|
76
|
+
QUANTUM = 0.02
|
77
|
+
|
78
|
+
attr_reader :interval, :time
|
79
|
+
|
80
|
+
def initialize(timers, interval, block)
|
81
|
+
@timers, @interval = timers, interval
|
82
|
+
@block = block
|
83
|
+
|
84
|
+
reset
|
85
|
+
end
|
86
|
+
|
87
|
+
def <=>(other)
|
88
|
+
@time <=> other.time
|
89
|
+
end
|
90
|
+
|
91
|
+
# Cancel this timer
|
92
|
+
def cancel
|
93
|
+
@timers.cancel self
|
94
|
+
end
|
95
|
+
|
96
|
+
# Reset this timer
|
97
|
+
def reset
|
98
|
+
@timers.cancel self if defined?(@time)
|
99
|
+
@time = Time.now + @interval
|
100
|
+
@timers.insert self
|
101
|
+
end
|
102
|
+
|
103
|
+
# Fire the block
|
104
|
+
def fire
|
105
|
+
@block.call
|
106
|
+
end
|
107
|
+
alias_method :call, :fire
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'monitor'
|
2
|
+
|
3
|
+
module Dataflow
|
4
|
+
VERSION = "0.3.1"
|
5
|
+
class << self
|
6
|
+
attr_accessor :forker
|
7
|
+
end
|
8
|
+
self.forker = Thread.method(:fork)
|
9
|
+
|
10
|
+
def self.included(cls)
|
11
|
+
class << cls
|
12
|
+
def declare(*readers)
|
13
|
+
readers.each do |name|
|
14
|
+
class_eval <<-RUBY
|
15
|
+
def #{name}
|
16
|
+
return @__dataflow_#{name}__ if defined? @__dataflow_#{name}__
|
17
|
+
Variable::LOCK.synchronize { @__dataflow_#{name}__ ||= Variable.new }
|
18
|
+
end
|
19
|
+
RUBY
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def local(&block)
|
26
|
+
return Variable.new unless block_given?
|
27
|
+
vars = Array.new(block.arity) { Variable.new }
|
28
|
+
block.call *vars
|
29
|
+
end
|
30
|
+
|
31
|
+
def unify(variable, value)
|
32
|
+
variable.__unify__ value
|
33
|
+
end
|
34
|
+
|
35
|
+
def by_need(&block)
|
36
|
+
Variable.new &block
|
37
|
+
end
|
38
|
+
|
39
|
+
def barrier(*variables)
|
40
|
+
variables.each{|v| v.__wait__ }
|
41
|
+
end
|
42
|
+
|
43
|
+
def flow(output=nil, &block)
|
44
|
+
Dataflow.forker.call do
|
45
|
+
result = block.call
|
46
|
+
unify output, result if output
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def need_later(&block)
|
51
|
+
local do |future|
|
52
|
+
flow(future) { block.call }
|
53
|
+
future
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
extend self
|
58
|
+
|
59
|
+
# Note that this class uses instance variables directly rather than nicely
|
60
|
+
# initialized instance variables in get/set methods for memory and
|
61
|
+
# performance reasons
|
62
|
+
class Variable
|
63
|
+
# Briefly disable the warning we would get when undefining object_id.
|
64
|
+
# We actually rely on the ability to do that, so...
|
65
|
+
v = $VERBOSE; $VERBOSE = nil
|
66
|
+
instance_methods.each { |m| undef_method(m) unless m =~ /^__|instance_eval/ }
|
67
|
+
$VERBOSE = v # back to sanity
|
68
|
+
LOCK = Monitor.new
|
69
|
+
def initialize(&block) @__trigger__ = block if block_given? end
|
70
|
+
|
71
|
+
# Lazy-load conditions to be nice on memory usage
|
72
|
+
def __binding_condition__() @__binding_condition__ ||= LOCK.new_cond end
|
73
|
+
|
74
|
+
def __unify__(value)
|
75
|
+
LOCK.synchronize do
|
76
|
+
__activate_trigger__ if @__trigger__
|
77
|
+
if @__bound__
|
78
|
+
return @__value__.__unify__(value) if @__value__.__dataflow__? rescue nil
|
79
|
+
raise UnificationError, "#{@__value__.inspect} != #{value.inspect}" if self != value
|
80
|
+
else
|
81
|
+
@__value__ = value
|
82
|
+
@__bound__ = true
|
83
|
+
__binding_condition__.broadcast # wakeup all method callers
|
84
|
+
@__binding_condition__ = nil # GC
|
85
|
+
end
|
86
|
+
end
|
87
|
+
@__value__
|
88
|
+
end
|
89
|
+
|
90
|
+
def __activate_trigger__
|
91
|
+
@__value__ = @__trigger__.call
|
92
|
+
@__bound__ = true
|
93
|
+
@__trigger__ = nil # GC
|
94
|
+
end
|
95
|
+
|
96
|
+
def __wait__
|
97
|
+
LOCK.synchronize do
|
98
|
+
unless @__bound__
|
99
|
+
if @__trigger__
|
100
|
+
__activate_trigger__
|
101
|
+
else
|
102
|
+
__binding_condition__.wait
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end unless @__bound__
|
106
|
+
end
|
107
|
+
|
108
|
+
def method_missing(name, *args, &block)
|
109
|
+
return "#<Dataflow::Variable:#{__id__} unbound>" if !@__bound__ && name == :inspect
|
110
|
+
__wait__
|
111
|
+
@__value__.__send__(name, *args, &block)
|
112
|
+
end
|
113
|
+
|
114
|
+
def __dataflow__?
|
115
|
+
true
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
UnificationError = Class.new StandardError
|
120
|
+
end
|
121
|
+
|
122
|
+
require "#{File.dirname(__FILE__)}/dataflow/port"
|
123
|
+
require "#{File.dirname(__FILE__)}/dataflow/actor"
|
124
|
+
require "#{File.dirname(__FILE__)}/dataflow/future_queue"
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Dataflow
|
2
|
+
class Actor < Thread
|
3
|
+
def initialize(&block)
|
4
|
+
@stream = Variable.new
|
5
|
+
@port = Port.new(@stream)
|
6
|
+
# Run this block in a new thread
|
7
|
+
super { instance_eval &block }
|
8
|
+
end
|
9
|
+
|
10
|
+
def send message
|
11
|
+
@port.send message
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def receive
|
17
|
+
result = @stream.head
|
18
|
+
@stream = @stream.tail
|
19
|
+
result
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# The purpose of this file is to make equality use method calls in as
|
2
|
+
# many Ruby implementations as possible. If method calls are used,
|
3
|
+
# then equality operations using dataflow variaables becomes seemless
|
4
|
+
# I realize overriding core classes is a pretty nasty hack, but if you
|
5
|
+
# have a better idea that also passes the equality_specs then I'm all
|
6
|
+
# ears. Please run the rubyspec before committing changes to this file.
|
7
|
+
|
8
|
+
class Object
|
9
|
+
alias original_equality ==
|
10
|
+
|
11
|
+
def ==(other)
|
12
|
+
object_id == other.object_id
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Symbol
|
17
|
+
alias original_equality ==
|
18
|
+
|
19
|
+
def ==(other)
|
20
|
+
object_id == other.object_id
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Regexp
|
25
|
+
alias original_equality ==
|
26
|
+
|
27
|
+
if /lol/.respond_to?(:encoding)
|
28
|
+
def ==(other)
|
29
|
+
other.is_a?(Regexp) &&
|
30
|
+
casefold? == other.casefold? &&
|
31
|
+
encoding == other.encoding &&
|
32
|
+
options == other.options &&
|
33
|
+
source == other.source
|
34
|
+
end
|
35
|
+
else
|
36
|
+
def ==(other)
|
37
|
+
other.is_a?(Regexp) &&
|
38
|
+
casefold? == other.casefold? &&
|
39
|
+
kcode == other.kcode &&
|
40
|
+
options == other.options &&
|
41
|
+
source == other.source
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Dataflow
|
2
|
+
class FutureQueue
|
3
|
+
include Dataflow
|
4
|
+
declare :push_port, :pop_port
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
local do |pushed, popped|
|
8
|
+
unify push_port, Dataflow::Port.new(pushed)
|
9
|
+
unify pop_port, Dataflow::Port.new(popped)
|
10
|
+
|
11
|
+
Thread.new {
|
12
|
+
loop do
|
13
|
+
barrier pushed.head, popped.head
|
14
|
+
unify popped.head, pushed.head
|
15
|
+
pushed, popped = pushed.tail, popped.tail
|
16
|
+
end
|
17
|
+
}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def push(x) push_port.send x end
|
22
|
+
def pop(x) pop_port.send x end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Dataflow
|
4
|
+
class Port
|
5
|
+
include Dataflow
|
6
|
+
LOCK = Mutex.new
|
7
|
+
|
8
|
+
class Stream
|
9
|
+
include Dataflow
|
10
|
+
declare :head, :tail
|
11
|
+
|
12
|
+
# Defining each allows us to use the enumerable mixin
|
13
|
+
# None of the list can be garbage collected less the head is
|
14
|
+
# garbage collected, so it will grow indefinitely even though
|
15
|
+
# the function isn't recursive.
|
16
|
+
include Enumerable
|
17
|
+
def each
|
18
|
+
s = self
|
19
|
+
loop do
|
20
|
+
yield s.head
|
21
|
+
s = s.tail
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Backported Enumerable#take for any 1.8.6 compatible Ruby
|
26
|
+
unless method_defined?(:take)
|
27
|
+
def take(num)
|
28
|
+
result = []
|
29
|
+
each_with_index do |x, i|
|
30
|
+
return result if num == i
|
31
|
+
result << x
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Create a stream object, bind it to the input variable
|
38
|
+
# Instance variables are necessary because @end is state
|
39
|
+
def initialize(x)
|
40
|
+
@end = Stream.new
|
41
|
+
unify x, @end
|
42
|
+
end
|
43
|
+
|
44
|
+
# This needs to be synchronized because it uses @end as state
|
45
|
+
def send value
|
46
|
+
LOCK.synchronize do
|
47
|
+
unify @end.head, value
|
48
|
+
unify @end.tail, Stream.new
|
49
|
+
@end = @end.tail
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
@@ -0,0 +1,432 @@
|
|
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
|
+
VERSION = '1.3.0'
|
8
|
+
def self.version() VERSION end
|
9
|
+
|
10
|
+
class Error < ::StandardError; end
|
11
|
+
|
12
|
+
def pfork4(fun, &b)
|
13
|
+
Open4.do_popen(b, :block) do |ps_read, _|
|
14
|
+
ps_read.close
|
15
|
+
begin
|
16
|
+
fun.call
|
17
|
+
rescue SystemExit => e
|
18
|
+
# Make it seem to the caller that calling Kernel#exit in +fun+ kills
|
19
|
+
# the child process normally. Kernel#exit! bypasses this rescue
|
20
|
+
# block.
|
21
|
+
exit! e.status
|
22
|
+
else
|
23
|
+
exit! 0
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
module_function :pfork4
|
28
|
+
|
29
|
+
def popen4(*cmd, &b)
|
30
|
+
Open4.do_popen(b, :init) do |ps_read, ps_write|
|
31
|
+
ps_read.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
32
|
+
ps_write.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
33
|
+
exec(*cmd)
|
34
|
+
raise 'forty-two' # Is this really needed?
|
35
|
+
end
|
36
|
+
end
|
37
|
+
alias open4 popen4
|
38
|
+
module_function :popen4
|
39
|
+
module_function :open4
|
40
|
+
|
41
|
+
def popen4ext(closefds=false, *cmd, &b)
|
42
|
+
Open4.do_popen(b, :init, closefds) do |ps_read, ps_write|
|
43
|
+
ps_read.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
44
|
+
ps_write.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
45
|
+
exec(*cmd)
|
46
|
+
raise 'forty-two' # Is this really needed?
|
47
|
+
end
|
48
|
+
end
|
49
|
+
module_function :popen4ext
|
50
|
+
|
51
|
+
def self.do_popen(b = nil, exception_propagation_at = nil, closefds=false, &cmd)
|
52
|
+
pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe
|
53
|
+
|
54
|
+
verbose = $VERBOSE
|
55
|
+
begin
|
56
|
+
$VERBOSE = nil
|
57
|
+
|
58
|
+
cid = fork {
|
59
|
+
if closefds
|
60
|
+
exlist = [0, 1, 2] | [pw,pr,pe,ps].map{|p| [p.first.fileno, p.last.fileno] }.flatten
|
61
|
+
ObjectSpace.each_object(IO){|io|
|
62
|
+
io.close if (not io.closed?) and (not exlist.include? io.fileno)
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
pw.last.close
|
67
|
+
STDIN.reopen pw.first
|
68
|
+
pw.first.close
|
69
|
+
|
70
|
+
pr.first.close
|
71
|
+
STDOUT.reopen pr.last
|
72
|
+
pr.last.close
|
73
|
+
|
74
|
+
pe.first.close
|
75
|
+
STDERR.reopen pe.last
|
76
|
+
pe.last.close
|
77
|
+
|
78
|
+
STDOUT.sync = STDERR.sync = true
|
79
|
+
|
80
|
+
begin
|
81
|
+
cmd.call(ps)
|
82
|
+
rescue Exception => e
|
83
|
+
Marshal.dump(e, ps.last)
|
84
|
+
ps.last.flush
|
85
|
+
ensure
|
86
|
+
ps.last.close unless ps.last.closed?
|
87
|
+
end
|
88
|
+
|
89
|
+
exit!
|
90
|
+
}
|
91
|
+
ensure
|
92
|
+
$VERBOSE = verbose
|
93
|
+
end
|
94
|
+
|
95
|
+
[ pw.first, pr.last, pe.last, ps.last ].each { |fd| fd.close }
|
96
|
+
|
97
|
+
Open4.propagate_exception cid, ps.first if exception_propagation_at == :init
|
98
|
+
|
99
|
+
pw.last.sync = true
|
100
|
+
|
101
|
+
pi = [ pw.last, pr.first, pe.first ]
|
102
|
+
|
103
|
+
begin
|
104
|
+
return [cid, *pi] unless b
|
105
|
+
|
106
|
+
begin
|
107
|
+
b.call(cid, *pi)
|
108
|
+
ensure
|
109
|
+
pi.each { |fd| fd.close unless fd.closed? }
|
110
|
+
end
|
111
|
+
|
112
|
+
Open4.propagate_exception cid, ps.first if exception_propagation_at == :block
|
113
|
+
|
114
|
+
Process.waitpid2(cid).last
|
115
|
+
ensure
|
116
|
+
ps.first.close unless ps.first.closed?
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.propagate_exception(cid, ps_read)
|
121
|
+
e = Marshal.load ps_read
|
122
|
+
raise Exception === e ? e : "unknown failure!"
|
123
|
+
rescue EOFError
|
124
|
+
# Child process did not raise exception.
|
125
|
+
rescue
|
126
|
+
# Child process raised exception; wait it in order to avoid a zombie.
|
127
|
+
Process.waitpid2 cid
|
128
|
+
raise
|
129
|
+
ensure
|
130
|
+
ps_read.close
|
131
|
+
end
|
132
|
+
|
133
|
+
class SpawnError < Error
|
134
|
+
attr 'cmd'
|
135
|
+
attr 'status'
|
136
|
+
attr 'signals'
|
137
|
+
def exitstatus
|
138
|
+
@status.exitstatus
|
139
|
+
end
|
140
|
+
def initialize cmd, status
|
141
|
+
@cmd, @status = cmd, status
|
142
|
+
@signals = {}
|
143
|
+
if status.signaled?
|
144
|
+
@signals['termsig'] = status.termsig
|
145
|
+
@signals['stopsig'] = status.stopsig
|
146
|
+
end
|
147
|
+
sigs = @signals.map{|k,v| "#{ k }:#{ v.inspect }"}.join(' ')
|
148
|
+
super "cmd <#{ cmd }> failed with status <#{ exitstatus.inspect }> signals <#{ sigs }>"
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
class ThreadEnsemble
|
153
|
+
attr 'threads'
|
154
|
+
|
155
|
+
def initialize cid
|
156
|
+
@cid, @threads, @argv, @done, @running = cid, [], [], Queue.new, false
|
157
|
+
@killed = false
|
158
|
+
end
|
159
|
+
|
160
|
+
def add_thread *a, &b
|
161
|
+
@running ? raise : (@argv << [a, b])
|
162
|
+
end
|
163
|
+
|
164
|
+
#
|
165
|
+
# take down process more nicely
|
166
|
+
#
|
167
|
+
def killall
|
168
|
+
c = Thread.critical
|
169
|
+
return nil if @killed
|
170
|
+
Thread.critical = true
|
171
|
+
(@threads - [Thread.current]).each{|t| t.kill rescue nil}
|
172
|
+
@killed = true
|
173
|
+
ensure
|
174
|
+
Thread.critical = c
|
175
|
+
end
|
176
|
+
|
177
|
+
def run
|
178
|
+
@running = true
|
179
|
+
|
180
|
+
begin
|
181
|
+
@argv.each do |a, b|
|
182
|
+
@threads << Thread.new(*a) do |*a|
|
183
|
+
begin
|
184
|
+
b[*a]
|
185
|
+
ensure
|
186
|
+
killall rescue nil if $!
|
187
|
+
@done.push Thread.current
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
rescue
|
192
|
+
killall
|
193
|
+
raise
|
194
|
+
ensure
|
195
|
+
all_done
|
196
|
+
end
|
197
|
+
|
198
|
+
@threads.map{|t| t.value}
|
199
|
+
end
|
200
|
+
|
201
|
+
def all_done
|
202
|
+
@threads.size.times{ @done.pop }
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def to timeout = nil
|
207
|
+
Timeout.timeout(timeout){ yield }
|
208
|
+
end
|
209
|
+
module_function :to
|
210
|
+
|
211
|
+
def new_thread *a, &b
|
212
|
+
cur = Thread.current
|
213
|
+
Thread.new(*a) do |*a|
|
214
|
+
begin
|
215
|
+
b[*a]
|
216
|
+
rescue Exception => e
|
217
|
+
cur.raise e
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
module_function :new_thread
|
222
|
+
|
223
|
+
def getopts opts = {}
|
224
|
+
lambda do |*args|
|
225
|
+
keys, default, ignored = args
|
226
|
+
catch(:opt) do
|
227
|
+
[keys].flatten.each do |key|
|
228
|
+
[key, key.to_s, key.to_s.intern].each do |key|
|
229
|
+
throw :opt, opts[key] if opts.has_key?(key)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
default
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
module_function :getopts
|
237
|
+
|
238
|
+
def relay src, dst = nil, t = nil
|
239
|
+
send_dst =
|
240
|
+
if dst.respond_to?(:call)
|
241
|
+
lambda{|buf| dst.call(buf)}
|
242
|
+
elsif dst.respond_to?(:<<)
|
243
|
+
lambda{|buf| dst << buf }
|
244
|
+
else
|
245
|
+
lambda{|buf| buf }
|
246
|
+
end
|
247
|
+
|
248
|
+
unless src.nil?
|
249
|
+
if src.respond_to? :gets
|
250
|
+
while buf = to(t){ src.gets }
|
251
|
+
send_dst[buf]
|
252
|
+
end
|
253
|
+
|
254
|
+
elsif src.respond_to? :each
|
255
|
+
q = Queue.new
|
256
|
+
th = nil
|
257
|
+
|
258
|
+
timer_set = lambda do |t|
|
259
|
+
th = new_thread{ to(t){ q.pop } }
|
260
|
+
end
|
261
|
+
|
262
|
+
timer_cancel = lambda do |t|
|
263
|
+
th.kill if th rescue nil
|
264
|
+
end
|
265
|
+
|
266
|
+
timer_set[t]
|
267
|
+
begin
|
268
|
+
src.each do |buf|
|
269
|
+
timer_cancel[t]
|
270
|
+
send_dst[buf]
|
271
|
+
timer_set[t]
|
272
|
+
end
|
273
|
+
ensure
|
274
|
+
timer_cancel[t]
|
275
|
+
end
|
276
|
+
|
277
|
+
elsif src.respond_to? :read
|
278
|
+
buf = to(t){ src.read }
|
279
|
+
send_dst[buf]
|
280
|
+
|
281
|
+
else
|
282
|
+
buf = to(t){ src.to_s }
|
283
|
+
send_dst[buf]
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
module_function :relay
|
288
|
+
|
289
|
+
def spawn arg, *argv
|
290
|
+
argv.unshift(arg)
|
291
|
+
opts = ((argv.size > 1 and Hash === argv.last) ? argv.pop : {})
|
292
|
+
argv.flatten!
|
293
|
+
cmd = argv.join(' ')
|
294
|
+
|
295
|
+
|
296
|
+
getopt = getopts opts
|
297
|
+
|
298
|
+
ignore_exit_failure = getopt[ 'ignore_exit_failure', getopt['quiet', false] ]
|
299
|
+
ignore_exec_failure = getopt[ 'ignore_exec_failure', !getopt['raise', true] ]
|
300
|
+
exitstatus = getopt[ %w( exitstatus exit_status status ) ]
|
301
|
+
stdin = getopt[ %w( stdin in i 0 ) << 0 ]
|
302
|
+
stdout = getopt[ %w( stdout out o 1 ) << 1 ]
|
303
|
+
stderr = getopt[ %w( stderr err e 2 ) << 2 ]
|
304
|
+
pid = getopt[ 'pid' ]
|
305
|
+
timeout = getopt[ %w( timeout spawn_timeout ) ]
|
306
|
+
stdin_timeout = getopt[ %w( stdin_timeout ) ]
|
307
|
+
stdout_timeout = getopt[ %w( stdout_timeout io_timeout ) ]
|
308
|
+
stderr_timeout = getopt[ %w( stderr_timeout ) ]
|
309
|
+
status = getopt[ %w( status ) ]
|
310
|
+
cwd = getopt[ %w( cwd dir ) ]
|
311
|
+
|
312
|
+
exitstatus =
|
313
|
+
case exitstatus
|
314
|
+
when TrueClass, FalseClass
|
315
|
+
ignore_exit_failure = true if exitstatus
|
316
|
+
[0]
|
317
|
+
else
|
318
|
+
[*(exitstatus || 0)].map{|i| Integer i}
|
319
|
+
end
|
320
|
+
|
321
|
+
stdin ||= '' if stdin_timeout
|
322
|
+
stdout ||= '' if stdout_timeout
|
323
|
+
stderr ||= '' if stderr_timeout
|
324
|
+
|
325
|
+
started = false
|
326
|
+
|
327
|
+
status =
|
328
|
+
begin
|
329
|
+
chdir(cwd) do
|
330
|
+
Timeout::timeout(timeout) do
|
331
|
+
popen4(*argv) do |c, i, o, e|
|
332
|
+
started = true
|
333
|
+
|
334
|
+
%w( replace pid= << push update ).each do |msg|
|
335
|
+
break(pid.send(msg, c)) if pid.respond_to? msg
|
336
|
+
end
|
337
|
+
|
338
|
+
te = ThreadEnsemble.new c
|
339
|
+
|
340
|
+
te.add_thread(i, stdin) do |i, stdin|
|
341
|
+
relay stdin, i, stdin_timeout
|
342
|
+
i.close rescue nil
|
343
|
+
end
|
344
|
+
|
345
|
+
te.add_thread(o, stdout) do |o, stdout|
|
346
|
+
relay o, stdout, stdout_timeout
|
347
|
+
end
|
348
|
+
|
349
|
+
te.add_thread(e, stderr) do |o, stderr|
|
350
|
+
relay e, stderr, stderr_timeout
|
351
|
+
end
|
352
|
+
|
353
|
+
te.run
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
rescue
|
358
|
+
raise unless(not started and ignore_exec_failure)
|
359
|
+
end
|
360
|
+
|
361
|
+
raise SpawnError.new(cmd, status) unless
|
362
|
+
(ignore_exit_failure or (status.nil? and ignore_exec_failure) or exitstatus.include?(status.exitstatus))
|
363
|
+
|
364
|
+
status
|
365
|
+
end
|
366
|
+
module_function :spawn
|
367
|
+
|
368
|
+
def chdir cwd, &block
|
369
|
+
return(block.call Dir.pwd) unless cwd
|
370
|
+
Dir.chdir cwd, &block
|
371
|
+
end
|
372
|
+
module_function :chdir
|
373
|
+
|
374
|
+
def background arg, *argv
|
375
|
+
require 'thread'
|
376
|
+
q = Queue.new
|
377
|
+
opts = { 'pid' => q, :pid => q }
|
378
|
+
case argv.last
|
379
|
+
when Hash
|
380
|
+
argv.last.update opts
|
381
|
+
else
|
382
|
+
argv.push opts
|
383
|
+
end
|
384
|
+
thread = Thread.new(arg, argv){|arg, argv| spawn arg, *argv}
|
385
|
+
sc = class << thread; self; end
|
386
|
+
sc.module_eval {
|
387
|
+
define_method(:pid){ @pid ||= q.pop }
|
388
|
+
define_method(:spawn_status){ @spawn_status ||= value }
|
389
|
+
define_method(:exitstatus){ @exitstatus ||= spawn_status.exitstatus }
|
390
|
+
}
|
391
|
+
thread
|
392
|
+
end
|
393
|
+
alias bg background
|
394
|
+
module_function :background
|
395
|
+
module_function :bg
|
396
|
+
|
397
|
+
def maim pid, opts = {}
|
398
|
+
getopt = getopts opts
|
399
|
+
sigs = getopt[ 'signals', %w(SIGTERM SIGQUIT SIGKILL) ]
|
400
|
+
suspend = getopt[ 'suspend', 4 ]
|
401
|
+
pid = Integer pid
|
402
|
+
existed = false
|
403
|
+
sigs.each do |sig|
|
404
|
+
begin
|
405
|
+
Process.kill sig, pid
|
406
|
+
existed = true
|
407
|
+
rescue Errno::ESRCH
|
408
|
+
return(existed ? nil : true)
|
409
|
+
end
|
410
|
+
return true unless alive? pid
|
411
|
+
sleep suspend
|
412
|
+
return true unless alive? pid
|
413
|
+
end
|
414
|
+
return(not alive?(pid))
|
415
|
+
end
|
416
|
+
module_function :maim
|
417
|
+
|
418
|
+
def alive pid
|
419
|
+
pid = Integer pid
|
420
|
+
begin
|
421
|
+
Process.kill 0, pid
|
422
|
+
true
|
423
|
+
rescue Errno::ESRCH
|
424
|
+
false
|
425
|
+
end
|
426
|
+
end
|
427
|
+
alias alive? alive
|
428
|
+
module_function :alive
|
429
|
+
module_function :'alive?'
|
430
|
+
end
|
431
|
+
|
432
|
+
def open4(*cmd, &b) cmd.size == 0 ? Open4 : Open4::popen4(*cmd, &b) end
|