polyphony 1.1.1 → 1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +1 -1
  3. data/.github/workflows/test_io_uring.yml +1 -1
  4. data/.rubocop.yml +16 -8
  5. data/CHANGELOG.md +9 -0
  6. data/README.md +2 -1
  7. data/docs/advanced-io.md +9 -1
  8. data/docs/cancellation.md +213 -0
  9. data/docs/readme.md +2 -1
  10. data/examples/core/enumerator.rb +92 -0
  11. data/examples/io/https_server_sni_2.rb +1 -1
  12. data/ext/polyphony/backend_common.c +11 -0
  13. data/ext/polyphony/backend_common.h +2 -0
  14. data/ext/polyphony/backend_io_uring.c +1 -1
  15. data/ext/polyphony/backend_libev.c +1 -1
  16. data/ext/polyphony/polyphony.h +3 -1
  17. data/lib/polyphony/core/debug.rb +24 -29
  18. data/lib/polyphony/core/exceptions.rb +0 -3
  19. data/lib/polyphony/core/sync.rb +0 -3
  20. data/lib/polyphony/core/thread_pool.rb +1 -5
  21. data/lib/polyphony/core/throttler.rb +0 -1
  22. data/lib/polyphony/core/timer.rb +7 -9
  23. data/lib/polyphony/extensions/exception.rb +0 -1
  24. data/lib/polyphony/extensions/fiber.rb +41 -28
  25. data/lib/polyphony/extensions/io.rb +86 -93
  26. data/lib/polyphony/extensions/kernel.rb +52 -16
  27. data/lib/polyphony/extensions/object.rb +7 -6
  28. data/lib/polyphony/extensions/openssl.rb +6 -8
  29. data/lib/polyphony/extensions/pipe.rb +5 -7
  30. data/lib/polyphony/extensions/socket.rb +28 -37
  31. data/lib/polyphony/extensions/thread.rb +2 -4
  32. data/lib/polyphony/extensions/timeout.rb +0 -1
  33. data/lib/polyphony/version.rb +1 -1
  34. data/lib/polyphony.rb +4 -7
  35. data/polyphony.gemspec +1 -1
  36. data/test/test_fiber.rb +6 -6
  37. data/test/test_global_api.rb +3 -3
  38. data/test/test_io.rb +2 -2
  39. data/test/test_socket.rb +2 -2
  40. data/test/test_supervise.rb +1 -1
  41. metadata +5 -3
@@ -2,7 +2,7 @@
2
2
  module ::Kernel
3
3
  # Prints a trace message to `STDOUT`, bypassing the Polyphony backend.
4
4
  def trace(*args)
5
- STDOUT.orig_write(format_trace(args))
5
+ $stdout.orig_write(format_trace(args))
6
6
  end
7
7
 
8
8
  # Formats a trace message.
@@ -20,12 +20,10 @@ module ::Kernel
20
20
  end
21
21
 
22
22
  module Polyphony
23
-
24
23
  # Trace provides tools for tracing the activity of the current thread's
25
24
  # backend.
26
25
  module Trace
27
26
  class << self
28
-
29
27
  # Starts tracing, emitting events converted to hashes to the given block.
30
28
  # If an IO instance is given, events are dumped to it instead.
31
29
  #
@@ -48,7 +46,7 @@ module Polyphony
48
46
  elsif block
49
47
  ->(*e) { block.(trace_event_info(e)) }
50
48
  else
51
- raise "Please provide an io or a block"
49
+ raise 'Please provide an io or a block'
52
50
  end
53
51
  end
54
52
 
@@ -56,12 +54,12 @@ module Polyphony
56
54
  #
57
55
  # @param e [Array] event as emitted by the backend
58
56
  # @return [Hash] event hash
59
- def trace_event_info(e)
57
+ def trace_event_info(event)
60
58
  {
61
59
  stamp: Time.now,
62
- event: e[0]
60
+ event: event[0]
63
61
  }.merge(
64
- send(:"event_props_#{e[0]}", e)
62
+ send(:"event_props_#{event[0]}", event)
65
63
  )
66
64
  end
67
65
 
@@ -69,10 +67,10 @@ module Polyphony
69
67
  #
70
68
  # @param e [Array] event array
71
69
  # @return [Hash] event hash
