polyphony 0.49.0 → 0.51.0

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.
@@ -101,7 +101,8 @@ VALUE Fiber_await(VALUE self) {
101
101
  }
102
102
  rb_hash_aset(waiting_fibers, fiber, Qtrue);
103
103
 
104
- result = Thread_switch_fiber(rb_thread_current());
104
+ VALUE backend = rb_ivar_get(rb_thread_current(), ID_ivar_backend);
105
+ result = Backend_wait_event(backend, Qnil);
105
106
 
106
107
  rb_hash_delete(waiting_fibers, fiber);
107
108
  RAISE_IF_EXCEPTION(result);
@@ -84,6 +84,7 @@ void Runqueue_push(VALUE self, VALUE fiber, VALUE value, int reschedule);
84
84
  void Runqueue_unshift(VALUE self, VALUE fiber, VALUE value, int reschedule);
85
85
  runqueue_entry Runqueue_shift(VALUE self);
86
86
  void Runqueue_delete(VALUE self, VALUE fiber);
87
+ int Runqueue_index_of(VALUE self, VALUE fiber);
87
88
  void Runqueue_clear(VALUE self);
88
89
  long Runqueue_len(VALUE self);
89
90
  int Runqueue_empty_p(VALUE self);
@@ -74,6 +74,12 @@ void Runqueue_delete(VALUE self, VALUE fiber) {
74
74
  runqueue_ring_buffer_delete(&runqueue->entries, fiber);
75
75
  }
76
76
 
77
+ int Runqueue_index_of(VALUE self, VALUE fiber) {
78
+ Runqueue_t *runqueue;
79
+ GetRunqueue(self, runqueue);
80
+ return runqueue_ring_buffer_index_of(&runqueue->entries, fiber);
81
+ }
82
+
77
83
  void Runqueue_clear(VALUE self) {
78
84
  Runqueue_t *runqueue;
79
85
  GetRunqueue(self, runqueue);
@@ -80,6 +80,15 @@ void runqueue_ring_buffer_delete(runqueue_ring_buffer *buffer, VALUE fiber) {
80
80
  }
81
81
  }
82
82
 
83
+ int runqueue_ring_buffer_index_of(runqueue_ring_buffer *buffer, VALUE fiber) {
84
+ for (unsigned int i = 0; i < buffer->count; i++) {
85
+ unsigned int idx = (buffer->head + i) % buffer->size;
86
+ if (buffer->entries[idx].fiber == fiber)
87
+ return i;
88
+ }
89
+ return -1;
90
+ }
91
+
83
92
  void runqueue_ring_buffer_clear(runqueue_ring_buffer *buffer) {
84
93
  buffer->count = buffer->head = buffer->tail = 0;
85
94
  }
@@ -27,5 +27,6 @@ void runqueue_ring_buffer_unshift(runqueue_ring_buffer *buffer, VALUE fiber, VAL
27
27
  void runqueue_ring_buffer_push(runqueue_ring_buffer *buffer, VALUE fiber, VALUE value);
28
28
 
29
29
  void runqueue_ring_buffer_delete(runqueue_ring_buffer *buffer, VALUE fiber);
30
+ int runqueue_ring_buffer_index_of(runqueue_ring_buffer *buffer, VALUE fiber);
30
31
 
31
32
  #endif /* RUNQUEUE_RING_BUFFER_H */
@@ -59,6 +59,18 @@ void schedule_fiber(VALUE self, VALUE fiber, VALUE value, int prioritize) {
59
59
  }
60
60
  }
61
61
 
62
+ VALUE Thread_fiber_scheduling_index(VALUE self, VALUE fiber) {
63
+ VALUE runqueue = rb_ivar_get(self, ID_ivar_runqueue);
64
+
65
+ return INT2NUM(Runqueue_index_of(runqueue, fiber));
66
+ }
67
+
68
+ VALUE Thread_fiber_unschedule(VALUE self, VALUE fiber) {
69
+ VALUE runqueue = rb_ivar_get(self, ID_ivar_runqueue);
70
+ Runqueue_delete(runqueue, fiber);
71
+ return self;
72
+ }
73
+
62
74
  VALUE Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
63
75
  schedule_fiber(self, fiber, value, 0);
64
76
  return self;
