polyphony 0.46.1 → 0.47.4

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