polyphony 0.57.0 → 0.60

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +27 -0
  3. data/Gemfile.lock +15 -29
  4. data/examples/core/message_based_supervision.rb +51 -0
  5. data/ext/polyphony/backend_common.c +108 -3
  6. data/ext/polyphony/backend_common.h +23 -0
  7. data/ext/polyphony/backend_io_uring.c +117 -39
  8. data/ext/polyphony/backend_io_uring_context.c +11 -3
  9. data/ext/polyphony/backend_io_uring_context.h +5 -3
  10. data/ext/polyphony/backend_libev.c +92 -30
  11. data/ext/polyphony/extconf.rb +2 -2
  12. data/ext/polyphony/fiber.c +1 -34
  13. data/ext/polyphony/polyphony.c +12 -19
  14. data/ext/polyphony/polyphony.h +10 -20
  15. data/ext/polyphony/polyphony_ext.c +0 -4
  16. data/ext/polyphony/queue.c +12 -12
  17. data/ext/polyphony/runqueue.c +17 -85
  18. data/ext/polyphony/runqueue.h +27 -0
  19. data/ext/polyphony/thread.c +10 -99
  20. data/lib/polyphony/core/timer.rb +2 -2
  21. data/lib/polyphony/extensions/fiber.rb +102 -82
  22. data/lib/polyphony/extensions/io.rb +10 -9
  23. data/lib/polyphony/extensions/openssl.rb +14 -4
  24. data/lib/polyphony/extensions/socket.rb +15 -15
  25. data/lib/polyphony/extensions/thread.rb +8 -0
  26. data/lib/polyphony/version.rb +1 -1
  27. data/polyphony.gemspec +0 -7
  28. data/test/test_backend.rb +71 -5
  29. data/test/test_ext.rb +1 -1
  30. data/test/test_fiber.rb +106 -18
  31. data/test/test_global_api.rb +1 -1
  32. data/test/test_io.rb +29 -0
  33. data/test/test_supervise.rb +100 -100
  34. data/test/test_thread.rb +57 -11
  35. data/test/test_thread_pool.rb +1 -1
  36. data/test/test_trace.rb +28 -49
  37. metadata +4 -108
  38. data/ext/polyphony/tracing.c +0 -11
  39. data/lib/polyphony/adapters/trace.rb +0 -138
@@ -3,8 +3,8 @@
3
3
  module Polyphony
4
4
  # Implements a common timer for running multiple timeouts
5
5
  class Timer
6
- def initialize(resolution:)
7
- @fiber = spin_loop(interval: resolution) { update }
6
+ def initialize(tag = nil, resolution:)
7
+ @fiber = spin_loop(tag, interval: resolution) { update }
8
8
  @timeouts = {}
9
9
  end
10
10
 
@@ -22,9 +22,9 @@ module Polyphony
22
22
  return self
23
23
  end
24
24
 
25
- parent.spin(@tag, @caller, &@block).tap do |f|
26
- f.schedule(value) unless value.nil?
27
- end
25
+ fiber = parent.spin(@tag, @caller, &@block)
26
+ fiber.schedule(value) unless value.nil?
27
+ fiber
28
28
  end
29
29
  alias_method :reset, :restart
30
30
 
@@ -66,6 +66,11 @@ module Polyphony
66
66
  def interject(&block)
67
67
  raise Polyphony::Interjection.new(block)
68
68
  end
69
+
70
+ def await
71
+ Fiber.await(self).first
72
+ end
73
+ alias_method :join, :await
69
74
  end
70
75
 
71
76
  # Fiber supervision
@@ -78,7 +83,7 @@ module Polyphony
78
83
  while true
79
84
  supervise_perform(opts)
80
85
  end
81
- rescue Polyphony::MoveOn
86
+ rescue Polyphony::MoveOn
82
87
  # generated in #supervise_perform to stop supervisor
83
88
  ensure
84
89
  @on_child_done = nil
