aggkit 0.2.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.dockerignore +20 -0
- data/.gitignore +109 -0
- data/.gitlab-ci.yml +66 -0
- data/.rspec +4 -0
- data/.rubocop.yml +98 -0
- data/.travis.yml +30 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +55 -0
- data/README.md +96 -0
- data/aggkit.gemspec +38 -0
- data/bin/agg +167 -0
- data/bin/aggconsul +222 -0
- data/bin/agglock +71 -0
- data/bin/aggmerge +118 -0
- data/bin/aggwait +262 -0
- data/bin/consul.rb +222 -0
- data/bin/locker.rb +71 -0
- data/bin/merger.rb +118 -0
- data/bin/terminator.rb +71 -0
- data/bin/waiter.rb +262 -0
- data/docker/Dockerfile +112 -0
- data/docker/docker-compose.yml +12 -0
- data/docker/down.sh +4 -0
- data/docker/run_tests.sh +23 -0
- data/lib/aggkit/childprocess/abstract_io.rb +38 -0
- data/lib/aggkit/childprocess/abstract_process.rb +194 -0
- data/lib/aggkit/childprocess/errors.rb +28 -0
- data/lib/aggkit/childprocess/jruby/io.rb +17 -0
- data/lib/aggkit/childprocess/jruby/process.rb +161 -0
- data/lib/aggkit/childprocess/jruby/pump.rb +55 -0
- data/lib/aggkit/childprocess/jruby.rb +58 -0
- data/lib/aggkit/childprocess/tools/generator.rb +148 -0
- data/lib/aggkit/childprocess/unix/fork_exec_process.rb +72 -0
- data/lib/aggkit/childprocess/unix/io.rb +22 -0
- data/lib/aggkit/childprocess/unix/lib.rb +188 -0
- data/lib/aggkit/childprocess/unix/platform/i386-linux.rb +14 -0
- data/lib/aggkit/childprocess/unix/platform/i386-solaris.rb +13 -0
- data/lib/aggkit/childprocess/unix/platform/x86_64-linux.rb +14 -0
- data/lib/aggkit/childprocess/unix/platform/x86_64-macosx.rb +13 -0
- data/lib/aggkit/childprocess/unix/posix_spawn_process.rb +135 -0
- data/lib/aggkit/childprocess/unix/process.rb +91 -0
- data/lib/aggkit/childprocess/unix.rb +11 -0
- data/lib/aggkit/childprocess/version.rb +5 -0
- data/lib/aggkit/childprocess/windows/handle.rb +93 -0
- data/lib/aggkit/childprocess/windows/io.rb +25 -0
- data/lib/aggkit/childprocess/windows/lib.rb +418 -0
- data/lib/aggkit/childprocess/windows/process.rb +132 -0
- data/lib/aggkit/childprocess/windows/process_builder.rb +177 -0
- data/lib/aggkit/childprocess/windows/structs.rb +151 -0
- data/lib/aggkit/childprocess/windows.rb +35 -0
- data/lib/aggkit/childprocess.rb +213 -0
- data/lib/aggkit/env.rb +219 -0
- data/lib/aggkit/runner.rb +80 -0
- data/lib/aggkit/version.rb +5 -0
- data/lib/aggkit/watcher.rb +239 -0
- data/lib/aggkit.rb +15 -0
- metadata +196 -0
data/bin/waiter.rb
ADDED
@@ -0,0 +1,262 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'securerandom'
|
5
|
+
require 'English'
|
6
|
+
require 'openssl'
|
7
|
+
require 'tempfile'
|
8
|
+
require 'json'
|
9
|
+
|
10
|
+
STDOUT.sync = true
|
11
|
+
STDERR.sync = true
|
12
|
+
|
13
|
+
TIMEOUT = 15
|
14
|
+
INTERVAL = 3
|
15
|
+
|
16
|
+
@opts = {}
|
17
|
+
|
18
|
+
@opts[:exec] = (begin
|
19
|
+
ARGV.join(' ').split(' -- ')[1].strip
|
20
|
+
rescue StandardError
|
21
|
+
nil
|
22
|
+
end)
|
23
|
+
@opts[:timeout] = TIMEOUT
|
24
|
+
@opts[:interval] = INTERVAL
|
25
|
+
@opts[:consul_addr] = 'http://localhost:8500'
|
26
|
+
@opts[:consul_service_count] = 1
|
27
|
+
|
28
|
+
|
29
|
+
OptionParser.new do |o|
|
30
|
+
o.banner = 'Usage: waiter.rb [options] -- exec'
|
31
|
+
|
32
|
+
o.on('--tcp host:port', 'Wait for tcp accepts on host:port') do |addr|
|
33
|
+
host, port = addr.split(':')
|
34
|
+
@opts[:tcp] = addr.strip
|
35
|
+
@opts[:host] = host.strip
|
36
|
+
@opts[:port] = port.strip
|
37
|
+
end
|
38
|
+
|
39
|
+
o.on('--db dbname', 'Wait for PG database exists. Using --tcp to conenct PG') do |db|
|
40
|
+
@opts[:db] = db.strip
|
41
|
+
end
|
42
|
+
|
43
|
+
o.on('--tb tablename', 'Wait for PG table exists. Using --tcp to conenct PG') do |tb|
|
44
|
+
@opts[:tb] = tb.strip
|
45
|
+
end
|
46
|
+
|
47
|
+
o.on('-f', '--file filename', 'Wait for file exists.') do |file|
|
48
|
+
@opts[:file] = file.strip
|
49
|
+
end
|
50
|
+
|
51
|
+
o.on("--consul-addr addr=#{@opts[:consul_addr]}", 'HTTP addres to connect to consul') do |addr|
|
52
|
+
@opts[:consul_addr] = addr.strip
|
53
|
+
end
|
54
|
+
|
55
|
+
o.on('--consul', 'Wait for local consul agent to be ready') do
|
56
|
+
@opts[:consul] = true
|
57
|
+
end
|
58
|
+
|
59
|
+
o.on('--consul-service service', 'Wait for service appear in consul') do |service|
|
60
|
+
@opts[:consul_service] = service
|
61
|
+
end
|
62
|
+
|
63
|
+
o.on("--consul-service-count count=#{@opts[:consul_service_count]}", 'Wait for this count of service appear in consul') do |count|
|
64
|
+
@opts[:consul_service_count] = count.to_i
|
65
|
+
end
|
66
|
+
|
67
|
+
o.on('--consul-tag tag', 'Filter consul service by tag') do |tag|
|
68
|
+
@opts[:consul_tag] = tag.to_s
|
69
|
+
end
|
70
|
+
|
71
|
+
o.on('--user user', 'username') do |user|
|
72
|
+
@opts[:user] = user.strip
|
73
|
+
end
|
74
|
+
|
75
|
+
o.on('--pass pass', 'password') do |pass|
|
76
|
+
@opts[:pass] = pass.strip
|
77
|
+
end
|
78
|
+
|
79
|
+
o.on('-t', '--timeout secs=15', 'Total timeout') do |timeout|
|
80
|
+
@opts[:timeout] = timeout.to_i
|
81
|
+
end
|
82
|
+
|
83
|
+
o.on('-i', '--interval secs=2', 'Interval between attempts') do |interval|
|
84
|
+
@opts[:interval] = interval.to_i
|
85
|
+
end
|
86
|
+
|
87
|
+
o.on('-q', '--quiet', 'Do not output any status messages') do
|
88
|
+
@opts[:quiet] = true
|
89
|
+
end
|
90
|
+
end.parse!
|
91
|
+
|
92
|
+
def log(message)
|
93
|
+
puts message unless @opts[:quiet]
|
94
|
+
end
|
95
|
+
|
96
|
+
@opts[:timeout] = @opts[:timeout].to_i
|
97
|
+
@opts[:interval] = @opts[:interval].to_i
|
98
|
+
|
99
|
+
if @opts[:db]
|
100
|
+
@pg = {}
|
101
|
+
@pg[:db] = "-d #{@opts[:db]}"
|
102
|
+
@pg[:user] = "-U #{@opts[:user]}" if @opts[:user]
|
103
|
+
@pg[:pass] = if @opts[:pass] && !@opts[:pass].empty?
|
104
|
+
"PGPASSWORD=#{@opts[:pass]}"
|
105
|
+
else
|
106
|
+
''
|
107
|
+
end
|
108
|
+
|
109
|
+
@pg[:host] = "-h #{@opts[:host]}" if @opts[:host]
|
110
|
+
@pg[:port] = "-p #{@opts[:port]}" if @opts[:port]
|
111
|
+
|
112
|
+
@pg[:tb] = @opts[:tb] if @opts[:tb]
|
113
|
+
end
|
114
|
+
|
115
|
+
def wait_for(timeout)
|
116
|
+
starttime = Time.now
|
117
|
+
loop do
|
118
|
+
success = yield
|
119
|
+
|
120
|
+
return success if success
|
121
|
+
|
122
|
+
return false if (Time.now - starttime) > timeout
|
123
|
+
sleep @opts[:interval]
|
124
|
+
end
|
125
|
+
|
126
|
+
false
|
127
|
+
end
|
128
|
+
|
129
|
+
def complete!(success)
|
130
|
+
if success
|
131
|
+
if @opts[:exec]
|
132
|
+
exec @opts[:exec]
|
133
|
+
else
|
134
|
+
exit 0
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
STDERR.puts 'Operation timed out'
|
139
|
+
exit 1
|
140
|
+
end
|
141
|
+
|
142
|
+
def wait_for_consul
|
143
|
+
log('Waiting for consul...')
|
144
|
+
ret = wait_for @opts[:timeout] do
|
145
|
+
cmd = "consul operator raft list-peers -http-addr=#{@opts[:consul_addr]} > /dev/null 2>&1"
|
146
|
+
system(cmd)
|
147
|
+
$?.success?
|
148
|
+
end
|
149
|
+
|
150
|
+
yield(ret)
|
151
|
+
end
|
152
|
+
|
153
|
+
def wait_for_consul_service(service)
|
154
|
+
log("Waiting for consul service #{service}...")
|
155
|
+
ret = wait_for @opts[:timeout] do
|
156
|
+
cmd = "curl -s #{@opts[:consul_addr]}/v1/health/service/#{service}?passing"
|
157
|
+
cmd += "&tag=#{@opts[:consul_tag]}" if @opts[:consul_tag]
|
158
|
+
result = `#{cmd}`
|
159
|
+
result = begin
|
160
|
+
JSON.parse(result)
|
161
|
+
rescue StandardError
|
162
|
+
[]
|
163
|
+
end
|
164
|
+
result.count >= @opts[:consul_service_count]
|
165
|
+
end
|
166
|
+
|
167
|
+
yield(ret)
|
168
|
+
end
|
169
|
+
|
170
|
+
def wait_for_tcp
|
171
|
+
log("Waiting for TCP: #{@opts[:host]}:#{@opts[:port]}...")
|
172
|
+
ret = wait_for @opts[:timeout] do
|
173
|
+
cmd = "nc -z #{@opts[:host]} #{@opts[:port]} > /dev/null 2>&1"
|
174
|
+
system(cmd)
|
175
|
+
$?.success?
|
176
|
+
end
|
177
|
+
|
178
|
+
yield(ret)
|
179
|
+
end
|
180
|
+
|
181
|
+
def wait_for_db
|
182
|
+
log("Waiting for DB: pg://#{@opts[:user]}:#{@opts[:pass]}@#{@opts[:host]}:#{@opts[:port]}/#{@opts[:db]}...")
|
183
|
+
ret = wait_for @opts[:timeout] do
|
184
|
+
cmd = "#{@pg[:pass]} psql -lqt #{@pg[:user]} #{@pg[:host]} #{@pg[:port]} #{@pg[:db]} 2>/dev/null | cut -d \\| -f 1 | grep -qw #{@opts[:db]} > /dev/null 2>&1"
|
185
|
+
system(cmd)
|
186
|
+
$?.success?
|
187
|
+
end
|
188
|
+
|
189
|
+
yield(ret)
|
190
|
+
end
|
191
|
+
|
192
|
+
def wait_for_tb
|
193
|
+
log("Waiting for TABLE: pg://#{@opts[:user]}:#{@opts[:pass]}@#{@opts[:host]}:#{@opts[:port]}/#{@opts[:db]}##{@opts[:tb]}...")
|
194
|
+
ret = wait_for @opts[:timeout] do
|
195
|
+
cmd = "echo \"\\dt\" | psql -qt #{@pg[:user]} #{@pg[:pass]} #{@pg[:host]} #{@pg[:port]} #{@pg[:db]} 2>/dev/null | cut -d \\| -f 2 | grep -qw #{@pg[:tb]} > /dev/null 2>&1"
|
196
|
+
system(cmd)
|
197
|
+
$?.success?
|
198
|
+
end
|
199
|
+
|
200
|
+
yield(ret)
|
201
|
+
end
|
202
|
+
|
203
|
+
def wait_for_file(file = @opts[:file], timeout = @opts[:timeout])
|
204
|
+
log("Waiting for FILE: #{file}")
|
205
|
+
ret = wait_for timeout do
|
206
|
+
File.exist? file
|
207
|
+
end
|
208
|
+
yield(ret)
|
209
|
+
end
|
210
|
+
|
211
|
+
if @opts[:tb]
|
212
|
+
wait_for_tcp do |success|
|
213
|
+
if success
|
214
|
+
wait_for_db do |success|
|
215
|
+
if success
|
216
|
+
wait_for_tb do |success|
|
217
|
+
complete!(success)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
complete!(false)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
if @opts[:db]
|
228
|
+
wait_for_tcp do |success|
|
229
|
+
if success
|
230
|
+
wait_for_db do |success|
|
231
|
+
complete!(success)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
complete!(false)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
|
240
|
+
if @opts[:consul]
|
241
|
+
wait_for_consul do |success|
|
242
|
+
complete!(success)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
if @opts[:consul_service]
|
247
|
+
wait_for_consul_service(@opts[:consul_service]) do |success|
|
248
|
+
complete!(success)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
if @opts[:tcp]
|
253
|
+
wait_for_tcp do |success|
|
254
|
+
complete!(success)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
if @opts[:file]
|
259
|
+
wait_for_file(@opts[:file], @opts[:timeout]) do |success|
|
260
|
+
complete!(success)
|
261
|
+
end
|
262
|
+
end
|
data/docker/Dockerfile
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
FROM alpine:3.8
|
2
|
+
MAINTAINER Firmhouse "kinnalru@gmail.com"
|
3
|
+
|
4
|
+
WORKDIR /home/app
|
5
|
+
|
6
|
+
RUN mkdir -p ~/.ssh && echo -e "Host * \
|
7
|
+
\n StrictHostKeyChecking no \
|
8
|
+
\n UserKnownHostsFile=/dev/null\n" >> ~/.ssh/config
|
9
|
+
|
10
|
+
RUN set -ex \
|
11
|
+
&& apk add --no-cache \
|
12
|
+
alpine-sdk \
|
13
|
+
bash \
|
14
|
+
curl \
|
15
|
+
gcc \
|
16
|
+
git \
|
17
|
+
gnupg \
|
18
|
+
libcurl \
|
19
|
+
libstdc++ \
|
20
|
+
libxml2 \
|
21
|
+
linux-headers \
|
22
|
+
make \
|
23
|
+
openssl \
|
24
|
+
openssl-dev \
|
25
|
+
procps \
|
26
|
+
ruby \
|
27
|
+
ruby-libs \
|
28
|
+
shadow \
|
29
|
+
tzdata \
|
30
|
+
wget
|
31
|
+
|
32
|
+
RUN set -ex \
|
33
|
+
&& gpg2 --keyserver hkp://pool.sks-keyservers.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB \
|
34
|
+
&& ln -sf /bin/bash /bin/ash \
|
35
|
+
&& ln -sf /bin/bash /bin/sh \
|
36
|
+
&& sed -i s%bin\\/ash%bin\\/bash%g /etc/passwd
|
37
|
+
|
38
|
+
SHELL ["/bin/bash", "-c", "-l"]
|
39
|
+
|
40
|
+
RUN set -ex \
|
41
|
+
&& apk add --no-cache --virtual .builddeps \
|
42
|
+
alpine-sdk \
|
43
|
+
curl \
|
44
|
+
gcc \
|
45
|
+
libssl1.0 \
|
46
|
+
linux-headers \
|
47
|
+
make \
|
48
|
+
musl-dev \
|
49
|
+
openssl \
|
50
|
+
openssl-dev \
|
51
|
+
procps \
|
52
|
+
ruby \
|
53
|
+
zlib \
|
54
|
+
zlib-dev \
|
55
|
+
sqlite sqlite-dev libtool autoconf automake bison readline yaml ncurses \
|
56
|
+
&& curl -sSL https://get.rvm.io | bash -s stable --autolibs=0 \
|
57
|
+
&& usermod -a -G rvm root \
|
58
|
+
&& set +x \
|
59
|
+
&& source /etc/profile && source /etc/profile.d/rvm.sh \
|
60
|
+
&& export PATH=$PATH:/usr/local/rvm/bin \
|
61
|
+
&& rvm install 2.1 && rvm use 2.1 && gem install bundler -v 1.17.3 \
|
62
|
+
&& rvm install 2.5 && rvm use 2.5 && gem install bundler \
|
63
|
+
&& rvm use --default 2.5 \
|
64
|
+
&& set -x \
|
65
|
+
&& apk del .builddeps \
|
66
|
+
&& gem cleanup \
|
67
|
+
&& rm -rf /tmp/* /var/tmp/* /usr/src/ruby /root/.gem /usr/local/bundle/cache
|
68
|
+
|
69
|
+
RUN mkdir -p /usr/local/etc \
|
70
|
+
&& { \
|
71
|
+
echo 'install: --no-document'; \
|
72
|
+
echo 'update: --no-document'; \
|
73
|
+
} >> /usr/local/etc/gemrc \
|
74
|
+
&& echo 'gem: --no-ri --no-rdoc' > ~/.gemrc
|
75
|
+
|
76
|
+
RUN set -ex \
|
77
|
+
&& curl -sSLo /tmp/consul.zip https://releases.hashicorp.com/consul/1.2.1/consul_1.2.1_linux_amd64.zip \
|
78
|
+
&& unzip -d /bin /tmp/consul.zip \
|
79
|
+
&& rm -rf /tmp/consul.zip \
|
80
|
+
&& addgroup consul \
|
81
|
+
&& adduser -D -g "" -s /bin/sh -G consul consul \
|
82
|
+
&& mkdir -p /tmp/consul \
|
83
|
+
&& chown -R consul:consul /tmp/consul
|
84
|
+
|
85
|
+
RUN set -ex \
|
86
|
+
&& curl -so envconsul.tgz https://releases.hashicorp.com/envconsul/0.7.3/envconsul_0.7.3_linux_amd64.tgz \
|
87
|
+
&& tar -xvzf envconsul.tgz \
|
88
|
+
&& rm -rf envconsul.tgz \
|
89
|
+
&& mv envconsul /bin/envconsul \
|
90
|
+
&& chmod +x /bin/envconsul
|
91
|
+
|
92
|
+
ADD Gemfile Gemfile.lock aggkit.gemspec /home/app/
|
93
|
+
ADD lib/ /home/app/lib
|
94
|
+
|
95
|
+
|
96
|
+
RUN set -e \
|
97
|
+
&& source /etc/profile && source /etc/profile.d/rvm.sh \
|
98
|
+
&& rvm use 2.1 && bundle install \
|
99
|
+
&& rvm use 2.5 && bundle install
|
100
|
+
|
101
|
+
ADD . /home/app
|
102
|
+
|
103
|
+
RUN set -e \
|
104
|
+
&& rvm use 2.1 \
|
105
|
+
&& gem build `ls | grep gemspec` && gem install `ls | grep -e '.gem$'` \
|
106
|
+
&& rvm use 2.5 \
|
107
|
+
&& gem build `ls | grep gemspec` && gem install `ls | grep -e '.gem$'`
|
108
|
+
|
109
|
+
CMD ["/bin/bash"]
|
110
|
+
|
111
|
+
|
112
|
+
|
data/docker/down.sh
ADDED
data/docker/run_tests.sh
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
SELF=$(readlink -f $0)
|
3
|
+
export ENV_ROOT=$(dirname "${SELF}")
|
4
|
+
|
5
|
+
trap '$ENV_ROOT/down.sh' EXIT
|
6
|
+
|
7
|
+
cd $ENV_ROOT
|
8
|
+
|
9
|
+
if [ -z "${RUBY_VERSION}" ]; then
|
10
|
+
export RUBY_VERSION=2.5
|
11
|
+
fi
|
12
|
+
|
13
|
+
docker-compose up -d --force-recreate --build || exit 1
|
14
|
+
echo "\n ** Testing with Ruby $RUBY_VERSION **\n"
|
15
|
+
docker-compose exec -T tests /bin/bash -c -l "rvm use $RUBY_VERSION; exec bundle exec rspec $@"
|
16
|
+
|
17
|
+
|
18
|
+
code=$?
|
19
|
+
if [ $code -ne 0 ]; then
|
20
|
+
docker-compose logs
|
21
|
+
fi
|
22
|
+
|
23
|
+
exit $code
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Aggkit
|
2
|
+
module ChildProcess
|
3
|
+
class AbstractIO
|
4
|
+
attr_reader :stderr, :stdout, :stdin
|
5
|
+
|
6
|
+
def inherit!
|
7
|
+
@stdout = STDOUT
|
8
|
+
@stderr = STDERR
|
9
|
+
end
|
10
|
+
|
11
|
+
def stderr=(io)
|
12
|
+
check_type io
|
13
|
+
@stderr = io
|
14
|
+
end
|
15
|
+
|
16
|
+
def stdout=(io)
|
17
|
+
check_type io
|
18
|
+
@stdout = io
|
19
|
+
end
|
20
|
+
|
21
|
+
#
|
22
|
+
# @api private
|
23
|
+
#
|
24
|
+
|
25
|
+
def _stdin=(io)
|
26
|
+
check_type io
|
27
|
+
@stdin = io
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def check_type(io)
|
33
|
+
raise SubclassResponsibility, "check_type"
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
module Aggkit
|
2
|
+
module ChildProcess
|
3
|
+
class AbstractProcess
|
4
|
+
POLL_INTERVAL = 0.1
|
5
|
+
|
6
|
+
attr_reader :exit_code
|
7
|
+
|
8
|
+
#
|
9
|
+
# Set this to true if you do not care about when or if the process quits.
|
10
|
+
#
|
11
|
+
attr_accessor :detach
|
12
|
+
|
13
|
+
#
|
14
|
+
# Set this to true if you want to write to the process' stdin (process.io.stdin)
|
15
|
+
#
|
16
|
+
attr_accessor :duplex
|
17
|
+
|
18
|
+
#
|
19
|
+
# Modify the child's environment variables
|
20
|
+
#
|
21
|
+
attr_reader :environment
|
22
|
+
|
23
|
+
#
|
24
|
+
# Set the child's current working directory.
|
25
|
+
#
|
26
|
+
attr_accessor :cwd
|
27
|
+
|
28
|
+
#
|
29
|
+
# Set this to true to make the child process the leader of a new process group
|
30
|
+
#
|
31
|
+
# This can be used to make sure that all grandchildren are killed
|
32
|
+
# when the child process dies.
|
33
|
+
#
|
34
|
+
attr_accessor :leader
|
35
|
+
|
36
|
+
#
|
37
|
+
# Create a new process with the given args.
|
38
|
+
#
|
39
|
+
# @api private
|
40
|
+
# @see ChildProcess.build
|
41
|
+
#
|
42
|
+
|
43
|
+
def initialize(args)
|
44
|
+
unless args.all? { |e| e.kind_of?(String) }
|
45
|
+
raise ArgumentError, "all arguments must be String: #{args.inspect}"
|
46
|
+
end
|
47
|
+
|
48
|
+
@args = args
|
49
|
+
@started = false
|
50
|
+
@exit_code = nil
|
51
|
+
@io = nil
|
52
|
+
@cwd = nil
|
53
|
+
@detach = false
|
54
|
+
@duplex = false
|
55
|
+
@leader = false
|
56
|
+
@environment = {}
|
57
|
+
end
|
58
|
+
|
59
|
+
#
|
60
|
+
# Returns a ChildProcess::AbstractIO subclass to configure the child's IO streams.
|
61
|
+
#
|
62
|
+
|
63
|
+
def io
|
64
|
+
raise SubclassResponsibility, "io"
|
65
|
+
end
|
66
|
+
|
67
|
+
#
|
68
|
+
# @return [Integer] the pid of the process after it has started
|
69
|
+
#
|
70
|
+
|
71
|
+
def pid
|
72
|
+
raise SubclassResponsibility, "pid"
|
73
|
+
end
|
74
|
+
|
75
|
+
#
|
76
|
+
# Launch the child process
|
77
|
+
#
|
78
|
+
# @return [AbstractProcess] self
|
79
|
+
#
|
80
|
+
|
81
|
+
def start
|
82
|
+
launch_process
|
83
|
+
@started = true
|
84
|
+
|
85
|
+
self
|
86
|
+
end
|
87
|
+
|
88
|
+
#
|
89
|
+
# Forcibly terminate the process, using increasingly harsher methods if possible.
|
90
|
+
#
|
91
|
+
# @param [Integer] timeout (3) Seconds to wait before trying the next method.
|
92
|
+
#
|
93
|
+
|
94
|
+
def stop(timeout = 3)
|
95
|
+
raise SubclassResponsibility, "stop"
|
96
|
+
end
|
97
|
+
|
98
|
+
#
|
99
|
+
# Block until the process has been terminated.
|
100
|
+
#
|
101
|
+
# @return [Integer] The exit status of the process
|
102
|
+
#
|
103
|
+
|
104
|
+
def wait
|
105
|
+
raise SubclassResponsibility, "wait"
|
106
|
+
end
|
107
|
+
|
108
|
+
#
|
109
|
+
# Did the process exit?
|
110
|
+
#
|
111
|
+
# @return [Boolean]
|
112
|
+
#
|
113
|
+
|
114
|
+
def exited?
|
115
|
+
raise SubclassResponsibility, "exited?"
|
116
|
+
end
|
117
|
+
|
118
|
+
#
|
119
|
+
# Has the process started?
|
120
|
+
#
|
121
|
+
# @return [Boolean]
|
122
|
+
#
|
123
|
+
|
124
|
+
def started?
|
125
|
+
@started
|
126
|
+
end
|
127
|
+
|
128
|
+
#
|
129
|
+
# Is this process running?
|
130
|
+
#
|
131
|
+
# @return [Boolean]
|
132
|
+
#
|
133
|
+
|
134
|
+
def alive?
|
135
|
+
started? && !exited?
|
136
|
+
end
|
137
|
+
|
138
|
+
#
|
139
|
+
# Returns true if the process has exited and the exit code was not 0.
|
140
|
+
#
|
141
|
+
# @return [Boolean]
|
142
|
+
#
|
143
|
+
|
144
|
+
def crashed?
|
145
|
+
exited? && @exit_code != 0
|
146
|
+
end
|
147
|
+
|
148
|
+
#
|
149
|
+
# Wait for the process to exit, raising a ChildProcess::TimeoutError if
|
150
|
+
# the timeout expires.
|
151
|
+
#
|
152
|
+
|
153
|
+
def poll_for_exit(timeout)
|
154
|
+
log "polling #{timeout} seconds for exit"
|
155
|
+
|
156
|
+
end_time = Time.now + timeout
|
157
|
+
until (ok = exited?) || Time.now > end_time
|
158
|
+
sleep POLL_INTERVAL
|
159
|
+
end
|
160
|
+
|
161
|
+
unless ok
|
162
|
+
raise TimeoutError, "process still alive after #{timeout} seconds"
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
private
|
167
|
+
|
168
|
+
def launch_process
|
169
|
+
raise SubclassResponsibility, "launch_process"
|
170
|
+
end
|
171
|
+
|
172
|
+
def detach?
|
173
|
+
@detach
|
174
|
+
end
|
175
|
+
|
176
|
+
def duplex?
|
177
|
+
@duplex
|
178
|
+
end
|
179
|
+
|
180
|
+
def leader?
|
181
|
+
@leader
|
182
|
+
end
|
183
|
+
|
184
|
+
def log(*args)
|
185
|
+
ChildProcess.logger.debug "#{self.inspect} : #{args.inspect}"
|
186
|
+
end
|
187
|
+
|
188
|
+
def assert_started
|
189
|
+
raise Error, "process not started" unless started?
|
190
|
+
end
|
191
|
+
|
192
|
+
end # AbstractProcess
|
193
|
+
end # ChildProcess
|
194
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Aggkit
|
2
|
+
module ChildProcess
|
3
|
+
class Error < StandardError
|
4
|
+
end
|
5
|
+
|
6
|
+
class TimeoutError < Error
|
7
|
+
end
|
8
|
+
|
9
|
+
class SubclassResponsibility < Error
|
10
|
+
end
|
11
|
+
|
12
|
+
class InvalidEnvironmentVariable < Error
|
13
|
+
end
|
14
|
+
|
15
|
+
class LaunchError < Error
|
16
|
+
end
|
17
|
+
|
18
|
+
class MissingPlatformError < Error
|
19
|
+
def initialize
|
20
|
+
message = "posix_spawn is not yet supported on #{ChildProcess.platform_name} (#{RUBY_PLATFORM}), falling back to default implementation. " +
|
21
|
+
"If you believe this is an error, please file a bug at http://github.com/enkessler/childprocess/issues"
|
22
|
+
|
23
|
+
super(message)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Aggkit
|
2
|
+
module ChildProcess
|
3
|
+
module JRuby
|
4
|
+
class IO < AbstractIO
|
5
|
+
private
|
6
|
+
|
7
|
+
def check_type(output)
|
8
|
+
unless output.respond_to?(:to_outputstream) && output.respond_to?(:write)
|
9
|
+
raise ArgumentError, "expected #{output.inspect} to respond to :to_outputstream"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
end # IO
|
14
|
+
end # Unix
|
15
|
+
end # ChildProcess
|
16
|
+
end
|
17
|
+
|