polyphony 0.57.0 → 0.60

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