@@ -119,72 +124,62 @@ module Polyphony
119
124
  def await(*fibers)
120
125
  return [] if fibers.empty?
121
126
 
122
- state = setup_await_select_state(fibers)
123
- await_setup_monitoring(fibers, state)
124
- suspend
125
- fibers.map(&:result)
126
- ensure
127
- await_select_cleanup(state) if state
128
- end
129
- alias_method :join, :await
130
-
131
- def setup_await_select_state(fibers)
132
- {
133
- awaiter: Fiber.current,
134
- pending: fibers.each_with_object({}) { |f, h| h[f] = true }
135
- }
136
- end
137
-
138
- def await_setup_monitoring(fibers, state)
127
+ Fiber.current.message_on_child_termination = true
128
+ results = {}
139
129
  fibers.each do |f|
140
- f.when_done { |r| await_fiber_done(f, r, state) }
130
+ results[f] = nil
131
+ if f.dead?
132
+ # fiber already terminated, so queue message
133
+ Fiber.current.send [f, f.result]
134
+ else
135
+ f.monitor
136
+ end
141
137
  end
142
- end
143
-
144
- def await_fiber_done(fiber, result, state)
145
- state[:pending].delete(fiber)
146
-
147
- if state[:cleanup]
148
- state[:awaiter].schedule if state[:pending].empty?
149
- elsif !state[:done] && (result.is_a?(Exception) || state[:pending].empty?)
150
- state[:awaiter].schedule(result)
151
- state[:done] = true
138
+ exception = nil
139
+ while !fibers.empty?
140
+ (fiber, result) = receive
141
+ next unless fibers.include?(fiber)
142
+
143
+ fibers.delete(fiber)
144
+ if result.is_a?(Exception)
145
+ exception ||= result
146
+ fibers.each { |f| f.terminate }
147
+ else
148
+ results[fiber] = result
149
+ end
152
150
  end
153
- end
154
-
155
- def await_select_cleanup(state)
156
- return if state[:pending].empty?
157
-
158
- terminate = Polyphony::Terminate.new
159
- state[:cleanup] = true
160
- state[:pending].each_key { |f| f.schedule(terminate) }
161
- suspend
162
- end
163
-
164
- def select(*fibers)
165
- state = setup_await_select_state(fibers)
166
- select_setup_monitoring(fibers, state)
167
- suspend
151
+ results.values
168
152
  ensure
169
- await_select_cleanup(state)
153
+ Fiber.current.message_on_child_termination = false
154
+ raise exception if exception
170
155
  end
156
+ alias_method :join, :await
171
157
 
172
- def select_setup_monitoring(fibers, state)
158
+ def select(*fibers)
159
+ return nil if fibers.empty?
160
+
173
161
  fibers.each do |f|
174
- f.when_done { |r| select_fiber_done(f, r, state) }
162
+ if f.dead?
163
+ result = f.result
164
+ result.is_a?(Exception) ? (raise result) : (return [f, result])
165
+ end
175
166
  end
176
- end
177
167
 
178
- def select_fiber_done(fiber, result, state)
179
- state[:pending].delete(fiber)
180
- if state[:cleanup]
181
- # in cleanup mode the selector is resumed if no more pending fibers
182
- state[:awaiter].schedule if state[:pending].empty?
183
- elsif !state[:selected]
184
- # first fiber to complete, we schedule the result
185
- state[:awaiter].schedule([fiber, result])
186
- state[:selected] = true
168
+ Fiber.current.message_on_child_termination = true
169
+ fibers.each { |f| f.monitor }
170
+ while true
171
+ (fiber, result) = receive
172
+ next unless fibers.include?(fiber)
173
+
174
+ fibers.each { |f| f.unmonitor }
175
+ if result.is_a?(Exception)
176
+ raise result
177
+ else
178
+ return [fiber, result]
179
+ end
187
180
  end
181
+ ensure
182
+ Fiber.current.message_on_child_termination = false
188
183
  end
189
184
 
