polyphony 0.46.1 → 0.47.4

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.
@@ -16,16 +16,29 @@ module Polyphony
16
16
  end
17
17
 
18
18
  def cancel_after(interval, with_exception: Polyphony::Cancel, &block)
19
- fiber = ::Fiber.current
20
- canceller = spin do
19
+ if !block
20
+ cancel_after_blockless_canceller(Fiber.current, interval, with_exception)
21
+ elsif block.arity > 0
22
+ cancel_after_with_block(Fiber.current, interval, with_exception, &block)
23
+ else
24
+ Thread.current.backend.timeout(interval, with_exception, &block)
25
+ end
26
+ end
27
+
28
+ def cancel_after_blockless_canceller(fiber, interval, with_exception)
29
+ spin do
21
30
  sleep interval
22
31
  exception = cancel_exception(with_exception)
23
- # we don't want the cancelling fiber caller location as part of the
24
- # exception backtrace
25
32
  exception.__raising_fiber__ = nil
26
33
  fiber.schedule exception
27
34
  end
28
- block ? cancel_after_wrap_block(canceller, &block) : canceller
35
+ end
36
+
37
+ def cancel_after_with_block(fiber, interval, with_exception, &block)
38
+ canceller = cancel_after_blockless_canceller(fiber, interval, with_exception)
39
+ block.call(canceller)
40
+ ensure
41
+ canceller.stop
29
42
  end
30
43
 
31
44
  def cancel_exception(exception)
@@ -36,49 +49,49 @@ module Polyphony
36
49
  end
37
50
  end
38
51
 
39
- def cancel_after_wrap_block(canceller, &block)
40
- block.call(canceller)
41
- ensure
42
- canceller.stop
43
- end
44
-
45
52
  def spin(tag = nil, &block)
46
53
  Fiber.current.spin(tag, caller, &block)
47
54
  end
48
55
 
49
- def spin_loop(tag = nil, rate: nil, &block)
50
- if rate
56
+ def spin_loop(tag = nil, rate: nil, interval: nil, &block)
57
+ if rate || interval
51
58
  Fiber.current.spin(tag, caller) do
52
- throttled_loop(rate, &block)
59
+ throttled_loop(rate: rate, interval: interval, &block)
53
60
  end
54
61
  else
55
62
  Fiber.current.spin(tag, caller) { loop(&block) }
56
63
  end
57
64
  end
58
65
 
59
- def every(interval)
60
- next_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + interval
61
- loop do
62
- now = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
63
- Thread.current.backend.sleep(next_time - now)
64
- yield
65
- loop do
66
- next_time += interval
67
- break if next_time > now
68
- end
69
- end
66
+ def spin_scope
67
+ raise unless block_given?
68
+
69
+ spin do
70
+ result = yield
71
+ Fiber.current.await_all_children
72
+ result
73
+ end.await
74
+ end
75
+
76
+ def every(interval, &block)
77
+ Thread.current.backend.timer_loop(interval, &block)
70
78
  end
71
79
 
72
80
  def move_on_after(interval, with_value: nil, &block)
73
- fiber = ::Fiber.current
74
- unless block
75
- return spin do
76
- sleep interval
77
- fiber.schedule with_value
78
- end
81
+ if !block
82
+ move_on_blockless_canceller(Fiber.current, interval, with_value)
83
+ elsif block.arity > 0
84
+ move_on_after_with_block(Fiber.current, interval, with_value, &block)
85
+ else
86
+ Thread.current.backend.timeout(interval, nil, with_value, &block)
79
87
  end
88
+ end
80
89
 
81
- move_on_after_with_block(fiber, interval, with_value, &block)
90
+ def move_on_blockless_canceller(fiber, interval, with_value)
91
+ spin do
92
+ sleep interval
93
+ fiber.schedule with_value
94
+ end
82
95
  end
83
96
 
84
97
  def move_on_after_with_block(fiber, interval, with_value, &block)
@@ -173,6 +173,7 @@ module Polyphony
173
173
  # signals (see also the patched `Kernel#trap`)
174
174
  def schedule_priority_oob_fiber(&block)
175
175
  f = Fiber.new do
176
+ Fiber.current.setup_raw
176
177
  block.call
177
178
  rescue Exception => e
178
179
  Thread.current.schedule_and_wakeup(Thread.main.main_fiber, e)
