aggkit 0.2.5
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.
- 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
|
+
|