190
185
  # Creates and schedules with priority an out-of-band fiber that runs the
@@ -209,6 +204,14 @@ module Polyphony
209
204
  (@children ||= {}).keys
210
205
  end
211
206
 
207
+ def add_child(child_fiber)
208
+ (@children ||= {})[child_fiber] = true
209
+ end
210
+
211
+ def remove_child(child_fiber)
212
+ @children.delete(child_fiber) if @children
213
+ end
214
+
212
215
  def spin(tag = nil, orig_caller = Kernel.caller, &block)
213
216
  f = Fiber.new { |v| f.run(v) }
214
217
  f.prepare(tag, block, orig_caller, self)
@@ -218,7 +221,10 @@ module Polyphony
218
221
 
219
222
  def child_done(child_fiber, result)
220
223
  @children.delete(child_fiber)
221
- @on_child_done&.(child_fiber, result)
224
+
225
+ if result.is_a?(Exception) && !@message_on_child_termination
226
+ schedule_with_priority(result)
227
+ end
222
228
  end
223
229
 
224
230
  def terminate_all_children(graceful = false)
@@ -234,14 +240,7 @@ module Polyphony
234
240
  def await_all_children
235
241
  return unless @children && !@children.empty?
236
242
 
237
- results = @children.dup
238
- @on_child_done = proc do |c, r|
239
- results[c] = r
240
- schedule if @children.empty?
241
- end
242
- suspend
243
- @on_child_done = nil
244
- results.values
243
+ Fiber.await(*@children.keys)
245
244
  end
246
245
 
247
246
  def shutdown_all_children(graceful = false)
@@ -252,6 +251,18 @@ module Polyphony
252
251
  c.await
253
252
  end
254
253
  end
254
+
255
+ def detach
256
+ @parent.remove_child(self)
257
+ @parent = @thread.main_fiber
258
+ @parent.add_child(self)
259
+ end
260
+
261
+ def attach(parent)
262
+ @parent.remove_child(self)
263
+ @parent = parent
264
+ @parent.add_child(self)
265
+ end
255
266
  end
256
267
 
257
268
  # Fiber life cycle methods
@@ -262,7 +273,7 @@ module Polyphony
262
273
  @parent = parent
263
274
  @caller = caller
264
275
  @block = block
265
- __fiber_trace__(:fiber_create, self)
276
+ Thread.backend.trace(:fiber_create, self)
266
277
  schedule
267
278
  end
268
279
 
@@ -304,17 +315,16 @@ module Polyphony
304
315
 
305
316
  def restart_self(first_value)
306
317
  @mailbox = nil
307
- @when_done_procs = nil
308
- @waiting_fibers = nil
309
318
  run(first_value)
310
319
  end
311
320
 
312
321
  def finalize(result, uncaught_exception = false)
313
322
  result, uncaught_exception = finalize_children(result, uncaught_exception)
314
- __fiber_trace__(:fiber_terminate, self, result)
323
+ Thread.backend.trace(:fiber_terminate, self, result)
315
324
  @result = result
316
- @running = false
325
+
317
326
  inform_dependants(result, uncaught_exception)
327
+ @running = false
318
328
  ensure
319
329
  # Prevent fiber from being resumed after terminating
320
330
  @thread.fiber_unschedule(self)
@@ -332,17 +342,27 @@ module Polyphony
332
342
  end
333
343
 
334
344
  def inform_dependants(result, uncaught_exception)
345
+ if @monitors
346
+ msg = [self, result]
347
+ @monitors.each { |f| f << msg }
348
+ end
349
+
335
350
  @parent&.child_done(self, result)
336
- @when_done_procs&.each { |p| p.(result) }
337
- @waiting_fibers&.each_key { |f| f.schedule(result) }
338
-
339
- # propagate unaught exception to parent
340
- @parent&.schedule_with_priority(result) if uncaught_exception && !@waiting_fibers
341
351
  end
342
352
 
