ey-deploy 0.7.0 → 0.7.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.
- data/lib/ey-deploy.rb +4 -0
- data/lib/ey-deploy/cli.rb +27 -16
- data/lib/ey-deploy/deploy.rb +13 -13
- data/lib/ey-deploy/deploy_hook.rb +13 -6
- data/lib/ey-deploy/logged_output.rb +58 -0
- data/lib/ey-deploy/server.rb +5 -5
- data/lib/ey-deploy/strategies/git.rb +5 -5
- data/lib/ey-deploy/task.rb +6 -12
- data/lib/ey-deploy/version.rb +1 -1
- data/lib/vendor/dataflow/HISTORY +52 -0
- data/lib/vendor/dataflow/LICENSE +19 -0
- data/lib/vendor/dataflow/README.textile +290 -0
- data/lib/vendor/dataflow/Rakefile +36 -0
- data/lib/vendor/dataflow/dataflow.rb +120 -0
- data/lib/vendor/dataflow/dataflow/actor.rb +22 -0
- data/lib/vendor/dataflow/dataflow/equality.rb +28 -0
- data/lib/vendor/dataflow/dataflow/future_queue.rb +24 -0
- data/lib/vendor/dataflow/dataflow/port.rb +54 -0
- data/lib/vendor/dataflow/examples/barrier.rb +9 -0
- data/lib/vendor/dataflow/examples/data_driven.rb +17 -0
- data/lib/vendor/dataflow/examples/dataflow_http_gets.rb +13 -0
- data/lib/vendor/dataflow/examples/flow.rb +20 -0
- data/lib/vendor/dataflow/examples/future_http_gets.rb +12 -0
- data/lib/vendor/dataflow/examples/future_queue.rb +11 -0
- data/lib/vendor/dataflow/examples/instance_variables.rb +15 -0
- data/lib/vendor/dataflow/examples/laziness.rb +9 -0
- data/lib/vendor/dataflow/examples/local_variables.rb +11 -0
- data/lib/vendor/dataflow/examples/messages.rb +26 -0
- data/lib/vendor/dataflow/examples/port_http_gets.rb +13 -0
- data/lib/vendor/dataflow/examples/port_send.rb +10 -0
- data/lib/vendor/dataflow/examples/ring.rb +21 -0
- data/lib/vendor/dataflow/spec/actor_spec.rb +28 -0
- data/lib/vendor/dataflow/spec/anonymous_variables_spec.rb +21 -0
- data/lib/vendor/dataflow/spec/barrier_spec.rb +25 -0
- data/lib/vendor/dataflow/spec/by_need_spec.rb +55 -0
- data/lib/vendor/dataflow/spec/dataflow_spec.rb +151 -0
- data/lib/vendor/dataflow/spec/equality_spec.rb +40 -0
- data/lib/vendor/dataflow/spec/flow_spec.rb +25 -0
- data/lib/vendor/dataflow/spec/forker_spec.rb +28 -0
- data/lib/vendor/dataflow/spec/future_queue_spec.rb +31 -0
- data/lib/vendor/dataflow/spec/inspect_spec.rb +19 -0
- data/lib/vendor/dataflow/spec/need_later_spec.rb +12 -0
- data/lib/vendor/dataflow/spec/port_spec.rb +26 -0
- data/lib/vendor/dataflow/spec/spec.opts +1 -0
- data/lib/vendor/dataflow/spec/spec_helper.rb +10 -0
- data/lib/vendor/open4/lib/open4.rb +403 -0
- data/spec/deploy_hook_spec.rb +16 -2
- data/spec/fixtures/invalid_hook.rb +1 -0
- data/spec/fixtures/valid_hook.rb +1 -0
- metadata +43 -4
- data/lib/ey-deploy/verbose_system.rb +0 -12
@@ -0,0 +1,151 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/spec_helper"
|
2
|
+
|
3
|
+
context 'Using "local" for local variables' do
|
4
|
+
describe 'An unbound Variable' do
|
5
|
+
it 'suspends if an unbound variable has a method called on it until it is bound' do
|
6
|
+
local do |big_cat, small_cat|
|
7
|
+
Thread.new { unify big_cat, small_cat.upcase }
|
8
|
+
unify small_cat, 'cat'
|
9
|
+
big_cat.should == 'CAT'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'suspends if an unbound variable has a method called on it until it is bound with nil' do
|
14
|
+
local do |is_nil, var_nil|
|
15
|
+
Thread.new { unify is_nil, var_nil.nil? }
|
16
|
+
unify var_nil, nil
|
17
|
+
is_nil.should be_true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'suspends if an unbound variable has a method called on it until it is bound (with nested local variables)' do
|
22
|
+
local do |small_cat|
|
23
|
+
local do |big_cat|
|
24
|
+
Thread.new { unify big_cat, small_cat.upcase }
|
25
|
+
unify small_cat, 'cat'
|
26
|
+
big_cat.should == 'CAT'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'performs order-determining concurrency' do
|
32
|
+
local do |x, y, z|
|
33
|
+
Thread.new { unify y, x + 2 }
|
34
|
+
Thread.new { unify z, y + 3 }
|
35
|
+
Thread.new { unify x, 1 }
|
36
|
+
z.should == 6
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'binds on unification' do
|
41
|
+
local do |animal|
|
42
|
+
unify animal, 'cat'
|
43
|
+
animal.should == 'cat'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe 'A bound Variable' do
|
50
|
+
it 'does not complain when unifying with an equal object' do
|
51
|
+
lambda do
|
52
|
+
local do |animal|
|
53
|
+
unify animal, 'cat'
|
54
|
+
unify animal, 'cat'
|
55
|
+
end
|
56
|
+
end.should_not raise_error
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'does not complain when unifying with an unequal object when shadowing' do
|
60
|
+
lambda do
|
61
|
+
local do |animal|
|
62
|
+
unify animal, 'cat'
|
63
|
+
local do |animal|
|
64
|
+
unify animal, 'dog'
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end.should_not raise_error
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'complains when unifying with an unequal object' do
|
71
|
+
lambda do
|
72
|
+
local do |animal|
|
73
|
+
unify animal, 'cat'
|
74
|
+
unify animal, 'dog'
|
75
|
+
end
|
76
|
+
end.should raise_error(Dataflow::UnificationError)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context 'Using "declare" for object-specific read-only attributes' do
|
81
|
+
class Store
|
82
|
+
include Dataflow
|
83
|
+
declare :animal, :big_cat, :small_cat, :x, :y, :z, :is_nil, :var_nil
|
84
|
+
end
|
85
|
+
before { @store = Store.new }
|
86
|
+
|
87
|
+
describe 'An unbound Variable' do
|
88
|
+
it 'suspends if an unbound variable has a method called on it until it is bound' do
|
89
|
+
Thread.new { unify @store.big_cat, @store.small_cat.upcase }
|
90
|
+
unify @store.small_cat, 'cat'
|
91
|
+
@store.big_cat.should == 'CAT'
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'suspends if an unbound variable has a method called on it until it is bound with nil' do
|
95
|
+
Thread.new { unify @store.is_nil, @store.var_nil.nil? }
|
96
|
+
unify @store.var_nil, nil
|
97
|
+
@store.is_nil.should be_true
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'performs order-determining concurrency' do
|
101
|
+
Thread.new { unify @store.y, @store.x + 2 }
|
102
|
+
Thread.new { unify @store.z, @store.y + 3 }
|
103
|
+
Thread.new { unify @store.x, 1 }
|
104
|
+
@store.z.should == 6
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'binds on unification' do
|
108
|
+
unify @store.animal, 'cat'
|
109
|
+
@store.animal.should == 'cat'
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe 'A bound Variable' do
|
114
|
+
it 'does not complain when unifying with an equal object' do
|
115
|
+
lambda do
|
116
|
+
unify @store.animal, 'cat'
|
117
|
+
unify @store.animal, 'cat'
|
118
|
+
end.should_not raise_error
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'complains when unifying with an unequal object' do
|
122
|
+
lambda do
|
123
|
+
unify @store.animal, 'cat'
|
124
|
+
unify @store.animal, 'dog'
|
125
|
+
end.should raise_error(Dataflow::UnificationError)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe 'Binding a variable that proxies through another' do
|
131
|
+
it 'binds through successfully' do
|
132
|
+
local do |x, y|
|
133
|
+
lambda do
|
134
|
+
unify x, y
|
135
|
+
unify x, 1337
|
136
|
+
x.should == 1337
|
137
|
+
y.should == 1337
|
138
|
+
end.should_not raise_error
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
describe 'Using static/module method' do
|
144
|
+
it 'works like the mixin versions' do
|
145
|
+
Dataflow.local do |big_cat, small_cat|
|
146
|
+
Thread.new { Dataflow.unify big_cat, small_cat.upcase }
|
147
|
+
Dataflow.unify small_cat, 'cat'
|
148
|
+
big_cat.should == 'CAT'
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/spec_helper"
|
2
|
+
|
3
|
+
describe 'Unifying a bound value' do
|
4
|
+
[nil, true, false,
|
5
|
+
:sym, "str", /regex/,
|
6
|
+
3, 2.0,
|
7
|
+
Object.new, Class.new.new,
|
8
|
+
[], {}].each do |type|
|
9
|
+
describe "for #{type.class} instances" do
|
10
|
+
it 'passes unification for an object of equal value' do
|
11
|
+
local do |var, var2|
|
12
|
+
unify var, type
|
13
|
+
var.should == type
|
14
|
+
type.should == var
|
15
|
+
lambda {unify var, type}.should_not raise_error
|
16
|
+
|
17
|
+
unify var2, type
|
18
|
+
var.should == var2
|
19
|
+
var2.should == var
|
20
|
+
lambda {unify var, var2}.should_not raise_error
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'fails unification for an object of inequal value' do
|
25
|
+
different = Object.new
|
26
|
+
local do |var, var2|
|
27
|
+
unify var, type
|
28
|
+
var.should_not == different
|
29
|
+
different.should_not == var
|
30
|
+
lambda {unify var, different}.should raise_error(Dataflow::UnificationError)
|
31
|
+
|
32
|
+
unify var2, different
|
33
|
+
var.should_not == var2
|
34
|
+
var2.should_not == var
|
35
|
+
lambda {unify var, different}.should raise_error(Dataflow::UnificationError)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/spec_helper"
|
2
|
+
|
3
|
+
describe 'Using flow without a parameter' do
|
4
|
+
it 'works like normal threads' do
|
5
|
+
local do |big_cat, small_cat|
|
6
|
+
flow { unify big_cat, small_cat.upcase }
|
7
|
+
unify small_cat, 'cat'
|
8
|
+
big_cat.should == 'CAT'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe 'Using flow with a parameter' do
|
14
|
+
it 'binds the parameter to the last line of the block' do
|
15
|
+
local do |big_cat, small_cat, output|
|
16
|
+
flow(output) do
|
17
|
+
unify big_cat, small_cat.upcase
|
18
|
+
'returned'
|
19
|
+
end
|
20
|
+
unify small_cat, 'cat'
|
21
|
+
big_cat.should == 'CAT'
|
22
|
+
output.should == 'returned'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/spec_helper"
|
2
|
+
|
3
|
+
describe 'Setting a customer forker' do
|
4
|
+
before(:all) do
|
5
|
+
@original_forker = Dataflow.forker
|
6
|
+
Dataflow.forker = Class.new do
|
7
|
+
def self.synchronous_forker(&block)
|
8
|
+
block.call
|
9
|
+
end
|
10
|
+
end.method(:synchronous_forker)
|
11
|
+
end
|
12
|
+
|
13
|
+
after(:all) do
|
14
|
+
Dataflow.forker = @original_forker
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'uses the custom forker in #flow' do
|
18
|
+
local do |my_var|
|
19
|
+
flow(my_var) { 1337 }
|
20
|
+
my_var.should == 1337
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'uses the custom forker in #need_later' do
|
25
|
+
my_var = need_later { 1337 }
|
26
|
+
my_var.should == 1337
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/spec_helper"
|
2
|
+
|
3
|
+
describe 'A future queue' do
|
4
|
+
it 'accepts synchronous pushes and pops' do
|
5
|
+
local do |queue, first, second, third|
|
6
|
+
unify queue, Dataflow::FutureQueue.new
|
7
|
+
queue.pop first
|
8
|
+
queue.pop second
|
9
|
+
queue.push 1
|
10
|
+
queue.push 2
|
11
|
+
queue.push 3
|
12
|
+
queue.pop third
|
13
|
+
|
14
|
+
[first, second, third].should == [1, 2, 3]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'accepts asynchronous pushes and pops' do
|
19
|
+
local do |queue, first, second, third|
|
20
|
+
unify queue, Dataflow::FutureQueue.new
|
21
|
+
Thread.new { queue.pop first }
|
22
|
+
Thread.new { queue.pop second }
|
23
|
+
Thread.new { queue.push 1 }
|
24
|
+
Thread.new { queue.push 2 }
|
25
|
+
Thread.new { queue.push 3 }
|
26
|
+
Thread.new { queue.pop third }
|
27
|
+
|
28
|
+
[first, second, third].sort.should == [1, 2, 3]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/spec_helper"
|
2
|
+
|
3
|
+
describe "An unbound variable" do
|
4
|
+
it "should be inspectable for debugging purposes" do
|
5
|
+
local do |unbound|
|
6
|
+
unbound.inspect.should =~ /#<Dataflow::Variable:[0-9]+ unbound>/
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "An bound variable" do
|
12
|
+
it "should proxy inspect" do
|
13
|
+
local do |bound|
|
14
|
+
bound.inspect
|
15
|
+
unify bound, "str"
|
16
|
+
bound.should == "str"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/spec_helper"
|
2
|
+
|
3
|
+
describe 'A need_later expression' do
|
4
|
+
it 'returns a future to be calculated later' do
|
5
|
+
local do |x, y, z|
|
6
|
+
unify y, need_later { 4 }
|
7
|
+
unify z, need_later { x + y }
|
8
|
+
unify x, need_later { 3 }
|
9
|
+
z.should == 7
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/spec_helper"
|
2
|
+
|
3
|
+
describe 'Syncronously sending to a Port' do
|
4
|
+
it 'extends the length of the stream and preserves order' do
|
5
|
+
local do |port, stream|
|
6
|
+
unify port, Dataflow::Port.new(stream)
|
7
|
+
port.send 1
|
8
|
+
port.send 2
|
9
|
+
stream.take(2).should == [1, 2]
|
10
|
+
port.send 3
|
11
|
+
stream.take(3).should == [1, 2, 3]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe 'Asyncronously sending to a Port' do
|
17
|
+
it 'extends the length of the stream and does not preserve order' do
|
18
|
+
local do |port, stream|
|
19
|
+
unify port, Dataflow::Port.new(stream)
|
20
|
+
Thread.new {port.send 2}
|
21
|
+
Thread.new {port.send 8}
|
22
|
+
Thread.new {port.send 1024}
|
23
|
+
stream.take(3).sort.should == [2, 8, 1024]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
@@ -0,0 +1,403 @@
|
|
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
|