@@ -262,6 +263,7 @@ module Polyphony
262
263
  # allows the fiber to be scheduled and to receive messages.
263
264
  def setup_raw
264
265
  @thread = Thread.current
266
+ @running = true
265
267
  end
266
268
 
267
269
  def setup_main_fiber
@@ -328,14 +330,18 @@ class ::Fiber
328
330
  extend Polyphony::FiberControlClassMethods
329
331
 
330
332
  attr_accessor :tag, :thread, :parent
331
- attr_reader :result, :mailbox
333
+ attr_reader :result
332
334
 
333
335
  def running?
334
336
  @running
335
337
  end
336
338
 
337
339
  def inspect
338
- "#<Fiber:#{object_id} #{location} (#{state})>"
340
+ if @tag
341
+ "#<Fiber #{tag}:#{object_id} #{location} (#{state})>"
342
+ else
343
+ "#<Fiber:#{object_id} #{location} (#{state})>"
344
+ end
339
345
  end
340
346
  alias_method :to_s, :inspect
341
347
 
@@ -19,22 +19,13 @@ class ::Socket
19
19
  end
20
20
 
21
21
  def recv(maxlen, flags = 0, outbuf = nil)
22
- Thread.current.backend.recv(self, buf || +'', maxlen)
23
- # outbuf ||= +''
24
- # loop do
25
- # result = recv_nonblock(maxlen, flags, outbuf, **NO_EXCEPTION)
26
- # case result
27
- # when nil then raise IOError
28
- # when :wait_readable then Thread.current.backend.wait_io(self, false)
29
- # else
30
- # return result
31
- # end
32
- # end
22
+ Thread.current.backend.recv(self, outbuf || +'', maxlen)
33
23
  end
34
24
 
35
25
  def recv_loop(&block)
36
26
  Thread.current.backend.recv_loop(self, &block)
37
27
  end
28
+ alias_method :read_loop, :recv_loop
38
29
 
39
30
  def recvfrom(maxlen, flags = 0)
40
31
  @read_buffer ||= +''
@@ -53,8 +44,12 @@ class ::Socket
53
44
  Thread.current.backend.send(self, mesg)
54
45
  end
55
46
 
56
- def write(str)
57
- Thread.current.backend.send(self, str)
47
+ def write(str, *args)
48
+ if args.empty?
49
+ Thread.current.backend.send(self, str)
50
+ else
51
+ Thread.current.backend.send(self, str + args.join)
52
+ end
58
53
  end
59
54
  alias_method :<<, :write
60
55
 
@@ -145,13 +140,18 @@ class ::TCPSocket
145
140
  def recv_loop(&block)
146
141
  Thread.current.backend.recv_loop(self, &block)
147
142
  end
143
+ alias_method :read_loop, :recv_loop
148
144
 
149
145
  def send(mesg, flags = 0)
150
146
  Thread.current.backend.send(self, mesg)
151
147
  end
152
148
 
153
- def write(str)
154
- Thread.current.backend.send(self, str)
149
+ def write(str, *args)
150
+ if args.empty?
151
+ Thread.current.backend.send(self, str)
152
+ else
153
+ Thread.current.backend.send(self, str + args.join)
154
+ end
155
155
  end
156
156
  alias_method :<<, :write
157
157
 
@@ -200,3 +200,62 @@ class ::TCPServer
200
200
  @io.close
201
201
  end
202
202
  end