72
- def event_props_block(e)
70
+ def event_props_block(event)
73
71
  {
74
- fiber: e[1],
75
- caller: e[2]
72
+ fiber: event[1],
73
+ caller: event[2]
76
74
  }
77
75
  end
78
76
 
@@ -80,7 +78,7 @@ module Polyphony
80
78
  #
81
79
  # @param e [Array] event array
82
80
  # @return [Hash] event hash
83
- def event_props_enter_poll(e)
81
+ def event_props_enter_poll(_event)
84
82
  {}
85
83
  end
86
84
 
@@ -88,7 +86,7 @@ module Polyphony
88
86
  #
89
87
  # @param e [Array] event array
90
88
  # @return [Hash] event hash
91
- def event_props_leave_poll(e)
89
+ def event_props_leave_poll(_event)
92
90
  {}
93
91
  end
94
92
 
@@ -96,11 +94,11 @@ module Polyphony
96
94
  #
97
95
  # @param e [Array] event array
98
96
  # @return [Hash] event hash
99
- def event_props_schedule(e)
97
+ def event_props_schedule(event)
100
98
  {
101
- fiber: e[1],
102
- value: e[2],
103
- caller: e[4],
99
+ fiber: event[1],
100
+ value: event[2],
101
+ caller: event[4],
104
102
  source_fiber: Fiber.current
105
103
  }
106
104
  end
@@ -109,10 +107,10 @@ module Polyphony
109
107
  #
110
108
  # @param e [Array] event array
111
109
  # @return [Hash] event hash
112
- def event_props_spin(e)
110
+ def event_props_spin(event)
113
111
  {
114
- fiber: e[1],
115
- caller: e[2],
112
+ fiber: event[1],
113
+ caller: event[2],
116
114
  source_fiber: Fiber.current
117
115
  }
118
116
  end
@@ -121,10 +119,10 @@ module Polyphony
121
119
  #
122
120
  # @param e [Array] event array
123
121
  # @return [Hash] event hash
124
- def event_props_terminate(e)
122
+ def event_props_terminate(event)
125
123
  {
126
- fiber: e[1],
127
- value: e[2],
124
+ fiber: event[1],
125
+ value: event[2]
128
126
  }
129
127
  end
130
128
 
@@ -132,11 +130,11 @@ module Polyphony
132
130
  #
133
131
  # @param e [Array] event array
134
132
  # @return [Hash] event hash
135
- def event_props_unblock(e)
133
+ def event_props_unblock(event)
136
134
  {
137
- fiber: e[1],
138
- value: e[2],
139
- caller: e[3],
135
+ fiber: event[1],
136
+ value: event[2],
137
+ caller: event[3]
140
138
  }
141
139
  end
142
140
 
@@ -170,12 +168,10 @@ module Polyphony
170
168
  # generic_event_format
171
169
  # end
172
170
 
173
-
174
171
  # def event_format_schedule(e)
175
172
  # "#{fiber_event_format} %<value>-24.24p %<caller>-120.120s <= %<origin_fiber>s"
176
173
  # end
177
174
 
178
-
179
175
  # def event_format_unblock(e)
180
176
  # "#{fiber_event_format} %<value>-24.24p %<caller>-120.120s"
181
177
  # end
@@ -188,7 +184,6 @@ module Polyphony
188
184
  # "#{fiber_event_format} #{' ' * 24} %<caller>-120.120s"
189
185
  # end
190
186
 
191
-
192
187
  # def event_format_spin(e)
193
188
  # "#{fiber_event_format} #{' ' * 24} %<caller>-120.120s <= %<origin_fiber>s"
194
189
  # end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
-
5
4
  # Base exception class for interrupting fibers. These exceptions allow control
6
5
  # of fibers. BaseException exceptions can encapsulate a value and thus provide
7
6
  # a way to interrupt long-running blocking operations while still passing a
@@ -9,7 +8,6 @@ module Polyphony
9
8
  # cancel scope in order to allow correct bubbling of exceptions through nested
10
9
  # cancel scopes.
11
10
  class BaseException < ::Exception
12
-
13
11
  # Exception value, used mainly for `MoveOn` exceptions.
14
12
  attr_reader :value
15
13
 
@@ -39,7 +37,6 @@ module Polyphony
39
37
 
40
38
  # Interjection is used to run arbitrary code on arbitrary fibers at any point
41
39
  class Interjection < BaseException
