pwrake 0.9.9.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/CHANGES_V2.md +90 -0
  4. data/{LICENSE.txt → MIT-LICENSE} +2 -3
  5. data/README +12 -0
  6. data/README.md +75 -52
  7. data/bin/gfwhere-pipe +23 -12
  8. data/bin/pwrake +22 -29
  9. data/bin/pwrake_branch +24 -0
  10. data/lib/pwrake/branch.rb +22 -0
  11. data/lib/pwrake/branch/branch.rb +213 -0
  12. data/lib/pwrake/branch/branch_application.rb +53 -0
  13. data/lib/pwrake/branch/fiber_queue.rb +36 -0
  14. data/lib/pwrake/branch/file_utils.rb +101 -0
  15. data/lib/pwrake/branch/shell.rb +231 -0
  16. data/lib/pwrake/{profiler.rb → branch/shell_profiler.rb} +28 -27
  17. data/lib/pwrake/branch/worker_communicator.rb +104 -0
  18. data/lib/pwrake/{gfarm_feature.rb → gfarm/gfarm_path.rb} +2 -100
  19. data/lib/pwrake/gfarm/gfarm_postprocess.rb +53 -0
  20. data/lib/pwrake/iomux/channel.rb +70 -0
  21. data/lib/pwrake/iomux/handler.rb +124 -0
  22. data/lib/pwrake/iomux/handler_set.rb +35 -0
  23. data/lib/pwrake/iomux/runner.rb +62 -0
  24. data/lib/pwrake/logger.rb +3 -150
  25. data/lib/pwrake/master.rb +30 -137
  26. data/lib/pwrake/master/fiber_pool.rb +69 -0
  27. data/lib/pwrake/master/idle_cores.rb +30 -0
  28. data/lib/pwrake/master/master.rb +345 -0
  29. data/lib/pwrake/master/master_application.rb +150 -0
  30. data/lib/pwrake/master/postprocess.rb +16 -0
  31. data/lib/pwrake/{graphviz.rb → misc/graphviz.rb} +0 -0
  32. data/lib/pwrake/{mcgp.rb → misc/mcgp.rb} +63 -42
  33. data/lib/pwrake/option/host_map.rb +158 -0
  34. data/lib/pwrake/option/option.rb +357 -0
  35. data/lib/pwrake/option/option_filesystem.rb +112 -0
  36. data/lib/pwrake/queue/locality_aware_queue.rb +158 -0
  37. data/lib/pwrake/queue/no_action_queue.rb +67 -0
  38. data/lib/pwrake/queue/queue_array.rb +366 -0
  39. data/lib/pwrake/queue/task_queue.rb +164 -0
  40. data/lib/pwrake/report.rb +1 -0
  41. data/lib/pwrake/report/parallelism.rb +9 -3
  42. data/lib/pwrake/report/report.rb +50 -103
  43. data/lib/pwrake/report/task_stat.rb +83 -0
  44. data/lib/pwrake/task/task_algorithm.rb +107 -0
  45. data/lib/pwrake/task/task_manager.rb +32 -0
  46. data/lib/pwrake/task/task_property.rb +98 -0
  47. data/lib/pwrake/task/task_rank.rb +48 -0
  48. data/lib/pwrake/task/task_wrapper.rb +296 -0
  49. data/lib/pwrake/version.rb +1 -1
  50. data/lib/pwrake/worker/executor.rb +169 -0
  51. data/lib/pwrake/worker/gfarm_directory.rb +90 -0
  52. data/lib/pwrake/worker/invoker.rb +199 -0
  53. data/lib/pwrake/worker/load.rb +14 -0
  54. data/lib/pwrake/worker/log_executor.rb +73 -0
  55. data/lib/pwrake/worker/shared_directory.rb +74 -0
  56. data/lib/pwrake/worker/worker_main.rb +14 -0
  57. data/lib/pwrake/worker/writer.rb +59 -0
  58. data/setup.rb +1212 -1502
  59. data/spec/003/Rakefile +2 -2
  60. data/spec/008/Rakefile +2 -1
  61. data/spec/009/Rakefile +1 -1
  62. data/spec/009/pwrake_conf.yaml +1 -3
  63. data/spec/hosts +0 -2
  64. data/spec/pwrake_spec.rb +9 -8
  65. metadata +50 -21
  66. data/lib/pwrake.rb +0 -19
  67. data/lib/pwrake/application.rb +0 -232
  68. data/lib/pwrake/counter.rb +0 -54
  69. data/lib/pwrake/file_utils.rb +0 -98
  70. data/lib/pwrake/gfwhere_pool.rb +0 -109
  71. data/lib/pwrake/host_list.rb +0 -88
  72. data/lib/pwrake/locality_aware_queue.rb +0 -413
  73. data/lib/pwrake/option.rb +0 -400
  74. data/lib/pwrake/rake_modify.rb +0 -14
  75. data/lib/pwrake/shell.rb +0 -186
  76. data/lib/pwrake/task_algorithm.rb +0 -475
  77. data/lib/pwrake/task_queue.rb +0 -633
  78. data/lib/pwrake/timer.rb +0 -22