203
+
204
+ class ::UNIXServer
205
+ alias_method :orig_accept, :accept
206
+ def accept
207
+ Thread.current.backend.accept(self)
208
+ end
209
+
210
+ def accept_loop(&block)
211
+ Thread.current.backend.accept_loop(self, &block)
212
+ end
213
+
214
+
215
+ end
216
+
217
+ class ::UNIXSocket
218
+ def recv(maxlen, flags = 0, outbuf = nil)
219
+ Thread.current.backend.recv(self, buf || +'', maxlen)
220
+ end
221
+
222
+ def recv_loop(&block)
223
+ Thread.current.backend.recv_loop(self, &block)
224
+ end
225
+ alias_method :read_loop, :recv_loop
226
+
227
+ def send(mesg, flags = 0)
228
+ Thread.current.backend.send(self, mesg)
229
+ end
230
+
231
+ def write(str, *args)
232
+ if args.empty?
233
+ Thread.current.backend.send(self, str)
234
+ else
235
+ Thread.current.backend.send(self, str + args.join)
236
+ end
237
+ end
238
+ alias_method :<<, :write
239
+
240
+ def readpartial(maxlen, str = nil)
241
+ @read_buffer ||= +''
242
+ result = Thread.current.backend.recv(self, @read_buffer, maxlen)
243
+ raise EOFError unless result
244
+
245
+ if str
246
+ str << @read_buffer
247
+ else
248
+ str = @read_buffer
249
+ end
250
+ @read_buffer = +''
251
+ str
252
+ end
253
+
254
+ def read_nonblock(len, str = nil, exception: true)
255
+ @io.read_nonblock(len, str, exception: exception)
256
+ end
257
+
258
+ def write_nonblock(buf, exception: true)
259
+ @io.write_nonblock(buf, exception: exception)
260
+ end
261
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
- VERSION = '0.46.1'
4
+ VERSION = '0.47.4'
5
5
  end
@@ -136,4 +136,52 @@ class BackendTest < MiniTest::Test
136
136
  f.await # TODO: check why this test sometimes segfaults if we don't a<wait fiber
137
137
  assert_in_range 4..6, i
138
138
  end
139
+
140
+ class MyTimeoutException < Exception
141
+ end
142
+
143
+ def test_timeout
144
+ buffer = []
145
+ assert_raises(Polyphony::TimeoutException) do
146
+ @backend.timeout(0.01, Polyphony::TimeoutException) do
147
+ buffer << 1
148
+ sleep 0.02
149
+ buffer << 2
150
+ end
151
+ end
152
+ assert_equal [1], buffer
153
+
154
+ buffer = []
155
+ assert_raises(MyTimeoutException) do
156
+ @backend.timeout(0.01, MyTimeoutException) do
157
+ buffer << 1
158
+ sleep 1
159
+ buffer << 2
160
+ end
161
+ end
162
+ assert_equal [1], buffer
163
+
164
+ buffer = []
165
+ result = @backend.timeout(0.01, nil, 42) do
166
+ buffer << 1
167
+ sleep 1
168
+ buffer << 2
169
+ end
170
+ assert_equal 42, result
171
+ assert_equal [1], buffer
172
+ end
173
+
174
+ def test_nested_timeout
175
+ buffer = []
176
+ assert_raises(MyTimeoutException) do
177
+ @backend.timeout(0.01, MyTimeoutException) do
178
+ @backend.timeout(0.02, nil) do
179
+ buffer << 1
180
+ sleep 1
181
+ buffer << 2
182
+ end
183
+ end
184
+ end
185
+ assert_equal [1], buffer
186
+ end
139
187
  end
@@ -565,14 +565,14 @@ class FiberTest < MiniTest::Test
565
565
  end
566
566
 
567
567
  def test_inspect
568
- expected = format('#<Fiber:%s (root) (running)>', Fiber.current.object_id)
568
+ expected = format('#<Fiber main:%s (root) (running)>', Fiber.current.object_id)
569
569
  assert_equal expected, Fiber.current.inspect
570
570
 
571
571
  spin_line_no = __LINE__ + 1
572
- f = spin { :foo }
572
+ f = spin(:baz) { :foo }
573
573
 
