polyphony 1.1 → 1.2

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 (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 +13 -0
  6. data/README.md +2 -1
  7. data/docs/advanced-io.md +141 -44
  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 +2 -2
  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 +6 -4
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ class ::Enumerator
7
+ def spin
8
+ map { |i| Object.spin { yield i } }
9
+ end
10
+
11
+ def concurrently(max_fibers: nil, &block)
12
+ return each_concurrently_with_fiber_pool(max_fibers, &block) if max_fibers
13
+
14
+ results = []
15
+ fibers = []
16
+ each_with_index do |i, idx|
17
+ fibers << Object.spin { results[idx] = block.(i) }
18
+ end
19
+ Fiber.await(fibers)
20
+ results
21
+ end
22
+
23
+ private
24
+
25
+ def each_concurrently_with_fiber_pool(max_fibers, &block)
26
+ fiber_count = 0
27
+ results = []
28
+ workers = []
29
+
30
+ each_with_index do |i, idx|
31
+ if fiber_count < max_fibers
32
+ workers << Object.spin do
33
+ loop do
34
+ item, idx = receive
35
+ break if item == :__stop__
36
+ results[idx] = block.(item)
37
+ end
38
+ end
39
+ end
40
+
41
+ fiber = workers.shift
42
+ fiber << [i, idx]
43
+ workers << fiber
44
+ end
45
+ workers.each { |f| f << :__stop__ }
46
+ Fiber.current.await_all_children
47
+ results
48
+ end
49
+ end
50
+
51
+ a = [1, 2, 3]
52
+
53
+ # ff = a.map do |i|
54
+ # spin do
55
+ # puts "#{Fiber.current.inspect} #{i} >>"
56
+ # sleep rand(0.1..0.2)
57
+ # puts "#{Fiber.current.inspect} #{i} <<"
58
+ # end
59
+ # end
60
+
61
+ # Fiber.await(*ff)
62
+
63
+ # puts; puts '*' * 40; puts
64
+
65
+ # ff = a.each.spin do |i|
66
+ # puts "#{Fiber.current.inspect} #{i} >>"
67
+ # sleep 0.1
68
+ # puts "#{Fiber.current.inspect} #{i} <<"
69
+ # end
70
+
71
+ # Fiber.await(*ff)
72
+
73
+ # puts; puts '*' * 40; puts
74
+
75
+ # ff = a.each.concurrently do |i|
76
+ # puts "#{Fiber.current.inspect} #{i} >>"
77
+ # sleep 0.1
78
+ # puts "#{Fiber.current.inspect} #{i} <<"
79
+ # i * 10
80
+ # end
81
+ # p ff: ff
82
+
83
+ puts; puts '*' * 40; puts
84
+
85
+ ff = a.each.concurrently(max_fibers: 2) do |i|
86
+ puts "#{Fiber.current.inspect} #{i} >>"
87
+ sleep i
88
+ puts "#{Fiber.current.inspect} #{i} <<"
89
+ i * 10
90
+ end
91
+
92
+ p ff: ff
@@ -28,7 +28,7 @@ server = Polyphony::Net.tcp_listen('localhost', 1234, opts)
28
28
  puts 'Serving HTTPS on port 1234'
29
29
 
30
30
  begin
31
- server.accept_loop(false) do |socket|
31
+ server.accept_loop(ignore_errors: false) do |socket|
32
32
  spin do
33
33
  while (data = socket.gets("\n", 8192))
34
34
  if data.chomp.empty?
@@ -8,6 +8,8 @@
8
8
  #include "ruby/io/buffer.h"
9
9
  #endif
10
10
 
11
+ VALUE cBackend;
12
+
11
13
  inline void backend_base_initialize(struct Backend_base *base) {
12
14
  runqueue_initialize(&base->runqueue);
13
15
  runqueue_initialize(&base->parked_runqueue);
@@ -604,3 +606,12 @@ VALUE coerce_io_string_or_buffer(VALUE buf) {
604
606
  return StringValue(buf);
605
607
  }
606
608
  }
609
+
610
+ inline VALUE Backend_for_current_thread(void) {
611
+ VALUE backend = rb_ivar_get(rb_thread_current(), ID_ivar_backend);
612
+ if (backend == Qnil) {
613
+ backend = rb_funcall(cBackend, ID_new, 0);
614
+ rb_ivar_set(rb_thread_current(), ID_ivar_backend, backend);
615
+ }
616
+ return backend;
617
+ }
@@ -10,6 +10,8 @@
10
10
  #include "ruby/io.h"
11
11
  #include "runqueue.h"
12
12
 
13
+ extern VALUE cBackend;
14
+
13
15
  #ifndef HAVE_RB_IO_DESCRIPTOR
14
16
  static int rb_io_descriptor_fallback(VALUE io) {
15
17
  rb_io_t *fptr;
@@ -1965,7 +1965,7 @@ void Backend_unpark_fiber(VALUE self, VALUE fiber) {
1965
1965
  }
1966
1966
 
1967
1967
  void Init_Backend(void) {
1968
- VALUE cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cObject);
1968
+ cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cObject);
1969
1969
  rb_define_alloc_func(cBackend, Backend_allocate);
1970
1970
 
1971
1971
  rb_define_method(cBackend, "initialize", Backend_initialize, 0);
@@ -1610,7 +1610,7 @@ void Backend_unpark_fiber(VALUE self, VALUE fiber) {
1610
1610
  void Init_Backend(void) {
1611
1611
  ev_set_allocator(xrealloc);
1612
1612
 
1613
- VALUE cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cObject);
1613
+ cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cObject);
1614
1614
  rb_define_alloc_func(cBackend, Backend_allocate);
1615
1615
 
1616
1616
  rb_define_method(cBackend, "initialize", Backend_initialize, 0);
@@ -25,7 +25,7 @@
25
25
  #define FIBER_TRANSFER(fiber, value) rb_funcall(fiber, ID_transfer, 1, value)
26
26
  #endif
27
27
 
28
- #define BACKEND() (rb_ivar_get(rb_thread_current(), ID_ivar_backend))
28
+ #define BACKEND() Backend_for_current_thread()
29
29
 
30
30
  // SAFE is used to cast functions used in rb_ensure
31
31
  #define SAFE(f) (VALUE (*)(VALUE))(f)
@@ -95,6 +95,8 @@ VALUE Pipe_close(VALUE self);
95
95
 
96
96
  // Backend public interface
97
97
 
98
+ VALUE Backend_for_current_thread();
99
+
98
100
  VALUE Backend_accept(VALUE self, VALUE server_socket, VALUE socket_class);
99
101
  VALUE Backend_accept_loop(VALUE self, VALUE server_socket, VALUE socket_class);
100
102
  VALUE Backend_connect(VALUE self, VALUE io, VALUE addr, VALUE port);
@@ -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)