@@ -0,0 +1,112 @@
1
+ module Pwrake
2
+
3
+ class Option
4
+
5
+ attr_reader :worker_option
6
+ attr_reader :worker_progs
7
+ attr_reader :queue_class
8
+
9
+ def setup_filesystem
10
+
11
+ @worker_progs = %w[ writer log_executor executor invoker shared_directory ]
12
+ @worker_option = {
13
+ :base_dir => "",
14
+ :work_dir => self['WORK_DIR'],
15
+ :log_dir => self['LOG_DIR'],
16
+ :pass_env => self['PASS_ENV'],
17
+ :ssh_option => self['SSH_OPTION'],
18
+ :shell_command => self['SHELL_COMMAND'],
19
+ :shell_rc => self['SHELL_RC'],
20
+ :heartbeat => self['HEARTBEAT']
21
+ }
22
+
23
+ if @filesystem.nil?
24
+ case mount_type
25
+ when /gfarm2fs/
26
+ self['FILESYSTEM'] = @filesystem = 'gfarm'
27
+ end
28
+ end
29
+
30
+ #n_noaction_th = self['NUM_NOACTION_THREADS']
31
+
32
+ case @filesystem
33
+ when 'gfarm'
34
+ require "pwrake/queue/locality_aware_queue"
35
+ require "pwrake/gfarm/gfarm_path"
36
+ GfarmPath.subdir = self['GFARM_SUBDIR']
37
+ @filesystem = 'gfarm'
38
+ base = self['GFARM_BASEDIR']
39
+ prefix = self['GFARM_PREFIX']
40
+ mntpnt = "#{base}/#{prefix}"
41
+ @worker_option.merge!({
42
+ :shared_directory => "GfarmDirectory",
43
+ :base_dir => mntpnt,
44
+ :work_dir => GfarmPath.pwd.to_s,
45
+ :gfarm2fs_option => self['GFARM2FS_OPTION'],
46
+ :gfarm2fs_debug => self['GFARM2FS_DEBUG'],
47
+ :gfarm2fs_debug_wait => self['GFARM2FS_DEBUG_WAIT'],
48
+ :single_mp => self['GFARM_SINGLE_MP']
49
+ })
50
+ @worker_progs << "gfarm_directory"
51
+
52
+ if self['DISABLE_AFFINITY']
53
+ @queue_class = "TaskQueue"
54
+ else
55
+ @queue_class = "LocalityAwareQueue"
56
+ end
57
+ #@num_noaction_threads = (n_noaction_th || [8,@host_map.num_threads].max).to_i
58
+ else
59
+ @filesystem = 'nfs'
60
+ @queue_class = "TaskQueue"
61
+ #@num_noaction_threads = (n_noaction_th || 1).to_i
62
+ @worker_option[:shared_directory] = "SharedDirectory"
63
+ end
64
+ @worker_progs << "worker_main"
65
+ Log.debug "@queue_class=#{@queue_class}"
66
+ end
67
+
68
+ def max_postprocess_pool
69
+ case @filesystem
70
+ when 'gfarm'
71
+ self['MAX_GFWHERE_WORKER']
72
+ else
73
+ 1
74
+ end
75
+ end
76
+
77
+ def postprocess(runner)
78
+ case @filesystem
79
+ when 'gfarm'
80
+ require "pwrake/gfarm/gfarm_postprocess"
81
+ GfarmPostprocess.new(runner)
82
+ else
83
+ require "pwrake/master/postprocess"
84
+ Postprocess.new(runner)
85
+ end
86
+ end
87
+
88
+ def mount_type(d=nil)
89
+ mtab = '/etc/mtab'
90
+ if File.exist?(mtab)
91
+ d ||= mountpoint_of_cwd
92
+ open(mtab,'r') do |f|
93
+ f.each_line do |l|
94
+ if /#{d} (?:type )?(\S+)/o =~ l
95
+ return $1
96
+ end
97
+ end
98
+ end
99
+ end
100
+ nil
101
+ end
102
+
103
+ def mountpoint_of_cwd
104
+ d = Pathname.pwd
105
+ while !d.mountpoint?
106
+ d = d.parent
107
+ end
108
+ d
109
+ end
110
+
111
+ end
112
+ end
@@ -0,0 +1,158 @@
1
+ module Pwrake
2
+
3
+ class LocalityAwareQueue < TaskQueue
4
+
5
+ def init_queue(group_map=nil)
6
+ # group_map = {gid1=>[hid1,hid2,...], ...}
7
+ @size_q = 0
8
+ @q = {}
9
+ @host_map.by_id.each{|h| @q[h.id] = @array_class.new(h.ncore)}
10
+ @q_group = {}
11
+ group_map ||= {1=>@host_map.by_id.map{|h| h.id}}
12
+ group_map.each do |gid,ary|
13
+ q1 = {} # same group
14
+ q2 = @q.dup # other groups
15
+ ary.each{|hid| q1[hid] = q2.delete(hid)}
16
+ a = [q1,q2]
17
+ ary.each{|hid| @q_group[hid] = a}
18
+ end
19
+ @q_remote = @array_class.new(0)
20
+ @disable_steal = Rake.application.pwrake_options['DISABLE_STEAL']
21
+ @last_enq_time = Time.now
22
+ @n_turn = @disable_steal ? 1 : 2
23
+ end
24
+
25
+
26
+ def enq_impl(t)
27
+ hints = t && t.suggest_location
28
+ Log.debug "enq #{t.name} hints=#{hints.inspect}"
29
+ if hints.nil? || hints.empty?
30
+ @q_remote.push(t)
31
+ else
32
+ stored = false
33
+ hints.each do |h|
34
+ host_info = @host_map.by_name[h]
35
+ if host_info && q = @q[host_info.id]
36
+ t.assigned.push(host_info.id)
37
+ q.push(t)
38
+ stored = true
39
+ end
40
+ end
41
+ if stored
42
+ @size_q += 1
43
+ else
44
+ @q_remote.push(t)
45
+ end
46
+ end
47
+ @last_enq_time = Time.now
48
+ end
49
+
50
+ def turn_empty?(turn)
51
+ case turn
52
+ when 0
53
+ @q_no_action.empty? && @size_q == 0 && @q_remote.empty?
54
+ when 1
55
+ @size_q == 0
56
+ end
57
+ end
58
+
59
+ def deq_impl(host_info, turn)
60
+ host = host_info.name
61
+ case turn
62
+ when 0
63
+ if t = @q_no_action.shift
64
+ Log.debug "deq_no_action task=#{t&&t.name} host=#{host}"
65
+ return t
66
+ elsif t = deq_locate(host_info,host_info)
67
+ Log.debug "deq_locate task=#{t&&t.name} host=#{host}"
68
+ return t
69
+ elsif t = @q_remote.shift(host_info)
70
+ Log.debug "deq_remote task=#{t&&t.name}"
71
+ return t
72
+ else
73
+ nil
74
+ end
75
+ when 1
76
+ if t = deq_steal(host_info)
77
+ Log.debug "deq_steal task=#{t&&t.name} host=#{host}"
78
+ return t
79
+ else
80
+ nil
81
+ end
82
+ end
83
+ end
84
+
85
+ def deq_locate(q_host,run_host)
86
+ q = @q[q_host.id]
87
+ if q && !q.empty?
88
+ t = q.shift(run_host)
89
+ if t
90
+ t.assigned.each do |h|
91
+ @q[h].delete(t)
92
+ end
93
+ @size_q -= 1
94
+ end
95
+ return t
96
+ else
97
+ nil
98
+ end
99
+ end
100
+
101
+ def deq_steal(host_info)
102
+ # select a task based on many and close
103
+ max_host = nil
104
+ max_num = 0
105
+ @q_group[host_info.id].each do |qg|
106
+ qg.each do |h,a|
107
+ if !a.empty? # && h!=host_info.id
108
+ d = a.size
109
+ if d > max_num
110
+ max_host = h
111
+ max_num = d
112
+ end
113
+ end
114
+ end
115
+ if max_num > 0
116
+ max_info = @host_map.by_id[max_host]
117
+ Log.debug "deq_steal max_host=#{max_info.name} max_num=#{max_num}"
118
+ t = host_info.steal_phase{|h| deq_locate(max_info,h)}
119
+ #Log.debug "deq_steal task=#{t.inspect}"
120
+ return t if t
121
+ end
122
+ end
123
+ nil
124
+ end
125
+
126
+ def inspect_q
127
+ s = _qstr("noaction",@q_no_action)
128
+ if @size_q == 0
129
+ n = @q.size
130
+ else
131
+ n = 0
132
+ @q.each do |h,q|
133
+ if q.size > 0
134
+ s << _qstr(@host_map.by_id[h].name,q)
135
+ else
136
+ n += 1
137
+ end
138
+ end
139
+ end
140
+ s << _qstr("local*#{n}",[]) if n > 0
141
+ s << _qstr("remote",@q_remote)
142
+ s
143
+ end
144
+
145
+ def clear
146
+ @q_no_action.clear
147
+ @q.each{|h,q| q.clear}
148
+ @q_remote.clear
149
+ end
150
+
151
+ def empty?
152
+ @size_q == 0 &&
153
+ @q_no_action.empty? &&
154
+ @q_remote.empty?
155
+ end
156
+
157
+ end
158
+ end
@@ -0,0 +1,67 @@
1
+ module Pwrake
2
+
3
+ class NoActionQueue
4
+
5
+ def initialize
6
+ @que = []
7
+ prio = Rake.application.pwrake_options['NOACTION_QUEUE_PRIORITY'] || 'fifo'
8
+ case prio
9
+ when /fifo/i
10
+ @prio = 0
11
+ Log.debug "NOACTION_QUEUE_PRIORITY=FIFO"
12
+ when /lifo/i
13
+ @prio = 1
14
+ Log.debug "NOACTION_QUEUE_PRIORITY=LIFO"
15
+ when /rand/i
16
+ @prio = 2
17
+ Log.debug "NOACTION_QUEUE_PRIORITY=RAND"
18
+ else
19
+ raise RuntimeError,"unknown option for NOACTION_QUEUE_PRIORITY: "+prio
20
+ end
21
+ end
22
+
23
+ def push(obj)
24
+ @que.push obj
25
+ end
26
+
27
+ alias << push
28
+ alias enq push
29
+
30
+ def pop
31
+ case @prio
32
+ when 0
33
+ x = @que.shift
34
+ when 1
35
+ x = @que.pop
36
+ when 2
37
+ x = @que.delete_at(rand(@que.size))
38
+ end
39
+ return x
40
+ end
41
+
42
+ alias shift pop
43
+ alias deq pop
44
+
45
+ def empty?
46
+ @que.empty?
47
+ end
48
+
49
+ def clear
50
+ @que.clear
51
+ end
52
+
53
+ def length
54
+ @que.length
55
+ end
56
+ alias size length
57
+
58
+ def first
59
+ @que.first
60
+ end
61
+
62
+ def last
63
+ @que.last
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,366 @@
1
+ require "forwardable"
2
+
3
+ module Pwrake
4
+
5
+ class PriorityQueueArray < Array
6
+ def initialize(n)
7
+ super()
8
+ end
9
+
10
+ def shift
11
+ pop
12
+ end
13
+
14
+ def push(t)
15
+ priority = t.priority
16
+ if empty? || last.priority <= priority
17
+ super(t)
18
+ elsif first.priority > priority
19
+ unshift(t)
20
+ else
21
+ lower = 0
22
+ upper = size-1
23
+ while lower+1 < upper
24
+ mid = ((lower + upper) / 2).to_i
25
+ if self[mid].priority <= priority
26
+ lower = mid
27
+ else
28
+ upper = mid
29
+ end
30
+ end
31
+ insert(upper,t)
32
+ end
33
+ end
34
+
35
+ def index(t)
36
+ if size < 40
37
+ return super(t)
38
+ end
39
+ priority = t.priority
40
+ if last.priority < priority || first.priority > priority
41
+ nil
42
+ else
43
+ lower = 0
44
+ upper = size-1
45
+ while lower+1 < upper
46
+ mid = ((lower + upper) / 2).to_i
47
+ if self[mid].priority < priority
48
+ lower = mid
49
+ else
50
+ upper = mid
51
+ end
52
+ end
53
+ mid = upper
54
+ if self[mid].priority == priority
55
+ Log.debug "TQA#index=#{mid}, priority=#{priority}"
56
+ mid
57
+ end
58
+ end
59
+ end
60
+ end # PriorityQueueArray
61
+
62
+
63
+ class LifoQueueArray < Array
64
+ def initialize(n_cores=nil)
65
+ super()
66
+ end
67
+
68
+ def shift(host_info)
69
+ (size-1).downto(0) do |i|
70
+ if at(i).acceptable_for(host_info)
71
+ return delete_at(i)
72
+ #else
73
+ # Log.debug "task[#{at(i).name}] is rejected"
74
+ end
75
+ end
76
+ nil
77
+ end
78
+ end
79
+
80
+ class FifoQueueArray < Array
81
+ def initialize(n_cores=nil)
82
+ super()
83
+ end
84
+
85
+ def shift(host_info)
86
+ size.times do |i|
87
+ if at(i).acceptable_for(host_info)
88
+ return delete_at(i)
89
+ end
90
+ end
91
+ nil
92
+ end
93
+ end
94
+
95
+ class RankCounter
96
+
97
+ def initialize
98
+ @ntask = []
99
+ @nproc = 0
100
+ @mutex = Mutex.new
101
+ end
102
+
103
+ def add_nproc(n)
104
+ @mutex.synchronize do
105
+ @nproc += n
106
+ end
107
+ end
108
+
109
+ def incr(r)
110
+ @mutex.synchronize do
111
+ @ntask[r] = (@ntask[r]||0) + 1
112
+ end
113
+ end
114
+
115
+ def get_task
116
+ @mutex.synchronize do
117
+ (@ntask.size-1).downto(0) do |r|
118
+ c = @ntask[r]
119
+ if c && c>0
120
+ t = yield(c, @nproc, r)
121
+ #t = (c<=@n) ? pop_last_rank(r) : pop
122
+ if t
123
+ @ntask[t.rank] -= 1
124
+ Log.debug "RankCount rank=#{r} nproc=#{@nproc} count=#{c} t.rank=#{t.rank} t.name=#{t.name}"
125
+ end
126
+ return t
127
+ end
128
+ end
129
+ end
130
+ nil
131
+ end
132
+ end
133
+
134
+ # HRF mixin module
135
+ module HrfQueue
136
+
137
+ def hrf_init(n_cores=nil)
138
+ @nproc = n_cores || 0
139
+ @count = []
140
+ end
141
+
142
+ def hrf_push(t)
143
+ r = t.rank
144
+ c = @count[r]
145
+ @count[r] = (c) ? c+1 : 1
146
+ end
147
+
148
+ def hrf_get(host_info)
149
+ (@count.size-1).downto(0) do |r|
150
+ c = @count[r]
151
+ if c && c>0
152
+ t = (c <= @nproc) ? pop_last_rank(r,host_info) : pop_super(host_info)
153
+ hrf_delete(t) if t
154
+ return t
155
+ end
156
+ end
157
+ raise "no element"
158
+ nil
159
+ end
160
+
161
+ def pop_last_rank(r,host_info)
162
+ (size-1).downto(0) do |i|
163
+ tw = at(i)
164
+ if tw.rank == r && tw.acceptable_for(host_info)
165
+ return delete_at(i)
166
+ end
167
+ end
168
+ nil
169
+ end
170
+
171
+ def hrf_delete(t)
172
+ @count[t.rank] -= 1
173
+ end
174
+
175
+ def check(t=nil)
176
+ sum = 0
177
+ @count.each{|x| sum+=x if x}
178
+ if size != sum
179
+ #$stderr.puts self.inspect
180
+ #$stderr.puts t.inspect if t
181
+ raise "sise != @count.sum"
182
+ end
183
+ end
184
+ end
185
+
186
+ # LIFO + HRF
187
+ class LifoHrfQueueArray
188
+ include HrfQueue
189
+ extend Forwardable
190
+ def_delegators :@a, :empty?, :size, :first, :last, :at, :delete_at
191
+
192
+ def initialize(n_cores)
193
+ @a = LifoQueueArray.new
194
+ hrf_init(n_cores)
195
+ end
196
+
197
+ def push(t)
198
+ @a.push(t)
199
+ hrf_push(t)
200
+ end
201
+
202
+ def shift(host_info)
203
+ return nil if empty?
204
+ hrf_get(host_info)
205
+ end
206
+
207
+ def delete(t)
208
+ if x=@a.delete(t)
209
+ hrf_delete(t)
210
+ end
211
+ x
212
+ end
213
+
214
+ def pop_super(host_info)
215
+ @a.shift(host_info)
216
+ end
217
+ end
218
+
219
+
220
+ # Priority + HRF
221
+ class PriorityHrfQueueArray < PriorityQueueArray
222
+ include HrfQueue
223
+
224
+ def initialize(n)
225
+ super(n)
226
+ hrf_init(n)
227
+ end
228
+
229
+ def push(t)
230
+ super(t)
231
+ hrf_push(t)
232
+ end
233
+
234
+ def shift
235
+ return nil if empty?
236
+ hrf_get
237
+ end
238
+
239
+ def pop_super
240
+ pop
241
+ end
242
+ end
243
+
244
+
245
+ # Rank-Even Last In First Out
246
+ class RankQueueArray
247
+
248
+ def initialize(n)
249
+ @q = []
250
+ @size = 0
251
+ @n = (n>0) ? n : 1
252
+ end
253
+
254
+ def push(t)
255
+ r = t ? t.rank : 0
256
+ a = @q[r]
257
+ if a.nil?
258
+ @q[r] = a = []
259
+ end
260
+ @size += 1
261
+ a.push(t)
262
+ end
263
+
264
+ def size
265
+ @size
266
+ end
267
+
268
+ def empty?
269
+ @size == 0
270
+ end
271
+
272
+ def shift
273
+ if empty?
274
+ return nil
275
+ end
276
+ (@q.size-1).downto(0) do |i|
277
+ a = @q[i]
278
+ next if a.nil? || a.empty?
279
+ @size -= 1
280
+ if a.size <= @n
281
+ return pop_last_max(a)
282
+ else
283
+ return shift_weighted
284
+ end
285
+ end
286
+ raise "ELIFO: @q=#{@q.inspect}"
287
+ end
288
+
289
+ def shift_weighted
290
+ weight, weight_avg = RANK_STAT.rank_weight
291
+ wsum = 0.0
292
+ q = []
293
+ @q.each_with_index do |a,i|
294
+ next if a.nil? || a.empty?
295
+ w = weight[i]
296
+ w = weight_avg if w.nil?
297
+ # w *= a.size
298
+ wsum += w
299
+ q << [a,wsum]
300
+ end
301
+ #
302
+ x = rand() * wsum
303
+ Log.debug "shift_weighted x=#{x} wsum=#{wsum} weight=#{weight.inspect}"
304
+ q.each do |a,w|
305
+ if w > x
306
+ return a.pop
307
+ end
308
+ end
309
+ raise "ELIFO: wsum=#{wsum} x=#{x}"
310
+ end
311
+
312
+ def pop_last_max(a)
313
+ if a.size < 2
314
+ return a.pop
315
+ end
316
+ y_max = nil
317
+ i_max = nil
318
+ n = [@n, a.size].min
319
+ (-n..-1).each do |i|
320
+ y = a[i].input_file_size
321
+ if y_max.nil? || y > y_max
322
+ y_max = y
323
+ i_max = i
324
+ end
325
+ end
326
+ a.delete_at(i_max)
327
+ end
328
+
329
+ def first
330
+ return nil if empty?
331
+ @q.size.times do |i|
332
+ a = @q[i]
333
+ unless a.nil? || a.empty?
334
+ return a.first
335
+ end
336
+ end
337
+ end
338
+
339
+ def last
340
+ return nil if empty?
341
+ @q.size.times do |i|
342
+ a = @q[-i-1]
343
+ unless a.nil? || a.empty?
344
+ return a.last
345
+ end
346
+ end
347
+ end
348
+
349
+ def delete(t)
350
+ n = 0
351
+ @q.each do |a|
352
+ if a
353
+ a.delete(t)
354
+ n += a.size
355
+ end
356
+ end
357
+ @size = n
358
+ end
359
+
360
+ def clear
361
+ @q.clear
362
+ @size = 0
363
+ end
364
+ end
365
+
366
+ end