574
574
  expected = format(
575
- '#<Fiber:%s %s:%d:in `test_inspect\' (runnable)>',
575
+ '#<Fiber baz:%s %s:%d:in `test_inspect\' (runnable)>',
576
576
  f.object_id,
577
577
  __FILE__,
578
578
  spin_line_no
@@ -581,7 +581,7 @@ class FiberTest < MiniTest::Test
581
581
 
582
582
  f.await
583
583
  expected = format(
584
- '#<Fiber:%s %s:%d:in `test_inspect\' (dead)>',
584
+ '#<Fiber baz:%s %s:%d:in `test_inspect\' (dead)>',
585
585
  f.object_id,
586
586
  __FILE__,
587
587
  spin_line_no
@@ -738,6 +738,35 @@ class MailboxTest < MiniTest::Test
738
738
  f&.stop
739
739
  end
740
740
 
741
+ def test_capped_fiber_mailbox
742
+ buffer = []
743
+ a = spin_loop do
744
+ 3.times { snooze }
745
+ buffer << [:receive, receive]
746
+ end
747
+ a.mailbox.cap(1)
748
+
749
+ b = spin do
750
+ (1..3).each do |i|
751
+ a << i
752
+ buffer << [:send, i]
753
+ end
754
+ end
755
+
756
+ (1..10).each do |i|
757
+ snooze
758
+ buffer << [:snooze, i]
759
+ end
760
+
761
+ b.join
762
+
763
+ assert_equal [
764
+ [:snooze, 1], [:send, 1], [:snooze, 2], [:snooze, 3], [:snooze, 4],
765
+ [:receive, 1], [:snooze, 5], [:send, 2], [:snooze, 6], [:snooze, 7],
766
+ [:snooze, 8], [:receive, 2], [:snooze, 9], [:send, 3], [:snooze, 10]
767
+ ], buffer
768
+ end
769
+
741
770
  def test_that_multiple_messages_sent_at_once_arrive_in_order
742
771
  msgs = []
743
772
  f = spin { loop { msgs << receive } }
@@ -150,6 +150,28 @@ class MoveOnAfterTest < MiniTest::Test
150
150
  assert t1 - t0 < 0.1
151
151
  assert_equal 'foo', v
152
152
  end
153
+
154
+ def test_nested_move_on_after
155
+ t0 = Time.now
156
+ o = move_on_after(0.01, with_value: 1) do
157
+ move_on_after(0.02, with_value: 2) do
158
+ sleep 1
159
+ end
160
+ end
161
+ t1 = Time.now
162
+ assert_equal 1, o
163
+ assert_in_range 0.008..0.013, t1 - t0
164
+
165
+ t0 = Time.now
166
+ o = move_on_after(0.02, with_value: 1) do
167
+ move_on_after(0.01, with_value: 2) do
168
+ sleep 1
169
+ end
170
+ end
171
+ t1 = Time.now
172
+ assert_equal 2, o
173
+ assert_in_range 0.008..0.013, t1 - t0
174
+ end
153
175
  end
154
176
 
155
177
  class CancelAfterTest < MiniTest::Test
@@ -188,7 +210,7 @@ class CancelAfterTest < MiniTest::Test
188
210
  sleep 0.007
189
211
  end
190
212
  t1 = Time.now
191
- assert_in_range 0.014..0.02, t1 - t0
213
+ assert_in_range 0.014..0.024, t1 - t0
192
214
  end
193
215
 
194
216
  class CustomException < Exception
@@ -275,6 +297,55 @@ class SpinLoopTest < MiniTest::Test
275
297
  f.stop
276
298
  assert_in_range 1..3, counter
277
299
  end
300
+
301
+ def test_spin_loop_with_interval
302
+ buffer = []
303
+ counter = 0
304
+ t0 = Time.now
305
+ f = spin_loop(interval: 0.01) { buffer << (counter += 1) }
306
+ sleep 0.02
307
+ f.stop
308
+ assert_in_range 1..3, counter
309
+ end
310
+ end
311
+
312
+ class SpinScopeTest < MiniTest::Test
313
+ def test_spin_scope
314
+ queue = Queue.new
315
+ buffer = {}
316
+ spin do
317
+ queue << 1
318
+ snooze
319
+ queue << 2
320
+ end
321
+ f = nil
322
+ result = spin_scope do
323
+ f = Fiber.current
324
+ spin { buffer[:a] = queue.shift }
325
+ spin { buffer[:b] = queue.shift }
326
+ :foo
327
+ end
328
+ assert_equal :foo, result
329
+ assert_kind_of Fiber, f
330
+ assert_equal :dead, f.state
331
+ assert_equal ({a: 1, b: 2}), buffer
332
+ end
333
+
334
+ def test_spin_scope_with_exception
335
+ queue = Queue.new
336
+ buffer = []
337
+ spin do
338
+ spin_scope do
339
+ spin { buffer << queue.shift }
340
+ spin { raise 'foobar' }
341
+ end
342
+ rescue => e
343
+ buffer << e.message
344
+ end
345
+ 6.times { snooze }
346
+ assert_equal 0, Fiber.current.children.size
347
+ assert_equal ['foobar'], buffer
348
+ end
278
349
  end
279
350
 
280
351
  class ThrottledLoopTest < MiniTest::Test