pwrake 0.9.7 → 0.9.8
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/lib/pwrake/application.rb +1 -0
- data/lib/pwrake/cache_aware_queue.rb +184 -0
- data/lib/pwrake/gfarm_feature.rb +5 -33
- data/lib/pwrake/gfwhere_pool.rb +112 -0
- data/lib/pwrake/locality_aware_queue.rb +15 -13
- data/lib/pwrake/master.rb +13 -5
- data/lib/pwrake/option.rb +14 -9
- data/lib/pwrake/report/parallelism.rb +154 -29
- data/lib/pwrake/report/report.rb +14 -6
- data/lib/pwrake/report/report_multi.rb +2 -2
- data/lib/pwrake/shell.rb +13 -17
- data/lib/pwrake/task_algorithm.rb +136 -15
- data/lib/pwrake/task_queue.rb +310 -67
- data/lib/pwrake/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d50d7fd5c00627f0eea3d59e26632c4e9e4c0f6d
|
4
|
+
data.tar.gz: 3d9d6638a9dde0ce9feebc1ba210d9ce1d94b310
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c7517d29d640a06db61bc8a656615d43706564c49596d594830cadbbe0134b79bfe71c542e182de003baff492b58008bcb90e60b97aab587de1460f21cd99ce6
|
7
|
+
data.tar.gz: 99f101b28c359ea3157caf688205da6734bb95569dd1eea6fed5cc6787e6b1f9a7bd01a35ab1e7dd694e2782c0969480f4abea1d18bc68d963bc85658d9432c6
|
data/lib/pwrake/application.rb
CHANGED
@@ -0,0 +1,184 @@
|
|
1
|
+
module Pwrake
|
2
|
+
|
3
|
+
class MTimePriorityArray < Array
|
4
|
+
|
5
|
+
def order_by(item)
|
6
|
+
item.input_file_mtime
|
7
|
+
end
|
8
|
+
|
9
|
+
def push(t)
|
10
|
+
order_value = order_by(t)
|
11
|
+
if empty? || order_by(last) <= order_value
|
12
|
+
super(t)
|
13
|
+
elsif order_by(first) > order_value
|
14
|
+
unshift(t)
|
15
|
+
else
|
16
|
+
lower = 0
|
17
|
+
upper = size-1
|
18
|
+
while lower+1 < upper
|
19
|
+
mid = ((lower + upper) / 2).to_i
|
20
|
+
if order_by(self[mid]) <= order_value
|
21
|
+
lower = mid
|
22
|
+
else
|
23
|
+
upper = mid
|
24
|
+
end
|
25
|
+
end
|
26
|
+
insert(upper,t)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def index(t)
|
31
|
+
if size < 40
|
32
|
+
return super(t)
|
33
|
+
end
|
34
|
+
order_value = t.order_by
|
35
|
+
if order_by(last) < order_value || order_by(first) > order_value
|
36
|
+
nil
|
37
|
+
else
|
38
|
+
lower = 0
|
39
|
+
upper = size-1
|
40
|
+
while lower+1 < upper
|
41
|
+
mid = ((lower + upper) / 2).to_i
|
42
|
+
if order_by(self[mid]) < order_value
|
43
|
+
lower = mid
|
44
|
+
else
|
45
|
+
upper = mid
|
46
|
+
end
|
47
|
+
end
|
48
|
+
mid = upper
|
49
|
+
if order_by(self[mid]) == order_value
|
50
|
+
Log.debug "--- TQA#index=#{mid}, order_value=#{order_value}"
|
51
|
+
mid
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
# rank-Even Last In First Out
|
59
|
+
class CacheAwareQueue
|
60
|
+
def initialize(n)
|
61
|
+
@q = []
|
62
|
+
@i = 0
|
63
|
+
@size = 0
|
64
|
+
@n = (n>0) ? n : 1
|
65
|
+
end
|
66
|
+
|
67
|
+
def push(t)
|
68
|
+
r = t.rank
|
69
|
+
a = @q[r]
|
70
|
+
if a.nil?
|
71
|
+
@q[r] = a = MTimePriorityArray.new
|
72
|
+
end
|
73
|
+
@size += 1
|
74
|
+
a.push(t)
|
75
|
+
end
|
76
|
+
|
77
|
+
def size
|
78
|
+
@size
|
79
|
+
end
|
80
|
+
|
81
|
+
def empty?
|
82
|
+
@size == 0
|
83
|
+
end
|
84
|
+
|
85
|
+
def shift
|
86
|
+
if empty?
|
87
|
+
return nil
|
88
|
+
elsif @size < @n*2
|
89
|
+
return shift_high_rank
|
90
|
+
else
|
91
|
+
return shift_weighted
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def shift_high_rank
|
96
|
+
(@q.size-1).downto(0) do |i|
|
97
|
+
a = @q[i]
|
98
|
+
next if a.nil? || a.empty?
|
99
|
+
@size -= 1
|
100
|
+
return pop_last_max(a)
|
101
|
+
end
|
102
|
+
nil
|
103
|
+
end
|
104
|
+
|
105
|
+
def shift_weighted
|
106
|
+
weight, weight_avg = RANK_STAT.rank_weight
|
107
|
+
wsum = 0.0
|
108
|
+
q = []
|
109
|
+
@q.each_with_index do |a,i|
|
110
|
+
next if a.nil? || a.empty?
|
111
|
+
w = weight[i]
|
112
|
+
w = weight_avg if w.nil?
|
113
|
+
# w *= a.size
|
114
|
+
wsum += w
|
115
|
+
q << [a,wsum]
|
116
|
+
end
|
117
|
+
#
|
118
|
+
x = rand() * wsum
|
119
|
+
Log.debug "--- shift_weighted x=#{x} wsum=#{wsum} weight=#{weight.inspect}"
|
120
|
+
@size -= 1
|
121
|
+
q.each do |a,w|
|
122
|
+
if w > x
|
123
|
+
return a.pop
|
124
|
+
end
|
125
|
+
end
|
126
|
+
raise "ELIFO: wsum=#{wsum} x=#{x}"
|
127
|
+
end
|
128
|
+
|
129
|
+
def pop_last_max(a)
|
130
|
+
if a.size < 2
|
131
|
+
return a.pop
|
132
|
+
end
|
133
|
+
y_max = nil
|
134
|
+
i_max = nil
|
135
|
+
n = [@n, a.size].min
|
136
|
+
(-n..-1).each do |i|
|
137
|
+
y = a[i].input_file_size
|
138
|
+
if y_max.nil? || y > y_max
|
139
|
+
y_max = y
|
140
|
+
i_max = i
|
141
|
+
end
|
142
|
+
end
|
143
|
+
a.delete_at(i_max)
|
144
|
+
end
|
145
|
+
|
146
|
+
def first
|
147
|
+
return nil if empty?
|
148
|
+
@q.size.times do |i|
|
149
|
+
a = @q[i]
|
150
|
+
unless a.nil? || a.empty?
|
151
|
+
return a.first
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def last
|
157
|
+
return nil if empty?
|
158
|
+
@q.size.times do |i|
|
159
|
+
a = @q[-i-1]
|
160
|
+
unless a.nil? || a.empty?
|
161
|
+
return a.last
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def delete(t)
|
167
|
+
n = 0
|
168
|
+
@q.each do |a|
|
169
|
+
if a
|
170
|
+
a.delete(t)
|
171
|
+
n += a.size
|
172
|
+
end
|
173
|
+
end
|
174
|
+
@size = n
|
175
|
+
end
|
176
|
+
|
177
|
+
def clear
|
178
|
+
@q.clear
|
179
|
+
@i = 0
|
180
|
+
@size = 0
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
data/lib/pwrake/gfarm_feature.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'pwrake/gfwhere_pool'
|
2
|
+
|
1
3
|
module Pwrake
|
2
4
|
|
3
5
|
module GfarmPath
|
@@ -207,43 +209,13 @@ module Pwrake
|
|
207
209
|
class GfarmPostprocess
|
208
210
|
|
209
211
|
def initialize
|
210
|
-
|
211
|
-
@
|
212
|
-
@io.sync = true
|
213
|
-
end
|
214
|
-
|
215
|
-
def gfwhere(file)
|
216
|
-
return [] if file==''
|
217
|
-
@lock.synchronize do
|
218
|
-
@io.puts(file)
|
219
|
-
@io.flush
|
220
|
-
s = @io.gets
|
221
|
-
if s.nil?
|
222
|
-
raise "gfwhere: unexpected end"
|
223
|
-
end
|
224
|
-
s.chomp!
|
225
|
-
if s != file
|
226
|
-
raise "gfwhere: file=#{file}, result=#{s}"
|
227
|
-
end
|
228
|
-
while s = @io.gets
|
229
|
-
s.chomp!
|
230
|
-
case s
|
231
|
-
when ""
|
232
|
-
next
|
233
|
-
when /^gfarm:\/\//
|
234
|
-
next
|
235
|
-
when /^Error:/
|
236
|
-
return []
|
237
|
-
else
|
238
|
-
return s.split(/\s+/)
|
239
|
-
end
|
240
|
-
end
|
241
|
-
end
|
212
|
+
max = Pwrake.application.pwrake_options['MAX_GFWHERE_WORKER']
|
213
|
+
@gfwhere_pool = WorkerPool.new(GfwhereWorker,max)
|
242
214
|
end
|
243
215
|
|
244
216
|
def postprocess(t)
|
245
217
|
if t.kind_of? Rake::FileTask
|
246
|
-
t.location =
|
218
|
+
t.location = @gfwhere_pool.work(t.name)
|
247
219
|
end
|
248
220
|
end
|
249
221
|
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module Pwrake
|
2
|
+
|
3
|
+
class WorkerPool
|
4
|
+
|
5
|
+
def initialize(wk_class, max)
|
6
|
+
@worker_class = wk_class
|
7
|
+
@max = max
|
8
|
+
@pool = []
|
9
|
+
@cond_pool = ConditionVariable.new
|
10
|
+
@mutex = Mutex.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def find_worker
|
14
|
+
@mutex.synchronize do
|
15
|
+
while true
|
16
|
+
@pool.each do |w|
|
17
|
+
return w if w.acquire
|
18
|
+
end
|
19
|
+
if @pool.size < @max
|
20
|
+
Log.debug "--- #{@worker_class}:new_worker #{@pool.size+1}"
|
21
|
+
w = @worker_class.new(@cond_pool)
|
22
|
+
@pool << w
|
23
|
+
return w if w.acquire
|
24
|
+
end
|
25
|
+
# wait for end of work in @pool
|
26
|
+
print "wait\n"
|
27
|
+
@cond_pool.wait(@mutex)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def work(*args)
|
33
|
+
w = find_worker
|
34
|
+
w.run(*args)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
class Worker
|
40
|
+
|
41
|
+
def initialize(cond_pool)
|
42
|
+
@cond_pool = cond_pool
|
43
|
+
@mutex = Mutex.new
|
44
|
+
@aquired = false
|
45
|
+
end
|
46
|
+
|
47
|
+
def acquire
|
48
|
+
return false if @mutex.locked?
|
49
|
+
if @aquired
|
50
|
+
return false
|
51
|
+
else
|
52
|
+
@aquired = true
|
53
|
+
return true
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def run(*args)
|
58
|
+
@mutex.synchronize do
|
59
|
+
raise "no aquired" unless @aquired
|
60
|
+
r = work(*args)
|
61
|
+
@aquired = false
|
62
|
+
@cond_pool.signal
|
63
|
+
return r
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def work(*args)
|
68
|
+
# inplement in subclass
|
69
|
+
return nil
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
class GfwhereWorker < Worker
|
75
|
+
|
76
|
+
def initialize(cond_pool)
|
77
|
+
super(cond_pool)
|
78
|
+
@io = IO.popen('gfwhere-pipe','r+')
|
79
|
+
@io.sync = true
|
80
|
+
end
|
81
|
+
|
82
|
+
def work(file)
|
83
|
+
return [] if file==''
|
84
|
+
t = Time.now
|
85
|
+
@io.puts(file)
|
86
|
+
@io.flush
|
87
|
+
s = @io.gets
|
88
|
+
if s.nil?
|
89
|
+
raise "gfwhere: unexpected end"
|
90
|
+
end
|
91
|
+
s.chomp!
|
92
|
+
if s != file
|
93
|
+
raise "gfwhere: file=#{file}, result=#{s}"
|
94
|
+
end
|
95
|
+
while s = @io.gets
|
96
|
+
s.chomp!
|
97
|
+
case s
|
98
|
+
when ""
|
99
|
+
next
|
100
|
+
when /^gfarm:\/\//
|
101
|
+
next
|
102
|
+
when /^Error:/
|
103
|
+
return []
|
104
|
+
else
|
105
|
+
Log.debug "gfwhere:path %.6f sec, file=%s" % [Time.now-t,file]
|
106
|
+
return s.split(/\s+/)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
@@ -131,14 +131,16 @@ module Pwrake
|
|
131
131
|
end # class Throughput
|
132
132
|
|
133
133
|
|
134
|
-
def init_queue(
|
134
|
+
def init_queue(core_list)
|
135
|
+
@host_count = Hash.new{0}
|
136
|
+
core_list.each{|h| @host_count[h] += 1}
|
137
|
+
@hosts = @host_count.keys
|
135
138
|
@cv = LocalityConditionVariable.new
|
136
|
-
@hosts = hosts
|
137
139
|
@throughput = Throughput.new
|
138
140
|
@size = 0
|
139
141
|
@q = {}
|
140
|
-
@
|
141
|
-
@q_remote = @array_class.new
|
142
|
+
@host_count.each{|h,n| @q[h] = @array_class.new(n)}
|
143
|
+
@q_remote = @array_class.new(0)
|
142
144
|
@q_later = Array.new
|
143
145
|
@enable_steal = !Pwrake.application.pwrake_options['DISABLE_STEAL']
|
144
146
|
@steal_wait = (Pwrake.application.pwrake_options['STEAL_WAIT'] || 0).to_i
|
@@ -213,10 +215,7 @@ module Pwrake
|
|
213
215
|
if q && !q.empty?
|
214
216
|
t = q.shift
|
215
217
|
t.assigned.each do |h|
|
216
|
-
|
217
|
-
if i = a.index(t)
|
218
|
-
a.delete_at(i)
|
219
|
-
end
|
218
|
+
@q[h].delete(t)
|
220
219
|
end
|
221
220
|
@size -= 1
|
222
221
|
return t
|
@@ -250,11 +249,14 @@ module Pwrake
|
|
250
249
|
when 0
|
251
250
|
s += "[]\n"
|
252
251
|
when 1
|
253
|
-
s += "[#{q
|
252
|
+
s += "[#{q.first.name}]\n"
|
253
|
+
when 2
|
254
|
+
s += "[#{q.first.name}, #{q.last.name}]\n"
|
254
255
|
else
|
255
|
-
s += "[#{q
|
256
|
+
s += "[#{q.first.name},.. #{q.last.name}]\n"
|
256
257
|
end
|
257
258
|
}
|
259
|
+
b.call("noaction",@q_noaction)
|
258
260
|
@q.each(&b)
|
259
261
|
b.call("remote",@q_remote)
|
260
262
|
b.call("later",@q_later)
|
@@ -266,17 +268,17 @@ module Pwrake
|
|
266
268
|
end
|
267
269
|
|
268
270
|
def clear
|
271
|
+
@q_noaction.clear
|
269
272
|
@q.each{|h,q| q.clear}
|
270
273
|
@q_remote.clear
|
271
274
|
@q_later.clear
|
272
|
-
@reserved_q.clear
|
273
275
|
end
|
274
276
|
|
275
277
|
def empty?
|
276
278
|
@q.all?{|h,q| q.empty?} &&
|
279
|
+
@q_noaction.empty? &&
|
277
280
|
@q_remote.empty? &&
|
278
|
-
@q_later.empty?
|
279
|
-
@reserved_q.empty?
|
281
|
+
@q_later.empty?
|
280
282
|
end
|
281
283
|
|
282
284
|
def finish
|
data/lib/pwrake/master.rb
CHANGED
@@ -22,19 +22,22 @@ module Pwrake
|
|
22
22
|
attr_reader :postprocess
|
23
23
|
|
24
24
|
def initialize
|
25
|
-
init_option # Pwrake::Option
|
26
|
-
setup_option # Pwrake::Option
|
27
25
|
@started = false
|
28
26
|
@lock = Mutex.new
|
29
27
|
@current_task_id = -1
|
30
28
|
end
|
31
29
|
|
30
|
+
def init
|
31
|
+
init_option # Pwrake::Option
|
32
|
+
setup_option # Pwrake::Option
|
33
|
+
end
|
34
|
+
|
32
35
|
def start
|
33
36
|
return if @task_queue
|
34
37
|
timer = Timer.new("start_worker")
|
35
38
|
@finish_queue = Queue.new
|
36
39
|
@task_queue = @queue_class.new(@core_list)
|
37
|
-
@shell_set =
|
40
|
+
@shell_set = (1..@num_noaction_threads).map{ NoActionShell.new }
|
38
41
|
@core_list.each_with_index do |h,i|
|
39
42
|
@shell_set << @shell_class.new(h,@shell_opt)
|
40
43
|
end
|
@@ -53,6 +56,7 @@ module Pwrake
|
|
53
56
|
def start_threads
|
54
57
|
Thread.abort_on_exception = true
|
55
58
|
@threads = []
|
59
|
+
@exit_task = {}
|
56
60
|
t_intvl = Pwrake.application.pwrake_options['THREAD_CREATE_INTERVAL']
|
57
61
|
@shell_set.each do |c|
|
58
62
|
tc0 = Time.now
|
@@ -75,7 +79,9 @@ module Pwrake
|
|
75
79
|
end
|
76
80
|
|
77
81
|
def thread_loop(conn,last=nil)
|
78
|
-
|
82
|
+
if last
|
83
|
+
@exit_task[last] = Thread.current
|
84
|
+
end
|
79
85
|
hint = (conn) ? conn.host : nil
|
80
86
|
standard_exception_handling do
|
81
87
|
while true
|
@@ -85,7 +91,9 @@ module Pwrake
|
|
85
91
|
time_deq = Time.now - time_start
|
86
92
|
Log.debug "--- Master#thread_loop deq t=#{t.inspect} time=#{time_deq}sec"
|
87
93
|
t.pw_invoke
|
88
|
-
|
94
|
+
if th = @exit_task.delete(t)
|
95
|
+
@task_queue.thread_end(th)
|
96
|
+
end
|
89
97
|
end
|
90
98
|
end
|
91
99
|
end
|
data/lib/pwrake/option.rb
CHANGED
@@ -12,9 +12,9 @@ module Pwrake
|
|
12
12
|
|
13
13
|
def parse_opt(s)
|
14
14
|
case s
|
15
|
-
when
|
15
|
+
when /^(false|nil|off)$/i
|
16
16
|
false
|
17
|
-
when
|
17
|
+
when /^(true|on)$/i
|
18
18
|
true
|
19
19
|
else
|
20
20
|
s
|
@@ -41,10 +41,11 @@ module Pwrake
|
|
41
41
|
'DEBUG',
|
42
42
|
'PLOT_PARALLELISM',
|
43
43
|
'HALT_QUEUE_WHILE_SEARCH',
|
44
|
-
'THREAD_CREATE_INTERVAL',
|
45
44
|
'SHOW_CONF',
|
46
45
|
'FAILED_TARGET', # rename(default), delete, leave
|
47
|
-
'QUEUE_PRIORITY', #
|
46
|
+
'QUEUE_PRIORITY', # RANK(default), FIFO, LIFO, DFS
|
47
|
+
'NOACTION_QUEUE_PRIORITY', # FIFO(default), LIFO, RAND
|
48
|
+
'NUM_NOACTION_THREADS', # default=4 when gfarm, else 1
|
48
49
|
'STEAL_WAIT',
|
49
50
|
'STEAL_WAIT_MAX',
|
50
51
|
|
@@ -88,16 +89,18 @@ module Pwrake
|
|
88
89
|
end
|
89
90
|
}],
|
90
91
|
['NUM_THREADS', proc{|v| v && v.to_i}],
|
92
|
+
['THREAD_CREATE_INTERVAL', proc{|v| (v || 0.010).to_f}],
|
91
93
|
['DISABLE_AFFINITY', proc{|v| v || ENV['AFFINITY']=='off'}],
|
92
94
|
['DISABLE_STEAL', proc{|v| v || ENV['STEAL']=='off'}],
|
93
95
|
['GFARM_BASEDIR', proc{|v| v || '/tmp'}],
|
94
96
|
['GFARM_PREFIX', proc{|v| v || "pwrake_#{ENV['USER']}"}],
|
95
97
|
['GFARM_SUBDIR', proc{|v| v || '/'}],
|
96
|
-
|
98
|
+
['MAX_GFWHERE_WORKER', proc{|v| (v || 8).to_i}],
|
99
|
+
['MASTER_HOSTNAME', proc{|v| (v || begin;`hostname -f`;rescue;end || '').chomp}],
|
97
100
|
['WORK_DIR',proc{|v|
|
98
101
|
v ||= '$HOME/%CWD_RELATIVE_TO_HOME'
|
99
102
|
v.sub('%CWD_RELATIVE_TO_HOME',cwd_relative_to_home)
|
100
|
-
}]
|
103
|
+
}],
|
101
104
|
]
|
102
105
|
end
|
103
106
|
|
@@ -155,9 +158,7 @@ module Pwrake
|
|
155
158
|
@yaml = open(@pwrake_conf){|f| YAML.load(f) }
|
156
159
|
end
|
157
160
|
|
158
|
-
@opts = {'PWRAKE_CONF' => @pwrake_conf,
|
159
|
-
'THREAD_CREATE_INTERVAL' => 0.006,
|
160
|
-
}
|
161
|
+
@opts = { 'PWRAKE_CONF' => @pwrake_conf, }
|
161
162
|
|
162
163
|
option_data.each do |a|
|
163
164
|
prc = nil
|
@@ -376,6 +377,8 @@ module Pwrake
|
|
376
377
|
end
|
377
378
|
end
|
378
379
|
|
380
|
+
n_noaction_th = @opts['NUM_NOACTION_THREADS']
|
381
|
+
|
379
382
|
case @filesystem
|
380
383
|
when 'gfarm'
|
381
384
|
require "pwrake/locality_aware_queue"
|
@@ -394,12 +397,14 @@ module Pwrake
|
|
394
397
|
else
|
395
398
|
@queue_class = LocalityAwareQueue
|
396
399
|
end
|
400
|
+
@num_noaction_threads = (n_noaction_th || [8,@num_threads].max).to_i
|
397
401
|
@postprocess = GfarmPostprocess.new
|
398
402
|
Log.debug "--- @queue_class=#{@queue_class}"
|
399
403
|
else
|
400
404
|
@filesystem = 'nfs'
|
401
405
|
@shell_class = Shell
|
402
406
|
@queue_class = TaskQueue
|
407
|
+
@num_noaction_threads = (n_noaction_th || 1).to_i
|
403
408
|
end
|
404
409
|
end
|
405
410
|
|