343
- def when_done(&block)
344
- @when_done_procs ||= []
345
- @when_done_procs << block
353
+ attr_accessor :message_on_child_termination
354
+
355
+ def monitor
356
+ @monitors ||= []
357
+ @monitors << Fiber.current
358
+ end
359
+
360
+ def unmonitor
361
+ @monitors.delete(Fiber.current) if @monitors
362
+ end
363
+
364
+ def dead?
365
+ state == :dead
346
366
  end
347
367
  end
348
368
  end
@@ -101,7 +101,7 @@ class ::IO
101
101
  return @read_buffer.slice!(0) if @read_buffer && !@read_buffer.empty?
102
102
 
103
103
  @read_buffer ||= +''
104
- Polyphony.backend_read(self, @read_buffer, 8192, false)
104
+ Polyphony.backend_read(self, @read_buffer, 8192, false, -1)
105
105
  return @read_buffer.slice!(0) if !@read_buffer.empty?
106
106
 
107
107
  nil
@@ -110,7 +110,7 @@ class ::IO
110
110
  alias_method :orig_read, :read
111
111
  def read(len = nil)
112
112
  @read_buffer ||= +''
113
- result = Polyphony.backend_read(self, @read_buffer, len, true)
113
+ result = Polyphony.backend_read(self, @read_buffer, len, true, -1)
114
114
  return nil unless result
115
115
 
116
116
  already_read = @read_buffer
@@ -119,8 +119,8 @@ class ::IO
119
119
  end
120
120
 
121
121
  alias_method :orig_readpartial, :read
122
- def readpartial(len, str = +'')
123
- result = Polyphony.backend_read(self, str, len, false)
122
+ def readpartial(len, str = +'', buffer_pos = 0)
123
+ result = Polyphony.backend_read(self, str, len, false, buffer_pos)
124
124
  raise EOFError unless result
125
125
 
126
126
  result
@@ -151,9 +151,10 @@ class ::IO
151
151
  idx = @read_buffer.index(sep)
152
152
  return @read_buffer.slice!(0, idx + sep_size) if idx
153
153
 
154
- data = readpartial(8192, +'')
155
- return nil unless data
156
- @read_buffer << data
154
+ result = readpartial(8192, @read_buffer, -1)
155
+
156
+ #Polyphony.backend_read(self, @read_buffer, 8192, false, -1)
157
+ return nil unless result
157
158
  end
158
159
  rescue EOFError
159
160
  return nil
@@ -216,8 +217,8 @@ class ::IO
216
217
  buf ? readpartial(maxlen, buf) : readpartial(maxlen)
217
218
  end
218
219
 
219
- def read_loop(&block)
220
- Polyphony.backend_read_loop(self, &block)
220
+ def read_loop(maxlen = 8192, &block)
221
+ Polyphony.backend_read_loop(self, maxlen, &block)
221
222
  end
222
223
 
223
224
  def feed_loop(receiver, method = :call, &block)
@@ -64,13 +64,23 @@ class ::OpenSSL::SSL::SSLSocket
64
64
  # @sync = osync
65
65
  end
66
66
 
67
- def readpartial(maxlen, buf = +'')
68
- result = sysread(maxlen, buf)
67
+ def readpartial(maxlen, buf = +'', buffer_pos = 0)
68
+ if buffer_pos != 0
69
+ if (result = sysread(maxlen, +''))
70
+ if buffer_pos == -1
71
+ result = buf + result
72
+ else
73
+ result = buf[0...buffer_pos] + result
74
+ end
75
+ end
76
+ else
77
+ result = sysread(maxlen, buf)
78
+ end
69
79
  result || (raise EOFError)
70
80
  end
71
81
 
72
- def read_loop
73
- while (data = sysread(8192))
82
+ def read_loop(maxlen = 8192)
83
+ while (data = sysread(maxlen))
74
84
  yield data
75
85
  end
76
86
  end
@@ -23,11 +23,11 @@ class ::Socket
23
23
  end
24
24
 