42
-
43
40
  # Initializes an Interjection with the given proc.
44
41
  #
45
42
  # @param proc [Proc] interjection proc
@@ -1,11 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
-
5
4
  # Implements mutex lock for synchronizing access to a shared resource. This
6
5
  # class replaces the stock `Thread::Mutex` class.
7
6
  class Mutex
8
-
9
7
  # Initializes a new mutex.
10
8
  def initialize
11
9
  @store = Queue.new
@@ -130,7 +128,6 @@ module Polyphony
130
128
 
131
129
  # Implements a fiber-aware ConditionVariable
132
130
  class ConditionVariable
133
-
134
131
  # Initializes the condition variable.
135
132
  def initialize
136
133
  @queue = Polyphony::Queue.new
@@ -3,10 +3,8 @@
3
3
  require 'etc'
4
4
 
5
5
  module Polyphony
6
-
7
6
  # Implements a pool of threads
8
7
  class ThreadPool
9
-
10
8
  # The pool size.
11
9
  attr_reader :size
12
10
 
@@ -79,9 +77,7 @@ module Polyphony
79
77
 
80
78
  # Runs a processing loop on a worker thread.
81
79
  def thread_loop
82
- while true
83
- run_queued_task
84
- end
80
+ run_queued_task while true
85
81
  end
86
82
 
87
83
  # Runs the first queued task in the task queue.
@@ -3,7 +3,6 @@
3
3
  module Polyphony
4
4
  # Implements general-purpose throttling
5
5
  class Throttler
6
-
7
6
  # Initializes a throttler instance with the given rate.
8
7
  #
9
8
  # @param rate [Number] throttler rate in times per second
@@ -1,13 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
-
5
4
  # Implements a common timer for running multiple timeouts. This class may be
6
5
  # used to reduce the timer granularity in case a large number of timeouts is
7
6
  # used concurrently. This class basically provides the same methods as global
8
7
  # methods concerned with timeouts, such as `#cancel_after`, `#every` etc.
9
8
  class Timer
10
-
11
9
  # Initializes a new timer with the given resolution.
12
10
  #
13
11
  # @param tag [any] tag to use for the timer's fiber
@@ -31,7 +29,7 @@ module Polyphony
31
29
  def sleep(duration)
32
30
  fiber = Fiber.current
33
31
  @timeouts[fiber] = {
34
- interval: duration,
32
+ interval: duration,
35
33
  target_stamp: now + duration
36
34
  }
37
35
  Polyphony.backend_wait_event(true)
@@ -58,9 +56,9 @@ module Polyphony
58
56
  def every(interval)
59
57
  fiber = Fiber.current
60
58
  @timeouts[fiber] = {
61
- interval: interval,
59
+ interval:,
62
60
  target_stamp: now + interval,
63
- recurring: true
61
+ recurring: true
64
62
  }
65
63
  while true
66
64
  Polyphony.backend_wait_event(true)
@@ -113,9 +111,9 @@ module Polyphony
113
111
  def cancel_after(interval, with_exception: Polyphony::Cancel)
114
112
  fiber = Fiber.current
115
113
  @timeouts[fiber] = {
116
- interval: interval,
114
+ interval:,
117
115
  target_stamp: now + interval,
118
- exception: with_exception
116
+ exception: with_exception
119
117
  }
120
118
  yield
121
119
  ensure
@@ -166,9 +164,9 @@ module Polyphony
166
164
  def move_on_after(interval, with_value: nil)
167
165
  fiber = Fiber.current
168
166
  @timeouts[fiber] = {
169
- interval: interval,
167
+ interval:,
170
168
  target_stamp: now + interval,
171
- exception: [Polyphony::MoveOn, with_value]
169
+ exception: [Polyphony::MoveOn, with_value]
172
170
  }
173
171
  yield
174
172
  rescue Polyphony::MoveOn => e
@@ -3,7 +3,6 @@
3
3
  # Extensions to the Exception class
4
4
  class ::Exception
5
5
  class << self
6
-
7
6
  # Set to true to disable sanitizing the backtrace (to remove frames occuring
8
7
  # in the Polyphony code itself.)
9
8
  attr_accessor :__disable_sanitized_backtrace__
@@ -119,7 +119,7 @@ class ::Fiber
119
119
  def cancel(exception = Polyphony::Cancel)
