pwrake 0.9.9.2 → 2.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.
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