polyphony 0.75 → 0.79

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.
@@ -2,6 +2,7 @@
2
2
  #include "ring_buffer.h"
3
3
 
4
4
  typedef struct queue {
5
+ unsigned int closed;
5
6
  ring_buffer values;
6
7
  ring_buffer shift_queue;
7
8
  ring_buffer push_queue;
@@ -9,6 +10,8 @@ typedef struct queue {
9
10
  } Queue_t;
10
11
 
11
12
  VALUE cQueue = Qnil;
13
+ VALUE cClosedQueueError = Qnil;
14
+ VALUE cThreadError = Qnil;
12
15
 
13
16
  static void Queue_mark(void *ptr) {
14
17
  Queue_t *queue = ptr;
@@ -49,6 +52,7 @@ static VALUE Queue_initialize(int argc, VALUE *argv, VALUE self) {
49
52
  Queue_t *queue;
50
53
  GetQueue(self, queue);
51
54
 
55
+ queue->closed = 0;
52
56
  ring_buffer_init(&queue->values);
53
57
  ring_buffer_init(&queue->shift_queue);
54
58
  ring_buffer_init(&queue->push_queue);
@@ -99,6 +103,9 @@ VALUE Queue_push(VALUE self, VALUE value) {
99
103
  Queue_t *queue;
100
104
  GetQueue(self, queue);
101
105
 
106
+ if (queue->closed)
107
+ rb_raise(cClosedQueueError, "queue closed");
108
+
102
109
  if (queue->capacity) capped_queue_block_push(queue);
103
110
 
104
111
  queue_schedule_first_blocked_fiber(&queue->shift_queue);
@@ -111,6 +118,9 @@ VALUE Queue_unshift(VALUE self, VALUE value) {
111
118
  Queue_t *queue;
112
119
  GetQueue(self, queue);
113
120
 
121
+ if (queue->closed)
122
+ rb_raise(cClosedQueueError, "queue closed");
123
+
114
124
  if (queue->capacity) capped_queue_block_push(queue);
115
125
 
116
126
  queue_schedule_first_blocked_fiber(&queue->shift_queue);
@@ -119,14 +129,25 @@ VALUE Queue_unshift(VALUE self, VALUE value) {
119
129
  return self;
120
130
  }
121
131
 
122
- VALUE Queue_shift(VALUE self) {
123
- Queue_t *queue;
132
+ VALUE Queue_shift_nonblock(Queue_t *queue) {
133
+ if (queue->values.count) {
134
+ VALUE value = ring_buffer_shift(&queue->values);
135
+ if ((queue->capacity) && (queue->capacity > queue->values.count))
136
+ queue_schedule_first_blocked_fiber(&queue->push_queue);
137
+ RB_GC_GUARD(value);
138
+ return value;
139
+ }
140
+ rb_raise(cThreadError, "queue empty");
141
+ }
142
+
143
+ VALUE Queue_shift_block(Queue_t *queue) {
124
144
  VALUE fiber = rb_fiber_current();
125
145
  VALUE thread = rb_thread_current();
126
146
  VALUE backend = rb_ivar_get(thread, ID_ivar_backend);
127
147
  VALUE value;
128
148
 
129
- GetQueue(self, queue);
149
+ if (queue->closed && !queue->values.count)
150
+ rb_raise(cClosedQueueError, "queue closed");
130
151
 
131
152
  while (1) {
132
153
  VALUE switchpoint_result;
@@ -140,6 +161,7 @@ VALUE Queue_shift(VALUE self) {
140
161
  RAISE_IF_EXCEPTION(switchpoint_result);
141
162
  RB_GC_GUARD(switchpoint_result);
142
163
  if (queue->values.count) break;
164
+ if (queue->closed) return Qnil;
143
165
  }
144
166
  value = ring_buffer_shift(&queue->values);
145
167
  if ((queue->capacity) && (queue->capacity > queue->values.count))
@@ -148,6 +170,16 @@ VALUE Queue_shift(VALUE self) {
148
170
  return value;
149
171
  }
150
172
 
173
+ VALUE Queue_shift(int argc,VALUE *argv, VALUE self) {
174
+ int nonblock = argc && RTEST(argv[0]);
175
+ Queue_t *queue;
176
+ GetQueue(self, queue);
177
+
178
+ return nonblock ?
179
+ Queue_shift_nonblock(queue) :
180
+ Queue_shift_block(queue);
181
+ }
182
+
151
183
  VALUE Queue_delete(VALUE self, VALUE value) {
152
184
  Queue_t *queue;
153
185
  GetQueue(self, queue);
@@ -244,6 +276,13 @@ VALUE Queue_pending_p(VALUE self) {
244
276
  return (queue->shift_queue.count) ? Qtrue : Qfalse;
245
277
  }
246
278
 
279
+ VALUE Queue_num_waiting(VALUE self) {
280
+ Queue_t *queue;
281
+ GetQueue(self, queue);
282
+
283
+ return INT2NUM(queue->shift_queue.count);
284
+ }
285
+
247
286
  VALUE Queue_size_m(VALUE self) {
248
287
  Queue_t *queue;
249
288
  GetQueue(self, queue);
@@ -251,20 +290,54 @@ VALUE Queue_size_m(VALUE self) {
251
290
  return INT2NUM(queue->values.count);
252
291
  }
253
292
 
293
+ VALUE Queue_closed_p(VALUE self) {
294
+ Queue_t *queue;
295
+ GetQueue(self, queue);
296
+
297
+ return (queue->closed) ? Qtrue : Qfalse;
298
+ }
299
+
300
+ VALUE Queue_close(VALUE self) {
301
+ Queue_t *queue;
302
+ GetQueue(self, queue);
303
+
304
+ if (queue->closed) goto end;
305
+ queue->closed = 1;
306
+
307
+ // release all fibers waiting on `#shift`
308
+ while (queue->shift_queue.count) {
309
+ VALUE fiber = ring_buffer_shift(&queue->shift_queue);
310
+ if (fiber == Qnil) break;
311
+ Fiber_make_runnable(fiber, Qnil);
312
+ }
313
+
314
+ end:
315
+ return self;
316
+ }
317
+
254
318
  void Init_Queue() {
319
+ cClosedQueueError = rb_const_get(rb_cObject, rb_intern("ClosedQueueError"));
320
+ cThreadError = rb_const_get(rb_cObject, rb_intern("ThreadError"));
321
+
255
322
  cQueue = rb_define_class_under(mPolyphony, "Queue", rb_cObject);
256
323
  rb_define_alloc_func(cQueue, Queue_allocate);
257
324
 
258
325
  rb_define_method(cQueue, "initialize", Queue_initialize, -1);
259
326
  rb_define_method(cQueue, "push", Queue_push, 1);
260
327
  rb_define_method(cQueue, "<<", Queue_push, 1);
328
+ rb_define_method(cQueue, "enq", Queue_push, 1);
261
329
  rb_define_method(cQueue, "unshift", Queue_unshift, 1);
262
330
 
263
- rb_define_method(cQueue, "shift", Queue_shift, 0);
264
- rb_define_method(cQueue, "pop", Queue_shift, 0);
331
+ rb_define_method(cQueue, "shift", Queue_shift, -1);
332
+ rb_define_method(cQueue, "pop", Queue_shift, -1);
333
+ rb_define_method(cQueue, "deq", Queue_shift, -1);
334
+
265
335
  rb_define_method(cQueue, "delete", Queue_delete, 1);
266
336
  rb_define_method(cQueue, "clear", Queue_clear, 0);
267
337
 
338
+ rb_define_method(cQueue, "size", Queue_size_m, 0);
339
+ rb_define_method(cQueue, "length", Queue_size_m, 0);
340
+
268
341
  rb_define_method(cQueue, "cap", Queue_cap, 1);
269
342
  rb_define_method(cQueue, "capped?", Queue_capped_p, 0);
270
343
 
@@ -273,5 +346,8 @@ void Init_Queue() {
273
346
  rb_define_method(cQueue, "flush_waiters", Queue_flush_waiters, 1);
274
347
  rb_define_method(cQueue, "empty?", Queue_empty_p, 0);
275
348
  rb_define_method(cQueue, "pending?", Queue_pending_p, 0);
276
- rb_define_method(cQueue, "size", Queue_size_m, 0);
349
+ rb_define_method(cQueue, "num_waiting", Queue_num_waiting, 0);
350
+
351
+ rb_define_method(cQueue, "closed?", Queue_closed_p, 0);
352
+ rb_define_method(cQueue, "close", Queue_close, 0);
277
353
  }
@@ -23,11 +23,15 @@ VALUE Thread_fiber_unschedule(VALUE self, VALUE fiber) {
23
23
  }
24
24
 
25
25
  inline void Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
26
- schedule_fiber(self, fiber, value, 0);
26
+ Backend_schedule_fiber(self, rb_ivar_get(self, ID_ivar_backend), fiber, value, 0);
27
+
28
+ // schedule_fiber(self, fiber, value, 0);
27
29
  }
28
30
 
29
31
  inline void Thread_schedule_fiber_with_priority(VALUE self, VALUE fiber, VALUE value) {
30
- schedule_fiber(self, fiber, value, 1);
32
+ Backend_schedule_fiber(self, rb_ivar_get(self, ID_ivar_backend), fiber, value, 1);
33
+
34
+ // schedule_fiber(self, fiber, value, 1);
31
35
  }
32
36
 
33
37
  VALUE Thread_switch_fiber(VALUE self) {
data/ext/test_eintr.c ADDED
@@ -0,0 +1,50 @@
1
+ #include <stdio.h>
2
+ #include <unistd.h>
3
+ #include <signal.h>
4
+ #include <poll.h>
5
+ #include "./liburing/liburing.h"
6
+
7
+ void sig_handler(int sig) {
8
+ printf("handle signal %d!\n", sig);
9
+ }
10
+
11
+ int main(int argc, char *argv[])
12
+ {
13
+ int pid = getpid();
14
+ int child_pid = fork();
15
+ if (!child_pid) {
16
+ sleep(1);
17
+ kill(pid, SIGINT);
18
+ sleep(1);
19
+ kill(pid, SIGINT);
20
+ }
21
+ else {
22
+ struct sigaction sa;
23
+
24
+ sa.sa_handler = sig_handler;
25
+ sa.sa_flags = SA_SIGINFO | SA_ONSTACK;//0;
26
+ sigemptyset(&sa.sa_mask);
27
+ sigaction(SIGINT, &sa, NULL);
28
+
29
+ printf("pid: %d\n", pid);
30
+
31
+ struct io_uring ring;
32
+ int ret = io_uring_queue_init(16, &ring, 0);
33
+ printf("io_uring_queue_init: %d\n", ret);
34
+
35
+ struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
36
+ io_uring_prep_poll_add(sqe, STDIN_FILENO, POLLIN);
37
+ ret = io_uring_submit(&ring);
38
+ printf("io_uring_submit: %d\n", ret);
39
+
40
+ struct io_uring_cqe *cqe;
41
+
42
+ wait_cqe:
43
+ ret = io_uring_wait_cqe(&ring, &cqe);
44
+ printf("io_uring_wait_cqe: %d\n", ret);
45
+ if (ret == -EINTR) goto wait_cqe;
46
+
47
+ printf("done\n");
48
+ return 0;
49
+ }
50
+ }
@@ -6,8 +6,154 @@ module ::Kernel
6
6
  def format_trace(args)
7
7
  if args.size > 1 && args.first.is_a?(String)
8
8
  format("%s: %p\n", args.shift, args.size == 1 ? args.first : args)
9
+ elsif args.size == 1 && args.first.is_a?(String)
10
+ "#{args.first}\n"
9
11
  else
10
12
  format("%p\n", args.size == 1 ? args.first : args)
11
13
  end
12
14
  end
13
15
  end
16
+
17
+ module Polyphony
18
+ module Trace
19
+ class << self
20
+ def start_event_firehose(io = nil, &block)
21
+ Thread.backend.trace_proc = firehose_proc(io, block)
22
+ end
23
+
24
+ private
25
+
26
+ def firehose_proc(io, block)
27
+ if io
28
+ ->(*e) { io.orig_write("#{trace_event_info(e).inspect}\n") }
29
+ elsif block
30
+ ->(*e) { block.(trace_event_info(e)) }
31
+ else
32
+ raise "Please provide an io or a block"
33
+ end
34
+ end
35
+
36
+ def trace_event_info(e)
37
+ {
38
+ stamp: format_current_time,
39
+ event: e[0]
40
+ }.merge(
41
+ send(:"event_props_#{e[0]}", e)
42
+ )
43
+ end
44
+
45
+ def format_trace_event_message(e)
46
+ props = send(:"event_props_#{e[0]}", e).merge(
47
+ timestamp: format_current_time,
48
+ event: e[0]
49
+ )
50
+ # templ = send(:"event_format_#{e[0]}", e)
51
+
52
+ # msg = format("%<timestamp>s #{templ}\n", **props)
53
+ end
54
+
55
+ def format_current_time
56
+ Time.now.strftime('%Y-%m-%d %H:%M:%S')
57
+ end
58
+
59
+ def generic_event_format
60
+ '%<event>-12.12s'
61
+ end
62
+
63
+ def fiber_event_format
64
+ "#{generic_event_format} %<fiber>-44.44s"
65
+ end
66
+
67
+ def event_props_enter_poll(e)
68
+ {}
69
+ end
70
+
71
+ def event_format_enter_poll(e)
72
+ generic_event_format
73
+ end
74
+
75
+ def event_props_leave_poll(e)
76
+ {}
77
+ end
78
+
79
+ def event_format_leave_poll(e)
80
+ generic_event_format
81
+ end
82
+
83
+ def event_props_schedule(e)
84
+ {
85
+ fiber: e[1],
86
+ value: e[2],
87
+ caller: e[4],
88
+ source_fiber: Fiber.current
89
+ }
90
+ end
91
+
92
+ def event_format_schedule(e)
93
+ "#{fiber_event_format} %<value>-24.24p %<caller>-120.120s <= %<origin_fiber>s"
94
+ end
95
+
96
+ def event_props_unblock(e)
97
+ {
98
+ fiber: e[1],
99
+ value: e[2],
100
+ caller: e[3],
101
+ }
102
+ end
103
+
104
+ def event_format_unblock(e)
105
+ "#{fiber_event_format} %<value>-24.24p %<caller>-120.120s"
106
+ end
107
+
108
+ def event_props_terminate(e)
109
+ {
110
+ fiber: e[1],
111
+ value: e[2],
112
+ }
113
+ end
114
+
115
+ def event_format_terminate(e)
116
+ "#{fiber_event_format} %<value>-24.24p"
117
+ end
118
+
119
+ def event_props_block(e)
120
+ {
121
+ fiber: e[1],
122
+ caller: e[2]
123
+ }
124
+ end
125
+
126
+ def event_format_block(e)
127
+ "#{fiber_event_format} #{' ' * 24} %<caller>-120.120s"
128
+ end
129
+
130
+ def event_props_spin(e)
131
+ {
132
+ fiber: e[1],
133
+ caller: e[2],
134
+ source_fiber: Fiber.current
135
+ }
136
+ end
137
+
138
+ def event_format_spin(e)
139
+ "#{fiber_event_format} #{' ' * 24} %<caller>-120.120s <= %<origin_fiber>s"
140
+ end
141
+
142
+ def fibe_repr(fiber)
143
+ format("%-6x %-20.20s %-10.10s", fiber.object_id, fiber.tag, "(#{fiber.state})")
144
+ end
145
+
146
+ def fiber_compact_repr(fiber)
147
+ if fiber.tag
148
+ format("%-6x %-.20s %-.10s", fiber.object_id, fiber.tag, "(#{fiber.state})")
149
+ else
150
+ format("%-6x %-.10s", fiber.object_id, "(#{fiber.state})")
151
+ end
152
+ end
153
+
154
+ def caller_repr(c)
155
+ c.map { |i| i.gsub('/home/sharon/repo/polyphony/lib/polyphony', '') }.join(' ')
156
+ end
157
+ end
158
+ end
159
+ end
@@ -248,21 +248,28 @@ module Polyphony
248
248
  # also be scheduled with priority. This method is mainly used trapping
249
249
  # signals (see also the patched `Kernel#trap`)
250
250
  def schedule_priority_oob_fiber(&block)
251
- f = Fiber.new do
251
+ oob_fiber = Fiber.new do
252
252
  Fiber.current.setup_raw
253
+ Thread.backend.trace(:unblock, oob_fiber, nil, @caller)
253
254
  result = block.call
254
255
  rescue Exception => e
255
256
  Thread.current.schedule_and_wakeup(Thread.main.main_fiber, e)
256
257
  result = e
257
258
  ensure
258
- Thread.backend.trace(:fiber_terminate, f, result)
259
+ Thread.backend.trace(:terminate, Fiber.current, result)
259
260
  suspend
260
261
  end
261
- f.oob = true
262
+ prepare_oob_fiber(oob_fiber, block)
263
+ Thread.backend.trace(:spin, oob_fiber, caller)
264
+ oob_fiber.schedule_with_priority(nil)
265
+ end
266
+
267
+ def prepare_oob_fiber(fiber, block)
268
+ fiber.oob = true
269
+ fiber.tag = :oob
270
+ fiber.thread = Thread.current
262
271
  location = block.source_location
263
- f.set_caller(["#{location.join(':')}"])
264
- Thread.backend.trace(:fiber_create, f)
265
- Thread.current.schedule_and_wakeup(f, nil)
272
+ fiber.set_caller(["#{location.join(':')}"])
266
273
  end
267
274
  end
268
275
 
@@ -349,12 +356,13 @@ module Polyphony
349
356
  @parent = parent
350
357
  @caller = caller
351
358
  @block = block
352
- Thread.backend.trace(:fiber_create, self)
359
+ Thread.backend.trace(:spin, self, Kernel.caller[1..-1])
353
360
  schedule
354
361
  end
355
362
 
356
363
  def run(first_value)
357
364
  setup first_value
365
+ Thread.backend.trace(:unblock, self, first_value, @caller)
358
366
  result = @block.(first_value)
359
367
  finalize result
360
368
  rescue Polyphony::Restart => e
@@ -396,7 +404,7 @@ module Polyphony
396
404
 
397
405
  def finalize(result, uncaught_exception = false)
398
406
  result, uncaught_exception = finalize_children(result, uncaught_exception)
399
- Thread.backend.trace(:fiber_terminate, self, result)
407
+ Thread.backend.trace(:terminate, self, result)
400
408
  @result = result
401
409
 
402
410
  inform_monitors(result, uncaught_exception)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
- VERSION = '0.75'
4
+ VERSION = '0.79'
5
5
  end
data/test/helper.rb CHANGED
@@ -46,10 +46,11 @@ class MiniTest::Test
46
46
  Thread.current.backend.finalize
47
47
  Thread.current.backend = Polyphony::Backend.new
48
48
  sleep 0.001
49
+ @__stamp = Time.now
49
50
  end
50
51
 
51
52
  def teardown
52
- # trace "* teardown #{self.name}"
53
+ # trace "* teardown #{self.name} (#{Time.now - @__stamp}s)"
53
54
  Fiber.current.shutdown_all_children
54
55
  if Fiber.current.children.size > 0
55
56
  puts "Children left after #{self.name}: #{Fiber.current.children.inspect}"
data/test/stress.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  count = ARGV[0] ? ARGV[0].to_i : 100
4
4
  test_name = ARGV[1]
5
5
 
6
- $test_cmd = +'ruby test/run.rb --name test_receive_cross_thread_exception'
6
+ $test_cmd = +'ruby test/run.rb --name test_cross_thread_send_receive'
7
7
  if test_name
8
8
  $test_cmd << " --name #{test_name}"
9
9
  end
data/test/test_fiber.rb CHANGED
@@ -923,18 +923,20 @@ class MailboxTest < MiniTest::Test
923
923
  def test_cross_thread_send_receive
924
924
  ping_receive_buffer = []
925
925
  pong_receive_buffer = []
926
+ master = Fiber.current
926
927
 
927
928
  pong = Thread.new do
928
- sleep 0.05
929
- loop do
929
+ master << :pong_ready
930
+ 3.times do
930
931
  peer, data = receive
931
932
  pong_receive_buffer << data
932
933
  peer << 'pong'
933
934
  end
934
935
  end
935
936
 
937
+ assert_equal :pong_ready, receive
938
+
936
939
  ping = Thread.new do
937
- sleep 0.05
938
940
  3.times do
939
941
  pong << [Fiber.current, 'ping']
940
942
  data = receive
@@ -943,7 +945,7 @@ class MailboxTest < MiniTest::Test
943
945
  end
944
946
 
945
947
  ping.join
946
- pong.kill
948
+ pong.join
947
949
  ping = pong = nil
948
950
 
949
951
  assert_equal %w{pong pong pong}, ping_receive_buffer
data/test/test_io.rb CHANGED
@@ -309,13 +309,13 @@ class IOClassMethodsTest < MiniTest::Test
309
309
  assert_equal BIN_DATA, s
310
310
  end
311
311
 
312
- def test_foreach
313
- skip 'IO.foreach is not yet implemented'
314
- lines = []
315
- IO.foreach(__FILE__) { |l| lines << l }
316
- assert_equal "# frozen_string_literal: true\n", lines[0]
317
- assert_equal "end\n", lines[-1]
318
- end
312
+ # def test_foreach
313
+ # skip 'IO.foreach is not yet implemented'
314
+ # lines = []
315
+ # IO.foreach(__FILE__) { |l| lines << l }
316
+ # assert_equal "# frozen_string_literal: true\n", lines[0]
317
+ # assert_equal "end\n", lines[-1]
318
+ # end
319
319
 
320
320
  def test_read_class_method
321
321
  s = IO.read(__FILE__)
data/test/test_queue.rb CHANGED
@@ -21,6 +21,44 @@ class QueueTest < MiniTest::Test
21
21
  assert_equal [1, 2, 3, 4], buf
22
22
  end
23
23
 
24
+ def test_chained_push
25
+ @queue << 5 << 6 << 7
26
+
27
+ buf = []
28
+ 3.times { buf << @queue.shift }
29
+ assert_equal [5, 6, 7], buf
30
+ end
31
+
32
+ def test_push_aliases
33
+ @queue.push 1
34
+ @queue << 2
35
+ @queue.enq 3
36
+
37
+ buf = []
38
+ 3.times { buf << @queue.shift }
39
+ assert_equal [1, 2, 3], buf
40
+ end
41
+
42
+ def test_pop_aliases
43
+ @queue << 1 << 2 << 3
44
+
45
+ assert_equal 1, @queue.pop
46
+ assert_equal 2, @queue.deq
47
+ assert_equal 3, @queue.shift
48
+
49
+ @queue << 1 << 2 << 3
50
+
51
+ assert_equal 1, @queue.pop(false)
52
+ assert_equal 2, @queue.deq(false)
53
+ assert_equal 3, @queue.shift(false)
54
+ end
55
+
56
+ def test_nonblocking_pop
57
+ assert_raises(ThreadError) { @queue.pop(true) }
58
+ assert_raises(ThreadError) { @queue.deq(true) }
59
+ assert_raises(ThreadError) { @queue.shift(true) }
60
+ end
61
+
24
62
  def test_unshift
25
63
  @queue.push 1
26
64
  @queue.push 2
@@ -112,22 +150,86 @@ class QueueTest < MiniTest::Test
112
150
 
113
151
  def test_queue_size
114
152
  assert_equal 0, @queue.size
153
+ assert_equal 0, @queue.length
115
154
 
116
155
  @queue.push 1
117
156
 
118
157
  assert_equal 1, @queue.size
158
+ assert_equal 1, @queue.length
119
159
 
120
160
  @queue.push 2
121
161
 
122
162
  assert_equal 2, @queue.size
163
+ assert_equal 2, @queue.length
123
164
 
124
165
  @queue.shift
125
166
 
126
167
  assert_equal 1, @queue.size
168
+ assert_equal 1, @queue.length
127
169
 
128
170
  @queue.shift
129
171
 
130
172
  assert_equal 0, @queue.size
173
+ assert_equal 0, @queue.length
174
+ end
175
+
176
+ def test_pending?
177
+ assert_equal false, @queue.pending?
178
+
179
+ buf = []
180
+ f = spin { buf << @queue.shift }
181
+ snooze
182
+ assert_equal true, @queue.pending?
183
+
184
+ @queue << 42
185
+ f.await
186
+ assert_equal [42], buf
187
+ assert_equal false, @queue.pending?
188
+ end
189
+
190
+ def test_num_waiting
191
+ assert_equal 0, @queue.num_waiting
192
+
193
+ f1 = spin { @queue.shift }
194
+ snooze # allow fiber to start
195
+ assert_equal 1, @queue.num_waiting
196
+
197
+ f2 = spin { @queue.shift }
198
+ snooze # allow fiber to start
199
+ assert_equal 2, @queue.num_waiting
200
+
201
+ @queue << 1
202
+ f1.await
203
+ assert_equal 1, @queue.num_waiting
204
+
205
+ @queue << 2
206
+ f2.await
207
+ assert_equal 0, @queue.num_waiting
208
+ end
209
+
210
+ def test_closed_queue
211
+ assert_equal false, @queue.closed?
212
+
213
+ buf = []
214
+ f = spin { buf << @queue.shift }
215
+ snooze # allow fiber to start
216
+
217
+ @queue.close
218
+ assert_equal true, @queue.closed?
219
+ cancel_after(1) { f.await }
220
+ assert_equal [nil], buf
221
+
222
+ assert_raises(ClosedQueueError) { @queue << 1 }
223
+ assert_raises(ClosedQueueError) { @queue.deq }
224
+ assert_raises(ThreadError) { @queue.pop(true) }
225
+
226
+ # test deq on closed non-empty queue
227
+ @queue = Polyphony::Queue.new
228
+ @queue << 42 << 43
229
+ @queue.close
230
+
231
+ assert_equal 42, @queue.deq(false)
232
+ assert_equal 43, @queue.deq(true)
131
233
  end
132
234
  end
133
235
 
@@ -246,4 +348,4 @@ class CappedQueueTest < MiniTest::Test
246
348
  a.join
247
349
  assert_equal [1, 2, 3, :d5, 4, :d8, 5], buffer
248
350
  end
249
- end
351
+ end