pwrake 0.9.7 → 0.9.8
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|