120
120
  return if @running == false
121
121
 
122
- value = (Class === exception) ? exception.new : exception
122
+ value = exception.is_a?(Class) ? exception.new : exception
123
123
  schedule value
124
124
  self
125
125
  end
@@ -143,7 +143,7 @@ class ::Fiber
143
143
  #
144
144
  # @param graceful [bool] Whether to perform a graceful shutdown
145
145
  # @return [Fiber] self
146
- def terminate(graceful = false)
146
+ def terminate(graceful: false)
147
147
  return if @running == false
148
148
 
149
149
  @graceful_shutdown = graceful
@@ -265,7 +265,7 @@ class ::Fiber
265
265
  #
266
266
  # @param graceful [bool] whether to perform a graceful termination
267
267
  # @return [Fiber] self
268
- def terminate_all_children(graceful = false)
268
+ def terminate_all_children(graceful: false)
269
269
  return self unless @children
270
270
 
271
271
  e = Polyphony::Terminate.new
@@ -283,23 +283,24 @@ class ::Fiber
283
283
  def await_all_children
284
284
  return unless @children && !@children.empty?
285
285
 
286
- Fiber.await(*@children.keys.reject { |c| c.dead? })
286
+ Fiber.await(@children.keys.reject(&:dead?))
287
287
  end
288
288
 
289
289
  # Terminates and blocks until all child fibers have terminated.
290
290
  #
291
291
  # @return [Fiber] self
292
- def shutdown_all_children(graceful = false)
292
+ def shutdown_all_children(graceful: false)
293
293
  return self unless @children
294
294
 
295
295
  pending = []
296
- @children.keys.each do |c|
296
+ child_fibers = @children.keys
297
+ child_fibers.each do |c|
297
298
  next if c.dead?
298
299
 
299
- c.terminate(graceful)
300
+ c.terminate(graceful:)
300
301
  pending << c
301
302
  end
302
- Fiber.await(*pending)
303
+ Fiber.await(pending)
303
304
  self
304
305
  end
305
306
 
@@ -308,7 +309,8 @@ class ::Fiber
308
309
  # @param parent [Fiber] new parent
309
310
  # @return [Fiber] self
310
311
  def attach_all_children_to(parent)
311
- @children&.keys.each { |c| c.attach_to(parent) }
312
+ child_fibers = @children&.keys
313
+ child_fibers&.each { |c| c.attach_to(parent) }
312
314
  self
313
315
  end
314
316
 
@@ -361,7 +363,7 @@ class ::Fiber
361
363
  # @param child_fiber [Fiber] child fiber to be removed
362
364
  # @return [Fiber] self
363
365
  def remove_child(child_fiber)
364
- @children.delete(child_fiber) if @children
366
+ @children&.delete(child_fiber)
365
367
  self
366
368
  end
367
369
 
@@ -382,7 +384,7 @@ class ::Fiber
382
384
  @parent = parent
383
385
  @caller = caller
384
386
  @block = block
385
- Thread.backend.trace(:spin, self, Kernel.caller[1..-1])
387
+ Thread.backend.trace(:spin, self, Kernel.caller[1..])
386
388
  schedule
387
389
  self
388
390
  end
@@ -405,7 +407,7 @@ class ::Fiber
405
407
  finalize(e.value)
406
408
  rescue Exception => e
407
409
  e.source_fiber = self
408
- finalize(e, true)
410
+ finalize(e, uncaught_exception: true)
409
411
  end
410
412
 
411
413
  # Performs setup for a "raw" Fiber created using Fiber.new. Note that this
@@ -446,7 +448,7 @@ class ::Fiber
446
448
  # @param result [any] return value
447
449
  # @param uncaught_exception [Exception, nil] uncaught exception
448
450
  # @return [false]
449
- def finalize(result, uncaught_exception = false)
451
+ def finalize(result, uncaught_exception: false)
450
452
  result, uncaught_exception = finalize_children(result, uncaught_exception)
451
453
  Thread.backend.trace(:terminate, self, result)
452
454
  @result = result
@@ -468,7 +470,7 @@ class ::Fiber
468
470
  # @param uncaught_exception [Exception, nil] uncaught exception
469
471
  # @return [Array] array containing result and uncaught exception if any
470
472
  def finalize_children(result, uncaught_exception)
