scout-gear 5.2.0 → 6.0.0
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 +4 -4
- data/.vimproject +14 -8
- data/VERSION +1 -1
- data/lib/scout/exceptions.rb +14 -2
- data/lib/scout/log/color.rb +34 -10
- data/lib/scout/log/progress/report.rb +5 -4
- data/lib/scout/misc/monitor.rb +18 -0
- data/lib/scout/open/stream.rb +30 -0
- data/lib/scout/semaphore.rb +148 -0
- data/lib/scout/work_queue/socket.rb +119 -0
- data/lib/scout/work_queue/worker.rb +54 -0
- data/lib/scout/work_queue.rb +86 -0
- data/lib/scout/workflow/step/info.rb +2 -2
- data/scout-gear.gemspec +14 -3
- data/share/color/color_names +507 -0
- data/share/color/diverging_colors.hex +12 -0
- data/test/scout/log/test_color.rb +0 -0
- data/test/scout/test_semaphore.rb +17 -0
- data/test/scout/test_work_queue.rb +93 -0
- data/test/scout/work_queue/test_socket.rb +46 -0
- data/test/scout/work_queue/test_worker.rb +99 -0
- metadata +13 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 919678993bd756d6a67712023120e562c01a69942faf8ad23eb088f29559b1fa
|
4
|
+
data.tar.gz: a233c808dc9af64381196bce97c74727c9064c9b1a4c2a129b364a5d776b01e1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 842323af61a5c66c7bf4433cbc0d23e6484e562c88601c64ac7a47ea2c1e217e6f7b590bd7d613d62614ebb2ab228246a2ac150d4d263f9425d9c1a2b4333e65
|
7
|
+
data.tar.gz: 2a4a7d00279d0cbc3153d7b116405fb5d02f8656249b4e0bdf417d01599749686242fb30db183b2baff856e92dcd5c83317161e73bb5bb1dbc861ce10d0086de
|
data/.vimproject
CHANGED
@@ -103,6 +103,12 @@ scout-gear=/$PWD filter="*.rb *.yaml" {
|
|
103
103
|
usage.rb
|
104
104
|
util.rb
|
105
105
|
}
|
106
|
+
semaphore.rb
|
107
|
+
work_queue.rb
|
108
|
+
work_queue=work_queue{
|
109
|
+
socket.rb
|
110
|
+
worker.rb
|
111
|
+
}
|
106
112
|
}
|
107
113
|
}
|
108
114
|
test=test {
|
@@ -224,16 +230,16 @@ scout-gear=/$PWD filter="*.rb *.yaml" {
|
|
224
230
|
concurrency=concurrency{
|
225
231
|
processes.rb
|
226
232
|
threads.rb
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
233
|
+
processes=processes{
|
234
|
+
socket.rb
|
235
|
+
worker.rb
|
236
|
+
}
|
237
|
+
}
|
232
238
|
|
233
|
-
|
239
|
+
procpath.rb
|
234
240
|
|
235
|
-
|
236
|
-
|
241
|
+
migrate.rb
|
242
|
+
}
|
237
243
|
|
238
244
|
monitor.rb
|
239
245
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
6.0.0
|
data/lib/scout/exceptions.rb
CHANGED
@@ -71,8 +71,20 @@ end
|
|
71
71
|
|
72
72
|
class LockInterrupted < TryAgain; end
|
73
73
|
|
74
|
-
|
75
|
-
|
74
|
+
class ClosedStream < StandardError; end
|
75
|
+
|
76
|
+
class DoneProcessing < Exception
|
77
|
+
attr_accessor :pid
|
78
|
+
def initialize(pid = Process.pid)
|
79
|
+
@pid = pid
|
80
|
+
end
|
81
|
+
|
82
|
+
def message
|
83
|
+
"Done processing pid #{pid}"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
|
76
88
|
#class OpenGzipError < StandardError; end
|
77
89
|
#
|
78
90
|
#
|
data/lib/scout/log/color.rb
CHANGED
@@ -2,6 +2,7 @@ require_relative 'color_class'
|
|
2
2
|
require_relative '../indiferent_hash'
|
3
3
|
|
4
4
|
require 'term/ansicolor'
|
5
|
+
require 'colorist'
|
5
6
|
|
6
7
|
module Colorize
|
7
8
|
def self.colors=(colors)
|
@@ -9,7 +10,13 @@ module Colorize
|
|
9
10
|
end
|
10
11
|
|
11
12
|
def self.colors
|
12
|
-
@colors ||= IndiferentHash.setup(
|
13
|
+
@colors ||= IndiferentHash.setup(Hash[<<-EOF.split("\n").collect{|l| l.split(" ")}])
|
14
|
+
green #00cd00
|
15
|
+
red #cd0000
|
16
|
+
yellow #ffd700
|
17
|
+
blue #0000cd
|
18
|
+
path blue
|
19
|
+
EOF
|
13
20
|
end
|
14
21
|
|
15
22
|
def self.diverging_colors=(colors)
|
@@ -33,7 +40,6 @@ module Colorize
|
|
33
40
|
EOF
|
34
41
|
end
|
35
42
|
|
36
|
-
|
37
43
|
def self.from_name(color)
|
38
44
|
return color if color =~ /^#?[0-9A-F]+$/i
|
39
45
|
return colors[color.to_s] if colors.include?(color.to_s)
|
@@ -52,7 +58,7 @@ module Colorize
|
|
52
58
|
when 'blue'
|
53
59
|
colors["RoyalBlue"]
|
54
60
|
else
|
55
|
-
colors[color.to_s] || color
|
61
|
+
colors[color.to_s] || color.to_s
|
56
62
|
end
|
57
63
|
end
|
58
64
|
|
@@ -134,8 +140,21 @@ module Log
|
|
134
140
|
WHITE, DARK, GREEN, YELLOW, RED = Color::SOLARIZED.values_at :base0, :base00, :green, :yellow, :magenta
|
135
141
|
|
136
142
|
SEVERITY_COLOR = [reset, cyan, green, magenta, blue, yellow, red] #.collect{|e| "\033[#{e}"}
|
143
|
+
CONCEPT_COLORS = IndiferentHash.setup({
|
144
|
+
:title => magenta,
|
145
|
+
:path => blue,
|
146
|
+
:input => blue,
|
147
|
+
:value => green,
|
148
|
+
:integer => green,
|
149
|
+
:negative => red,
|
150
|
+
:float => green,
|
151
|
+
:waiting => yellow,
|
152
|
+
:started => blue,
|
153
|
+
:done => green,
|
154
|
+
:error => red,
|
155
|
+
})
|
137
156
|
HIGHLIGHT = "\033[1m"
|
138
|
-
|
157
|
+
|
139
158
|
def self.uncolor(str)
|
140
159
|
"" << Term::ANSIColor.uncolor(str)
|
141
160
|
end
|
@@ -144,15 +163,20 @@ module Log
|
|
144
163
|
reset
|
145
164
|
end
|
146
165
|
|
147
|
-
def self.color(
|
166
|
+
def self.color(color, str = nil, reset = false)
|
148
167
|
return str.dup || "" if nocolor
|
149
|
-
|
150
|
-
color
|
151
|
-
color
|
168
|
+
|
169
|
+
color = SEVERITY_COLOR[color] if Integer === color
|
170
|
+
color = CONCEPT_COLORS[color] if CONCEPT_COLORS.include?(color)
|
171
|
+
color = Term::ANSIColor.send(color) if Symbol === color and Term::ANSIColor.respond_to?(color)
|
172
|
+
|
173
|
+
return str if Symbol === color
|
174
|
+
color_str = reset ? Term::ANSIColor.reset : ""
|
175
|
+
color_str << color
|
152
176
|
if str.nil?
|
153
|
-
|
177
|
+
color_str
|
154
178
|
else
|
155
|
-
|
179
|
+
color_str + str.to_s + Term::ANSIColor.reset
|
156
180
|
end
|
157
181
|
end
|
158
182
|
|
@@ -195,15 +195,16 @@ module Log
|
|
195
195
|
def done(io = STDERR)
|
196
196
|
done_msg = Log.color(:magenta, "· ") << Log.color(:green, "done")
|
197
197
|
if @start
|
198
|
-
ellapsed = (Time.now - @start)
|
198
|
+
ellapsed = (Time.now - @start)
|
199
199
|
else
|
200
200
|
ellapsed = 0
|
201
201
|
end
|
202
|
-
|
203
|
-
done_msg << " " << Log.color(:blue, (@ticks).to_s) << " #{bytes ? 'bytes' : 'items'} in " << Log.color(:green,
|
202
|
+
ellapsed_str = [ellapsed/3600, ellapsed/60 % 60, ellapsed % 60].map{|t| "%02i" % t }.join(':')
|
203
|
+
done_msg << " " << Log.color(:blue, (@ticks).to_s) << " #{bytes ? 'bytes' : 'items'} in " << Log.color(:green, ellapsed_str)
|
204
204
|
@last_count = 0
|
205
205
|
@last_time = @start
|
206
|
-
|
206
|
+
thr = ellapsed > 0 ? (@ticks / ellapsed).to_i.to_s : 0
|
207
|
+
done_msg << " - " << Log.color(:blue, thr) << " per second"
|
207
208
|
done_msg << Log.color(:magenta, " · " << desc)
|
208
209
|
print(io, Log.up_lines(@depth) << done_msg << Log.down_lines(@depth))
|
209
210
|
|
data/lib/scout/misc/monitor.rb
CHANGED
@@ -20,4 +20,22 @@ module Misc
|
|
20
20
|
end
|
21
21
|
res
|
22
22
|
end
|
23
|
+
|
24
|
+
def self.profile(options = {})
|
25
|
+
require 'ruby-prof'
|
26
|
+
profiler = RubyProf::Profile.new
|
27
|
+
profiler.start
|
28
|
+
begin
|
29
|
+
res = yield
|
30
|
+
rescue Exception
|
31
|
+
puts "Profiling aborted"
|
32
|
+
raise $!
|
33
|
+
ensure
|
34
|
+
result = profiler.stop
|
35
|
+
printer = RubyProf::FlatPrinter.new(result)
|
36
|
+
printer.print(STDOUT, options)
|
37
|
+
end
|
38
|
+
|
39
|
+
res
|
40
|
+
end
|
23
41
|
end
|
data/lib/scout/open/stream.rb
CHANGED
@@ -370,4 +370,34 @@ module Open
|
|
370
370
|
def self.tee_stream(stream)
|
371
371
|
tee_stream_thread(stream)
|
372
372
|
end
|
373
|
+
|
374
|
+
def self.read_stream(stream, size)
|
375
|
+
str = nil
|
376
|
+
Thread.pass while IO.select([stream],nil,nil,1).nil?
|
377
|
+
while not str = stream.read(size)
|
378
|
+
IO.select([stream],nil,nil,1)
|
379
|
+
Thread.pass
|
380
|
+
raise ClosedStream if stream.eof?
|
381
|
+
end
|
382
|
+
|
383
|
+
while str.length < size
|
384
|
+
raise ClosedStream if stream.eof?
|
385
|
+
IO.select([stream],nil,nil,1)
|
386
|
+
if new = stream.read(size-str.length)
|
387
|
+
str << new
|
388
|
+
end
|
389
|
+
end
|
390
|
+
str
|
391
|
+
end
|
392
|
+
|
393
|
+
def self.read_stream(stream, size)
|
394
|
+
str = ""
|
395
|
+
while str.length < size
|
396
|
+
missing = size - str.length
|
397
|
+
more = stream.read(missing)
|
398
|
+
str << more
|
399
|
+
end
|
400
|
+
str
|
401
|
+
end
|
402
|
+
|
373
403
|
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
begin
|
2
|
+
require 'inline'
|
3
|
+
continue = true
|
4
|
+
rescue Exception
|
5
|
+
Log.warn "The RubyInline gem could not be loaded: semaphore synchronization will not work"
|
6
|
+
continue = false
|
7
|
+
end
|
8
|
+
|
9
|
+
if continue
|
10
|
+
module ScoutSemaphore
|
11
|
+
inline(:C) do |builder|
|
12
|
+
builder.prefix <<-EOF
|
13
|
+
#include <unistd.h>
|
14
|
+
#include <stdio.h>
|
15
|
+
#include <stdlib.h>
|
16
|
+
#include <semaphore.h>
|
17
|
+
#include <time.h>
|
18
|
+
#include <assert.h>
|
19
|
+
#include <errno.h>
|
20
|
+
#include <signal.h>
|
21
|
+
#include <fcntl.h>
|
22
|
+
EOF
|
23
|
+
|
24
|
+
builder.c_singleton <<-EOF
|
25
|
+
void create_semaphore(char* name, int value){
|
26
|
+
sem_open(name, O_CREAT, S_IRWXU|S_IRWXG|S_IRWXO, value);
|
27
|
+
}
|
28
|
+
EOF
|
29
|
+
builder.c_singleton <<-EOF
|
30
|
+
void delete_semaphore(char* name){
|
31
|
+
sem_unlink(name);
|
32
|
+
}
|
33
|
+
EOF
|
34
|
+
|
35
|
+
builder.c_singleton <<-EOF
|
36
|
+
int wait_semaphore(char* name){
|
37
|
+
int ret;
|
38
|
+
sem_t* sem;
|
39
|
+
sem = sem_open(name, 0);
|
40
|
+
ret = sem_wait(sem);
|
41
|
+
sem_close(sem);
|
42
|
+
return(ret);
|
43
|
+
}
|
44
|
+
EOF
|
45
|
+
|
46
|
+
builder.c_singleton <<-EOF
|
47
|
+
void post_semaphore(char* name){
|
48
|
+
sem_t* sem;
|
49
|
+
sem = sem_open(name, 0);
|
50
|
+
sem_post(sem);
|
51
|
+
sem_close(sem);
|
52
|
+
}
|
53
|
+
EOF
|
54
|
+
end
|
55
|
+
|
56
|
+
SEM_MUTEX = Mutex.new
|
57
|
+
def self.synchronize(sem)
|
58
|
+
ret = ScoutSemaphore.wait_semaphore(sem)
|
59
|
+
raise SemaphoreInterrupted if ret == -1
|
60
|
+
begin
|
61
|
+
yield
|
62
|
+
ensure
|
63
|
+
ScoutSemaphore.post_semaphore(sem)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.with_semaphore(size, file = nil)
|
68
|
+
if file.nil?
|
69
|
+
file = "/" << Misc.digest(rand(1000000000000).to_s) if file.nil?
|
70
|
+
else
|
71
|
+
file = file.gsub('/', '_') if file
|
72
|
+
end
|
73
|
+
|
74
|
+
begin
|
75
|
+
Log.debug "Creating semaphore (#{ size }): #{file}"
|
76
|
+
ScoutSemaphore.create_semaphore(file, size)
|
77
|
+
yield file
|
78
|
+
ensure
|
79
|
+
Log.debug "Removing semaphore #{ file }"
|
80
|
+
ScoutSemaphore.delete_semaphore(file)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.fork_each_on_semaphore(elems, size, file = nil)
|
85
|
+
|
86
|
+
TSV.traverse elems, :cpus => size, :bar => "Fork each on semaphore: #{ Misc.fingerprint elems }", :into => Set.new do |elem|
|
87
|
+
elems.annotate elem if elems.respond_to? :annotate
|
88
|
+
begin
|
89
|
+
yield elem
|
90
|
+
rescue Interrupt
|
91
|
+
Log.warn "Process #{Process.pid} was aborted"
|
92
|
+
end
|
93
|
+
nil
|
94
|
+
end
|
95
|
+
nil
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.thread_each_on_semaphore(elems, size)
|
99
|
+
mutex = Mutex.new
|
100
|
+
count = 0
|
101
|
+
cv = ConditionVariable.new
|
102
|
+
wait_mutex = Mutex.new
|
103
|
+
|
104
|
+
begin
|
105
|
+
|
106
|
+
threads = []
|
107
|
+
wait_mutex.synchronize do
|
108
|
+
threads = elems.collect do |elem|
|
109
|
+
Thread.new(elem) do |elem|
|
110
|
+
|
111
|
+
continue = false
|
112
|
+
mutex.synchronize do
|
113
|
+
while not continue do
|
114
|
+
if count < size
|
115
|
+
continue = true
|
116
|
+
count += 1
|
117
|
+
end
|
118
|
+
mutex.sleep 1 unless continue
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
begin
|
123
|
+
yield elem
|
124
|
+
rescue Interrupt
|
125
|
+
Log.error "Thread was aborted while processing: #{Misc.fingerprint elem}"
|
126
|
+
raise $!
|
127
|
+
ensure
|
128
|
+
mutex.synchronize do
|
129
|
+
count -= 1
|
130
|
+
cv.signal if mutex.locked?
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
threads.each do |thread|
|
138
|
+
thread.join
|
139
|
+
end
|
140
|
+
rescue Exception
|
141
|
+
Log.exception $!
|
142
|
+
Log.info "Ensuring threads are dead: #{threads.length}"
|
143
|
+
threads.each do |thread| thread.kill end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'scout/open'
|
2
|
+
require 'scout/semaphore'
|
3
|
+
require 'scout/exceptions'
|
4
|
+
class WorkQueue
|
5
|
+
class Socket
|
6
|
+
attr_accessor :sread, :swrite, :write_sem, :read_sem, :cleaned
|
7
|
+
def initialize(serializer = nil)
|
8
|
+
@sread, @swrite = Open.pipe
|
9
|
+
|
10
|
+
@serializer = serializer || Marshal
|
11
|
+
|
12
|
+
@key = "/" << rand(1000000000).to_s << '.' << Process.pid.to_s;
|
13
|
+
@write_sem = @key + '.in'
|
14
|
+
@read_sem = @key + '.out'
|
15
|
+
Log.debug "Creating socket semaphores: #{@key}"
|
16
|
+
ScoutSemaphore.create_semaphore(@write_sem,1)
|
17
|
+
ScoutSemaphore.create_semaphore(@read_sem,1)
|
18
|
+
end
|
19
|
+
|
20
|
+
def clean
|
21
|
+
@cleaned = true
|
22
|
+
@sread.close unless @sread.closed?
|
23
|
+
@swrite.close unless @swrite.closed?
|
24
|
+
Log.low "Destroying socket semaphores: #{[@key] * ", "}"
|
25
|
+
ScoutSemaphore.delete_semaphore(@write_sem)
|
26
|
+
ScoutSemaphore.delete_semaphore(@read_sem)
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
def dump(obj)
|
31
|
+
stream = @swrite
|
32
|
+
obj.concurrent_stream = nil if obj.respond_to?(:concurrent_stream)
|
33
|
+
case obj
|
34
|
+
when Integer
|
35
|
+
size_head = [obj,"I"].pack 'La'
|
36
|
+
str = size_head
|
37
|
+
when nil
|
38
|
+
size_head = [0,"N"].pack 'La'
|
39
|
+
str = size_head
|
40
|
+
when String
|
41
|
+
payload = obj
|
42
|
+
size_head = [payload.bytesize,"C"].pack 'La'
|
43
|
+
str = size_head << payload
|
44
|
+
else
|
45
|
+
payload = @serializer.dump(obj)
|
46
|
+
size_head = [payload.bytesize,"S"].pack 'La'
|
47
|
+
str = size_head << payload
|
48
|
+
end
|
49
|
+
|
50
|
+
write_length = str.length
|
51
|
+
wrote = stream.write(str)
|
52
|
+
while wrote < write_length
|
53
|
+
wrote += stream.write(str[wrote..-1])
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def load
|
58
|
+
stream = @sread
|
59
|
+
size_head = Open.read_stream stream, 5
|
60
|
+
|
61
|
+
size, type = size_head.unpack('La')
|
62
|
+
|
63
|
+
return nil if type == "N"
|
64
|
+
return size.to_i if type == "I"
|
65
|
+
begin
|
66
|
+
payload = Open.read_stream stream, size
|
67
|
+
case type
|
68
|
+
when "S"
|
69
|
+
begin
|
70
|
+
@serializer.load(payload)
|
71
|
+
rescue Exception
|
72
|
+
Log.exception $!
|
73
|
+
raise $!
|
74
|
+
end
|
75
|
+
when "C"
|
76
|
+
payload
|
77
|
+
end
|
78
|
+
rescue TryAgain
|
79
|
+
retry
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def closed_read?
|
84
|
+
@sread.closed?
|
85
|
+
end
|
86
|
+
|
87
|
+
def closed_write?
|
88
|
+
@swrite.closed?
|
89
|
+
end
|
90
|
+
|
91
|
+
def close_write
|
92
|
+
self.dump ClosedStream.new
|
93
|
+
@swrite.close unless closed_write?
|
94
|
+
end
|
95
|
+
|
96
|
+
def close_read
|
97
|
+
@sread.close unless closed_read?
|
98
|
+
end
|
99
|
+
|
100
|
+
#{{{ ACCESSOR
|
101
|
+
def push(obj)
|
102
|
+
ScoutSemaphore.synchronize(@write_sem) do
|
103
|
+
self.dump(obj)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def pop
|
108
|
+
ScoutSemaphore.synchronize(@read_sem) do
|
109
|
+
res = self.load
|
110
|
+
raise res if ClosedStream === res
|
111
|
+
res
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
alias write push
|
116
|
+
|
117
|
+
alias read pop
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
class WorkQueue
|
2
|
+
class Worker
|
3
|
+
attr_accessor :pid, :ignore_ouput
|
4
|
+
def initialize
|
5
|
+
end
|
6
|
+
|
7
|
+
def run
|
8
|
+
@pid = Process.fork do
|
9
|
+
yield
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def process(input, output, &block)
|
14
|
+
run do
|
15
|
+
begin
|
16
|
+
while obj = input.read
|
17
|
+
if DoneProcessing === obj
|
18
|
+
output.write DoneProcessing.new
|
19
|
+
raise obj
|
20
|
+
end
|
21
|
+
res = block.call obj
|
22
|
+
output.write res unless ignore_ouput || res == :ignore
|
23
|
+
end
|
24
|
+
rescue DoneProcessing
|
25
|
+
Log.log "Worker #{Process.pid} done"
|
26
|
+
rescue Exception
|
27
|
+
Log.exception $!
|
28
|
+
exit -1
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def join
|
34
|
+
Log.log "Joining worker #{@pid}"
|
35
|
+
Process.waitpid @pid
|
36
|
+
end
|
37
|
+
|
38
|
+
def exit(status)
|
39
|
+
Log.log "Worker #{@pid} exited with status #{Log.color(:green, status)}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.join(workers)
|
43
|
+
workers = [workers] unless Array === workers
|
44
|
+
begin
|
45
|
+
while pid = Process.wait
|
46
|
+
status = $?
|
47
|
+
worker = workers.select{|w| w.pid == pid }.first
|
48
|
+
worker.exit status.exitstatus if worker
|
49
|
+
end
|
50
|
+
rescue Errno::ECHILD
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require_relative 'work_queue/socket'
|
2
|
+
require_relative 'work_queue/worker'
|
3
|
+
|
4
|
+
class WorkQueue
|
5
|
+
attr_accessor :workers, :worker_proc, :callback
|
6
|
+
|
7
|
+
def initialize(workers = 0, &block)
|
8
|
+
@input = WorkQueue::Socket.new
|
9
|
+
@output = WorkQueue::Socket.new
|
10
|
+
@workers = workers.times.collect{ Worker.new }
|
11
|
+
@worker_proc = block
|
12
|
+
@worker_mutex = Mutex.new
|
13
|
+
@removed_workers = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_worker(&block)
|
17
|
+
worker = Worker.new
|
18
|
+
@worker_mutex.synchronize do
|
19
|
+
@workers.push(worker)
|
20
|
+
if block_given?
|
21
|
+
worker.process @input, @output, &block
|
22
|
+
else
|
23
|
+
worker.process @input, @output, &@worker_proc
|
24
|
+
end
|
25
|
+
end
|
26
|
+
worker
|
27
|
+
end
|
28
|
+
|
29
|
+
def ignore_ouput
|
30
|
+
@workers.each{|w| w.ignore_ouput = true }
|
31
|
+
end
|
32
|
+
|
33
|
+
def remove_one_worker
|
34
|
+
@input.write DoneProcessing.new
|
35
|
+
end
|
36
|
+
|
37
|
+
def remove_worker(pid)
|
38
|
+
worker = @worker_mutex.synchronize do
|
39
|
+
Log.debug "Remove #{pid}"
|
40
|
+
@removed_workers.concat(@workers.delete_if{|w| w.pid == pid })
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def process(&callback)
|
45
|
+
@workers.each do |w|
|
46
|
+
w.process @input, @output, &@worker_proc
|
47
|
+
end
|
48
|
+
@reader = Thread.new do
|
49
|
+
begin
|
50
|
+
while true
|
51
|
+
obj = @output.read
|
52
|
+
if DoneProcessing === obj
|
53
|
+
remove_worker obj.pid if obj.pid
|
54
|
+
else
|
55
|
+
callback.call obj if callback
|
56
|
+
end
|
57
|
+
end
|
58
|
+
rescue Aborted
|
59
|
+
end
|
60
|
+
end if @output
|
61
|
+
end
|
62
|
+
|
63
|
+
def write(obj)
|
64
|
+
@input.write obj
|
65
|
+
end
|
66
|
+
|
67
|
+
def close
|
68
|
+
while @worker_mutex.synchronize{ @workers.length } > 0
|
69
|
+
begin
|
70
|
+
@input.write DoneProcessing.new
|
71
|
+
pid = Process.wait
|
72
|
+
status = $?
|
73
|
+
worker = @worker_mutex.synchronize{ @removed_workers.delete_if{|w| w.pid == pid }.first }
|
74
|
+
worker.exit $?.exitstatus if worker
|
75
|
+
rescue Errno::ECHILD
|
76
|
+
Thread.pass until @workers.length == 0
|
77
|
+
break
|
78
|
+
end
|
79
|
+
end
|
80
|
+
@reader.raise Aborted if @reader
|
81
|
+
end
|
82
|
+
|
83
|
+
def join
|
84
|
+
@reader.join if @reader
|
85
|
+
end
|
86
|
+
end
|
@@ -55,9 +55,9 @@ class Step
|
|
55
55
|
|
56
56
|
def report_status(status, message = nil)
|
57
57
|
if message.nil?
|
58
|
-
Log.info Log.color(
|
58
|
+
Log.info Log.color(status, status.to_s) + " " + Log.color(:path, path)
|
59
59
|
else
|
60
|
-
Log.info Log.color(
|
60
|
+
Log.info Log.color(status, status.to_s) + " " + Log.color(:path, path) + " " + message
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|