25
25
  def recv(maxlen, flags = 0, outbuf = nil)
26
- Polyphony.backend_recv(self, outbuf || +'', maxlen)
26
+ Polyphony.backend_recv(self, outbuf || +'', maxlen, 0)
27
27
  end
28
28
 
29
- def recv_loop(&block)
30
- Polyphony.backend_recv_loop(self, &block)
29
+ def recv_loop(maxlen = 8192, &block)
30
+ Polyphony.backend_recv_loop(self, maxlen, &block)
31
31
  end
32
32
  alias_method :read_loop, :recv_loop
33
33
 
@@ -60,8 +60,8 @@ class ::Socket
60
60
  # Polyphony.backend_send(self, mesg, 0)
61
61
  # end
62
62
 
63
- def readpartial(maxlen, str = +'')
64
- Polyphony.backend_recv(self, str, maxlen)
63
+ def readpartial(maxlen, str = +'', buffer_pos = 0)
64
+ Polyphony.backend_recv(self, str, maxlen, buffer_pos)
65
65
  end
66
66
 
67
67
  ZERO_LINGER = [0, 0].pack('ii').freeze
@@ -141,11 +141,11 @@ class ::TCPSocket
141
141
  end
142
142
 
143
143
  def recv(maxlen, flags = 0, outbuf = nil)
144
- Polyphony.backend_recv(self, outbuf || +'', maxlen)
144
+ Polyphony.backend_recv(self, outbuf || +'', maxlen, 0)
145
145
  end
146
146
 
147
- def recv_loop(&block)
148
- Polyphony.backend_recv_loop(self, &block)
147
+ def recv_loop(maxlen = 8192, &block)
148
+ Polyphony.backend_recv_loop(self, maxlen, &block)
149
149
  end
150
150
  alias_method :read_loop, :recv_loop
151
151
 
@@ -165,8 +165,8 @@ class ::TCPSocket
165
165
  # Polyphony.backend_send(self, mesg, 0)
166
166
  # end
167
167
 
168
- def readpartial(maxlen, str = +'')
169
- result = Polyphony.backend_recv(self, str, maxlen)
168
+ def readpartial(maxlen, str = +'', buffer_pos = 0)
169
+ result = Polyphony.backend_recv(self, str, maxlen, buffer_pos)
170
170
  raise EOFError unless result
171
171
 
172
172
  str
@@ -218,11 +218,11 @@ end
218
218
 
219
219
  class ::UNIXSocket
220
220
  def recv(maxlen, flags = 0, outbuf = nil)
221
- Polyphony.backend_recv(self, outbuf || +'', maxlen)
221
+ Polyphony.backend_recv(self, outbuf || +'', maxlen, 0)
222
222
  end
223
223
 
224
- def recv_loop(&block)
225
- Polyphony.backend_recv_loop(self, &block)
224
+ def recv_loop(maxlen = 8192, &block)
225
+ Polyphony.backend_recv_loop(self, maxlen, &block)
226
226
  end
227
227
  alias_method :read_loop, :recv_loop
228
228
 
@@ -242,8 +242,8 @@ class ::UNIXSocket
242
242
  Polyphony.backend_send(self, mesg, 0)
243
243
  end
244
244
 
245
- def readpartial(maxlen, str = +'')
246
- result = Polyphony.backend_recv(self, str, maxlen)
245
+ def readpartial(maxlen, str = +'', buffer_pos = 0)
246
+ result = Polyphony.backend_recv(self, str, maxlen, buffer_pos)
247
247
  raise EOFError unless result
248
248
 
249
249
  str
@@ -105,4 +105,12 @@ class ::Thread
105
105
  main_fiber << value
106
106
  end
107
107
  alias_method :send, :<<
108
+
109
+ def idle_gc_period=(period)
110
+ backend.idle_gc_period = period
111
+ end
112
+
113
+ def on_idle(&block)
114
+ backend.idle_proc = block
115
+ end
108
116
  end