471
- shutdown_all_children(graceful_shutdown?)
473
+ shutdown_all_children(graceful: graceful_shutdown?)
472
474
  [result, uncaught_exception]
473
475
  rescue Exception => e
474
476
  [e, true]
@@ -486,7 +488,7 @@ class ::Fiber
486
488
  end
487
489
 
488
490
  if uncaught_exception && @parent
489
- parent_is_monitor = @monitors&.has_key?(@parent)
491
+ parent_is_monitor = @monitors&.key?(@parent)
490
492
  @parent.schedule_with_priority(result) unless parent_is_monitor
491
493
  end
492
494
 
@@ -532,12 +534,23 @@ class ::Fiber
532
534
  # terminates with an uncaught exception, `Fiber.await` will await all the
533
535
  # other fibers to terminate, then reraise the exception.
534
536
  #
535
- # @param fibers [Array<Fiber>] fibers to wait for
536
- # @return [Array<any>] return values of given fibers
537
+ # This method can be called with multiple fibers as multiple arguments, or
538
+ # with a single array containing one or more fibers.
539
+ #
540
+ # @overload Fiber.await(f1, f2, ...)
541
+ # @param fibers [Array<Fiber>] fibers to wait for
542
+ # @return [Array<any>] return values of given fibers
543
+ # @overload Fiber.await(fibers)
544
+ # @param fibers [Array<Fiber>] fibers to wait for
545
+ # @return [Array<any>] return values of given fibers
537
546
  def await(*fibers)
538
547
  return [] if fibers.empty?
539
548
 
540
- current_fiber = self.current
549
+ if (first = fibers.first).is_a?(Array)
550
+ fibers = first
551
+ end
552
+
553
+ current_fiber = Fiber.current
541
554
  mailbox = current_fiber.monitor_mailbox
542
555
  results = {}
543
556
  fibers.each do |f|
@@ -553,16 +566,18 @@ class ::Fiber
553
566
  while !fibers.empty?
554
567
  (fiber, result) = mailbox.shift
555
568
  next unless fibers.include?(fiber)
569
+
556
570
  fibers.delete(fiber)
557
571
  current_fiber.remove_child(fiber) if fiber.parent == current_fiber
558
572
  if result.is_a?(Exception)
559
573
  exception ||= result
560
- fibers.each { |f| f.terminate }
574
+ fibers.each(&:terminate)
561
575
  else
562
576
  results[fiber] = result
563
577
  end
564
578
  end
565
579
  raise exception if exception
580
+
566
581
  results.values
567
582
  end
568
583
  alias_method :join, :await
@@ -576,7 +591,7 @@ class ::Fiber
576
591
  def select(*fibers)
577
592
  return nil if fibers.empty?
578
593
 
579
- current_fiber = self.current
594
+ current_fiber = Fiber.current
580
595
  mailbox = current_fiber.monitor_mailbox
581
596
  fibers.each do |f|
582
597
  if f.dead?
@@ -591,11 +606,9 @@ class ::Fiber
591
606
  next unless fibers.include?(fiber)
592
607
 
593
608
  fibers.each { |f| f.unmonitor(current_fiber) }
594
- if result.is_a?(Exception)
595
- raise result
596
- else
597
- return [fiber, result]
598
- end
609
+ raise result if result.is_a?(Exception)
610
+
611
+ return [fiber, result]
599
612
  end
600
613
  end
601
614
 
@@ -629,7 +642,7 @@ class ::Fiber
629
642
  fiber.tag = :oob
630
643
  fiber.thread = Thread.current
631
644
  location = block.source_location
632
- fiber.set_caller(["#{location.join(':')}"])
645
+ fiber.set_caller([location.join(':')])
633
646
  end
634
647
  end
635
648
 
@@ -651,11 +664,11 @@ class ::Fiber
651
664
  restart = opts[:restart]
652
665
  return nil unless block || restart
653
666
 
654
- error_only = !!opts[:on_error]
667
+ error_only = opts[:on_error]
655
668
  restart_always = (restart == :always) || (restart == true)
656
669
  restart_on_error = restart == :on_error
657
670
 
658
- ->(f, r) do
671
+ lambda do |f, r|
659
672
  is_error = r.is_a?(Exception)
660
673
  block.(f, r) if block && (!error_only || is_error)
661
674
  f.restart if restart_always || (restart_on_error && is_error)