@@ -135,6 +147,8 @@ void Init_Thread() {
135
147
  rb_define_method(rb_cThread, "schedule_fiber_with_priority",
136
148
  Thread_schedule_fiber_with_priority, 2);
137
149
  rb_define_method(rb_cThread, "switch_fiber", Thread_switch_fiber, 0);
150
+ rb_define_method(rb_cThread, "fiber_scheduling_index", Thread_fiber_scheduling_index, 1);
151
+ rb_define_method(rb_cThread, "fiber_unschedule", Thread_fiber_unschedule, 1);
138
152
 
139
153
  rb_define_method(rb_cThread, "debug!", Thread_debug, 0);
140
154
 
data/lib/polyphony.rb CHANGED
@@ -44,7 +44,7 @@ module Polyphony
44
44
  rescue SystemExit
45
45
  # fall through to ensure
46
46
  rescue Exception => e
47
- warn e.full_message
47
+ STDERR << e.full_message
48
48
  exit!
49
49
  ensure
50
50
  exit_forked_process
@@ -24,6 +24,8 @@ module Polyphony
24
24
  def kill_and_await(sig, pid)
25
25
  ::Process.kill(sig, pid)
26
26
  Thread.current.backend.waitpid(pid)
27
+ rescue Errno::ESRCH
28
+ # process doesn't exist
27
29
  end
28
30
  end
29
31
  end
@@ -13,6 +13,7 @@ module Polyphony
13
13
  def initialize(value = nil)
14
14
  @caller_backtrace = caller
15
15
  @value = value
16
+ super
16
17
  end
17
18
  end
18
19
 
@@ -29,7 +29,7 @@ module Polyphony
29
29
  spin do
30
30
  sleep interval
31
31
  exception = cancel_exception(with_exception)
32
- exception.__raising_fiber__ = nil
32
+ exception.raising_fiber = nil
33
33
  fiber.schedule exception
34
34
  end
35
35
  end
@@ -11,12 +11,45 @@ module Polyphony
11
11
  def stop
12
12
  @fiber.stop
13
13
  end
14
+
15
+ def sleep(duration)
16
+ fiber = Fiber.current
17
+ @timeouts[fiber] = {
18
+ interval: duration,
19
+ target_stamp: now + duration
20
+ }
21
+ Thread.current.backend.wait_event(true)
22
+ ensure
23
+ @timeouts.delete(fiber)
24
+ end
25
+
26
+ def after(interval, &block)
27
+ spin do
28
+ self.sleep interval
29
+ block.()
30
+ end
31
+ end
32
+
33
+ def every(interval)
34
+ fiber = Fiber.current
35
+ @timeouts[fiber] = {
36
+ interval: interval,
37
+ target_stamp: now + interval,
38
+ recurring: true
39
+ }
40
+ while true
41
+ Thread.current.backend.wait_event(true)
42
+ yield
43
+ end
44
+ ensure
45
+ @timeouts.delete(fiber)
46
+ end
14
47
 
15
- def cancel_after(duration, with_exception: Polyphony::Cancel)
48
+ def cancel_after(interval, with_exception: Polyphony::Cancel)
16
49
  fiber = Fiber.current
17
50
  @timeouts[fiber] = {
18
- duration: duration,
19
- target_stamp: Time.now + duration,
51
+ interval: interval,
52
+ target_stamp: now + interval,
20
53
  exception: with_exception
21
54
  }
22
55
  yield
@@ -24,12 +57,12 @@ module Polyphony
24
57
  @timeouts.delete(fiber)
25
58
  end
26
59
 
27
- def move_on_after(duration, with_value: nil)
60
+ def move_on_after(interval, with_value: nil)
28
61
  fiber = Fiber.current
29
62
  @timeouts[fiber] = {
30
- duration: duration,
31
- target_stamp: Time.now + duration,
32
- value: with_value
63
+ interval: interval,
64
+ target_stamp: now + interval,
65
+ exception: [Polyphony::MoveOn, with_value]
33
66
  }
34
67
  yield
35
68
  rescue Polyphony::MoveOn => e
@@ -37,36 +70,46 @@ module Polyphony
37
70
  ensure
38
71
  @timeouts.delete(fiber)
39
72
  end
40
-
73
+
41
74
  def reset
42
75
  record = @timeouts[Fiber.current]
43
76
  return unless record
44
77
 
45
- record[:target_stamp] = Time.now + record[:duration]
78
+ record[:target_stamp] = now + record[:interval]
46
79
  end
47
-
80
+
48
81
  private
49
82
 
83
+ def now
84
+ ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
85
+ end
86
+
50
87
  def timeout_exception(record)
51
88
  case (exception = record[:exception])
52
- when Class then exception.new
53
- when Array then exception[0].new(exception[1])
54
- when nil then Polyphony::MoveOn.new(record[:value])
55
- else RuntimeError.new(exception)
89
+ when Array
90
+ exception[0].new(exception[1])
91
+ when Class
92
+ exception.new
93
+ else
94
+ RuntimeError.new(exception)
56
95
  end
57
96
  end
58
97
 
59
98
  def update
60
- now = Time.now
61
- # elapsed = nil
99
+ return if @timeouts.empty?
100
+
62
101
  @timeouts.each do |fiber, record|
63
102
  next if record[:target_stamp] > now
64
103
 
65
- exception = timeout_exception(record)
66
- # (elapsed ||= []) << fiber
67
- fiber.schedule exception
104
+ value = record[:exception] ? timeout_exception(record) : record[:value]
105
+ fiber.schedule value
106
+
107
+ next unless record[:recurring]
108
+
109
+ while record[:target_stamp] <= now
110
+ record[:target_stamp] += record[:interval]
111
+ end
68
112
  end
69
- # elapsed&.each { |f| @timeouts.delete(f) }
70
113
  end
71
114
  end
72
115
  end
@@ -12,11 +12,11 @@ class ::Exception
12
12
  attr_accessor :__disable_sanitized_backtrace__
13
13
  end
14
14
 
15
- attr_accessor :source_fiber, :__raising_fiber__
15
+ attr_accessor :source_fiber, :raising_fiber
16
16
 
17
17
  alias_method :orig_initialize, :initialize
18
18
  def initialize(*args)
19
- @__raising_fiber__ = Fiber.current
19
+ @raising_fiber = Fiber.current
20
20
  orig_initialize(*args)
21
21
  end
22
22
 
@@ -31,10 +31,10 @@ class ::Exception
31
31
  end
32
32
 
33
33
  def sanitized_backtrace
34
- return sanitize(orig_backtrace) unless @__raising_fiber__
34
+ return sanitize(orig_backtrace) unless @raising_fiber
35
35
 
36
36
  backtrace = orig_backtrace || []
37
- sanitize(backtrace + @__raising_fiber__.caller)
37
+ sanitize(backtrace + @raising_fiber.caller)
38
38
  end
39
39
 
40
40
  POLYPHONY_DIR = File.expand_path(File.join(__dir__, '..'))
@@ -245,8 +245,12 @@ module Polyphony
245
245
  end
246
246
 
247
247
  def shutdown_all_children(graceful = false)
248
- terminate_all_children(graceful)
249
- await_all_children
248
+ return unless @children
249
+
250
+ @children.keys.each do |c|
251
+ c.terminate(graceful)
252
+ c.await
253
+ end
250
254
  end
251
255
  end
252
256
 
@@ -312,6 +316,8 @@ module Polyphony
312
316
  @running = false
313
317
  inform_dependants(result, uncaught_exception)
314
318
  ensure
319
+ # Prevent fiber from being resumed after terminating
320
+ @thread.fiber_unschedule(self)
315
321
  Thread.current.switch_fiber
316
322
  end
317
323
 
@@ -319,13 +325,10 @@ module Polyphony
319
325
  # the children are shut down, it is returned along with the uncaught_exception
320
326
  # flag set. Otherwise, it returns the given arguments.
321
327
  def finalize_children(result, uncaught_exception)
322
- begin
323
- shutdown_all_children
324
- rescue Exception => e
325
- result = e
326
- uncaught_exception = true
327
- end
328
+ shutdown_all_children
328
329
  [result, uncaught_exception]
330
+ rescue Exception => e
331
+ [e, true]
329
332
  end
330
333
 
331
334
  def inform_dependants(result, uncaught_exception)
@@ -220,6 +220,10 @@ class ::IO
220
220
  Thread.current.backend.read_loop(self, &block)
221
221
  end
222
222
 
223
+ def feed_loop(receiver, method, &block)
224
+ Thread.current.backend.feed_loop(self, receiver, method, &block)
225
+ end
226
+
223
227
  # alias_method :orig_read, :read
224
228
  # def read(length = nil, outbuf = nil)
225
229
  # if length
@@ -31,6 +31,10 @@ class ::Socket
31
31
  end
32
32
  alias_method :read_loop, :recv_loop
33
33
 
34
+ def feed_loop(receiver, method, &block)
35
+ Thread.current.backend.recv_feed_loop(self, receiver, method, &block)
36
+ end
37
+
34
38
  def recvfrom(maxlen, flags = 0)
35
39
  @read_buffer ||= +''
36
40
  while true
@@ -146,6 +150,10 @@ class ::TCPSocket
146
150
  end
147
151
  alias_method :read_loop, :recv_loop
148
152
 
153
+ def feed_loop(receiver, method, &block)
154
+ Thread.current.backend.recv_feed_loop(self, receiver, method, &block)
155
+ end
156
+
149
157
  def send(mesg, flags = 0)
150
158
  Thread.current.backend.send(self, mesg)
151
159
  end
@@ -227,6 +235,10 @@ class ::UNIXSocket
227
235
  end
228
236
  alias_method :read_loop, :recv_loop
229
237
 
238
+ def feed_loop(receiver, method, &block)
239
+ Thread.current.backend.recv_feed_loop(self, receiver, method, &block)
240
+ end
241
+
230
242
  def send(mesg, flags = 0)
231
243
  Thread.current.backend.send(self, mesg)
232
244
  end
@@ -41,8 +41,7 @@ class ::Thread
41
41
 
42
42
  def finalize(result)
43
43
  unless Fiber.current.children.empty?
44
- Fiber.current.terminate_all_children
45
- Fiber.current.await_all_children
44
+ Fiber.current.shutdown_all_children
46
45
  end
47
46
  @finalization_mutex.synchronize do
48
47
  @terminated = true
data/lib/polyphony/net.rb CHANGED
@@ -8,10 +8,7 @@ module Polyphony
8
8
  module Net
9
9
  class << self
10
10
  def tcp_connect(host, port, opts = {})
11
- socket = ::Socket.new(:INET, :STREAM).tap do |s|
12
- addr = ::Socket.sockaddr_in(port, host)
13
- s.connect(addr)
14
- end
11
+ socket = TCPSocket.new(host, port)
15
12
  if opts[:secure_context] || opts[:secure]
16
13
  secure_socket(socket, opts[:secure_context], opts.merge(host: host))
17
14
  else
@@ -23,7 +20,7 @@ module Polyphony
23
20
  host ||= '0.0.0.0'
24
21
  raise 'Port number not specified' unless port
25
22
 
26
- socket = socket_from_options(host, port, opts)
23
+ socket = listening_socket_from_options(host, port, opts)
27
24
  if opts[:secure_context] || opts[:secure]
28
25
  secure_server(socket, opts[:secure_context], opts)
29
26
  else
@@ -31,7 +28,7 @@ module Polyphony
31
28
  end
32
29
  end
33
30
 
34
- def socket_from_options(host, port, opts)
31
+ def listening_socket_from_options(host, port, opts)
35
32
  ::Socket.new(:INET, :STREAM).tap do |s|
36
33
  s.reuse_addr if opts[:reuse_addr]
37
34
  s.dont_linger if opts[:dont_linger]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
- VERSION = '0.49.0'
4
+ VERSION = '0.51.0'
5
5
  end
data/polyphony.gemspec CHANGED
@@ -28,6 +28,7 @@ Gem::Specification.new do |s|
28
28
  s.add_development_dependency 'rubocop', '0.85.1'
29
29
  s.add_development_dependency 'pry', '0.13.1'
30
30
 
31
+ s.add_development_dependency 'msgpack', '1.4.2'
31
32
  s.add_development_dependency 'pg', '1.1.4'
32
33
  s.add_development_dependency 'redis', '4.1.0'
33
34
  s.add_development_dependency 'hiredis', '0.6.3'
data/test/helper.rb CHANGED
@@ -57,8 +57,7 @@ class MiniTest::Test
57
57
 
58
58
  def teardown
59
59
  # trace "* teardown #{self.name}"
60
- Fiber.current.terminate_all_children
61
- Fiber.current.await_all_children
60
+ Fiber.current.shutdown_all_children
62
61
  Fiber.current.instance_variable_set(:@auto_watcher, nil)
63
62
  rescue => e
64
63
  puts e
data/test/test_backend.rb CHANGED
@@ -100,6 +100,31 @@ class BackendTest < MiniTest::Test
100
100
  assert_equal [:ready, 'foo', 'bar', :done], buf
101
101
  end
102
102
 
103
+ def test_read_loop_terminate
104
+ i, o = IO.pipe
105
+
106
+ buf = []
107
+ parent = spin do
108
+ f = spin do
109
+ buf << :ready
110
+ @backend.read_loop(i) { |d| buf << d }
111
+ buf << :done
112
+ end
113
+ suspend
114
+ end
115
+
116
+ # writing always causes snoozing
117
+ o << 'foo'
118
+ sleep 0.01
119
+ o << 'bar'
120
+ sleep 0.01
121
+
122
+ parent.stop
123
+
124
+ parent.await
125
+ assert_equal [:ready, 'foo', 'bar'], buf
126
+ end
127
+
103
128
  def test_accept_loop
104
129
  server = TCPServer.new('127.0.0.1', 1234)
105
130