polyphony 0.42 → 0.43.4

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.
@@ -120,11 +120,12 @@ static VALUE LibevQueue_initialize(VALUE self) {
120
120
 
121
121
  VALUE LibevQueue_push(VALUE self, VALUE value) {
122
122
  LibevQueue_t *queue;
123
- struct async_watcher *watcher;
124
123
  GetQueue(self, queue);
125
- watcher = async_queue_pop(&queue->shift_queue);
126
- if (watcher) {
127
- ev_async_send(watcher->ev_loop, &watcher->async);
124
+ if (queue->shift_queue.count > 0) {
125
+ struct async_watcher *watcher = async_queue_pop(&queue->shift_queue);
126
+ if (watcher) {
127
+ ev_async_send(watcher->ev_loop, &watcher->async);
128
+ }
128
129
  }
129
130
  rb_ary_push(queue->items, value);
130
131
  return self;
@@ -7,11 +7,8 @@ ID ID_call;
7
7
  ID ID_caller;
8
8
  ID ID_clear;
9
9
  ID ID_each;
10
- ID ID_empty;
11
10
  ID ID_inspect;
12
11
  ID ID_new;
13
- ID ID_pop;
14
- ID ID_push;
15
12
  ID ID_raise;
16
13
  ID ID_ivar_running;
17
14
  ID ID_ivar_thread;
@@ -62,13 +59,10 @@ void Init_Polyphony() {
62
59
  ID_caller = rb_intern("caller");
63
60
  ID_clear = rb_intern("clear");
64
61
  ID_each = rb_intern("each");
65
- ID_empty = rb_intern("empty?");
66
62
  ID_inspect = rb_intern("inspect");
67
63
  ID_ivar_running = rb_intern("@running");
68
64
  ID_ivar_thread = rb_intern("@thread");
69
65
  ID_new = rb_intern("new");
70
- ID_pop = rb_intern("pop");
71
- ID_push = rb_intern("push");
72
66
  ID_raise = rb_intern("raise");
73
67
  ID_runnable = rb_intern("runnable");
74
68
  ID_runnable_value = rb_intern("runnable_value");
@@ -4,7 +4,6 @@ void Init_Fiber();
4
4
  void Init_Polyphony();
5
5
  void Init_LibevAgent();
6
6
  void Init_LibevQueue();
7
- void Init_Socket();
8
7
  void Init_Thread();
9
8
  void Init_Tracing();
10
9
 
@@ -16,7 +15,6 @@ void Init_polyphony_ext() {
16
15
  Init_LibevQueue();
17
16
 
18
17
  Init_Fiber();
19
- Init_Socket();
20
18
  Init_Thread();
21
19
 
22
20
  Init_Tracing();
@@ -1,14 +1,11 @@
1
1
  #include "polyphony.h"
2
2
 
3
3
  ID ID_deactivate_all_watchers_post_fork;
4
- ID ID_empty;
5
4
  ID ID_ivar_agent;
6
5
  ID ID_ivar_join_wait_queue;
7
6
  ID ID_ivar_main_fiber;
8
7
  ID ID_ivar_result;
9
8
  ID ID_ivar_terminated;
10
- ID ID_pop;
11
- ID ID_push;
12
9
  ID ID_run_queue;
13
10
  ID ID_runnable_next;
14
11
  ID ID_stop;
@@ -187,14 +184,11 @@ void Init_Thread() {
187
184
  rb_define_method(rb_cThread, "switch_fiber", Thread_switch_fiber, 0);
188
185
 
189
186
  ID_deactivate_all_watchers_post_fork = rb_intern("deactivate_all_watchers_post_fork");
190
- ID_empty = rb_intern("empty?");
191
187
  ID_ivar_agent = rb_intern("@agent");
192
188
  ID_ivar_join_wait_queue = rb_intern("@join_wait_queue");
193
189
  ID_ivar_main_fiber = rb_intern("@main_fiber");
194
190
  ID_ivar_result = rb_intern("@result");
195
191
  ID_ivar_terminated = rb_intern("@terminated");
196
- ID_pop = rb_intern("pop");
197
- ID_push = rb_intern("push");
198
192
  ID_run_queue = rb_intern("run_queue");
199
193
  ID_runnable_next = rb_intern("runnable_next");
200
194
  ID_stop = rb_intern("stop");
@@ -6,6 +6,12 @@ require_relative './polyphony_ext'
6
6
  module Polyphony
7
7
  # Map Queue to Libev queue implementation
8
8
  Queue = LibevQueue
9
+
10
+ # replace core Queue class with our own
11
+ verbose = $VERBOSE
12
+ $VERBOSE = nil
13
+ Object.const_set(:Queue, Polyphony::Queue)
14
+ $VERBOSE = verbose
9
15
  end
10
16
 
11
17
  require_relative './polyphony/extensions/core'
@@ -94,17 +100,9 @@ module Polyphony
94
100
  Polyphony::Process.watch(cmd, &block)
95
101
  end
96
102
 
97
- def emit_signal_exception(exception, fiber = Thread.main.main_fiber)
98
- Thread.current.break_out_of_ev_loop(fiber, exception)
99
- end
100
-
101
- def install_terminating_signal_handler(signal, exception_class)
102
- trap(signal) { emit_signal_exception(exception_class.new) }
103
- end
104
-
105
103
  def install_terminating_signal_handlers
106
- install_terminating_signal_handler('SIGTERM', ::SystemExit)
107
- install_terminating_signal_handler('SIGINT', ::Interrupt)
104
+ trap('SIGTERM', SystemExit)
105
+ trap('SIGINT', Interrupt)
108
106
  end
109
107
 
110
108
  def terminate_threads
@@ -114,7 +112,25 @@ module Polyphony
114
112
  threads.each(&:kill)
115
113
  threads.each(&:join)
116
114
  end
115
+
116
+ attr_accessor :original_pid
117
+
118
+ def install_at_exit_handler
119
+ @original_pid = ::Process.pid
120
+
121
+ # This at_exit handler is needed only when the original process exits. Due to
122
+ # the behaviour of fibers on fork (and especially on exit from forked
123
+ # processes,) we use a separate mechanism to terminate fibers in forked
124
+ # processes (see Polyphony.fork).
125
+ at_exit do
126
+ next unless @original_pid == ::Process.pid
127
+
128
+ Polyphony.terminate_threads
129
+ Fiber.current.shutdown_all_children
130
+ end
131
+ end
117
132
  end
118
133
  end
119
134
 
120
135
  Polyphony.install_terminating_signal_handlers
136
+ Polyphony.install_at_exit_handler
@@ -6,7 +6,7 @@ require 'redis'
6
6
  require 'hiredis/reader'
7
7
 
8
8
  # Polyphony-based Redis driver
9
- class Driver
9
+ class Polyphony::RedisDriver
10
10
  def self.connect(config)
11
11
  raise 'unix sockets not supported' if config[:scheme] == 'unix'
12
12
 
@@ -43,6 +43,7 @@ class Driver
43
43
  end
44
44
 
45
45
  def format_command(args)
46
+ args = args.flatten
46
47
  (+"*#{args.size}\r\n").tap do |s|
47
48
  args.each do |a|
48
49
  a = a.to_s
@@ -63,4 +64,4 @@ class Driver
63
64
  end
64
65
  end
65
66
 
66
- Redis::Connection.drivers << Driver
67
+ Redis::Connection.drivers << Polyphony::RedisDriver
@@ -15,11 +15,13 @@ module Polyphony
15
15
  end
16
16
  end
17
17
 
18
- def cancel_after(interval, &block)
18
+ def cancel_after(interval, with_exception: Polyphony::Cancel, &block)
19
19
  fiber = ::Fiber.current
20
20
  canceller = spin do
21
21
  sleep interval
22
- fiber.schedule Polyphony::Cancel.new
22
+ exception = with_exception.is_a?(Class) ?
23
+ with_exception.new : RuntimeError.new(with_exception)
24
+ fiber.schedule exception
23
25
  end
24
26
  block ? cancel_after_wrap_block(canceller, &block) : canceller
25
27
  end
@@ -101,7 +103,7 @@ module Polyphony
101
103
 
102
104
  def sleep_forever
103
105
  Thread.current.agent.ref
104
- suspend
106
+ loop { sleep 60 }
105
107
  ensure
106
108
  Thread.current.agent.unref
107
109
  end
@@ -13,6 +13,7 @@ module Polyphony
13
13
 
14
14
  @stock = []
15
15
  @queue = []
16
+ @acquired_resources = {}
16
17
 
17
18
  @limit = opts[:limit] || 4
18
19
  @size = 0
@@ -23,16 +24,25 @@ module Polyphony
23
24
  end
24
25
 
25
26
  def acquire
26
- Thread.current.agent.ref
27
- resource = wait_for_resource
28
- return unless resource
29
-
30
- yield resource
31
- ensure
32
- Thread.current.agent.unref
33
- release(resource) if resource
27
+ fiber = Fiber.current
28
+ if @acquired_resources[fiber]
29
+ yield @acquired_resources[fiber]
30
+ else
31
+ begin
32
+ Thread.current.agent.ref
33
+ resource = wait_for_resource
34
+ return unless resource
35
+
36
+ @acquired_resources[fiber] = resource
37
+ yield resource
38
+ ensure
39
+ @acquired_resources[fiber] = nil
40
+ Thread.current.agent.unref
41
+ release(resource) if resource
42
+ end
43
+ end
34
44
  end
35
-
45
+
36
46
  def wait_for_resource
37
47
  fiber = Fiber.current
38
48
  @queue << fiber
@@ -57,6 +57,12 @@ module ::Process
57
57
  fiber.define_singleton_method(:pid) { pid }
58
58
  fiber
59
59
  end
60
+
61
+ alias_method :orig_daemon, :daemon
62
+ def daemon(*args)
63
+ orig_daemon(*args)
64
+ Polyphony.original_pid = Process.pid
65
+ end
60
66
  end
61
67
  end
62
68
 
@@ -120,6 +126,31 @@ module ::Kernel
120
126
  break
121
127
  end
122
128
  end
129
+
130
+ alias_method :orig_trap, :trap
131
+ def trap(sig, command = nil, &block)
132
+ return orig_trap(sig, command) if command.is_a? String
133
+
134
+ block = command if command.respond_to?(:call) && !block
135
+ exception = command.is_a?(Class) && command.new
136
+
137
+ # The signal trap can be invoked at any time, including while the system
138
+ # agent is blocking while polling for events. In order to deal with this
139
+ # correctly, we spin a fiber that will run the signal handler code, then
140
+ # call break_out_of_ev_loop, which will put the fiber at the front of the
141
+ # run queue, then wake up the system agent.
142
+ #
143
+ # If the command argument is an exception class however, it will be raised
144
+ # directly in the context of the main fiber.
145
+ orig_trap(sig) do
146
+ if exception
147
+ Thread.current.break_out_of_ev_loop(Thread.main.main_fiber, exception)
148
+ else
149
+ fiber = spin { snooze; block.call }
150
+ Thread.current.break_out_of_ev_loop(fiber, nil)
151
+ end
152
+ end
153
+ end
123
154
  end
124
155
 
125
156
  # Override Timeout to use cancel scope
@@ -370,15 +370,3 @@ class ::Fiber
370
370
  end
371
371
 
372
372
  Fiber.current.setup_main_fiber
373
-
374
- # This at_exit handler is needed only when the original process exits. Due to
375
- # the behaviour of fibers on fork (and especially on exit from forked
376
- # processes,) we use a separate mechanism to terminate fibers in forked
377
- # processes (see Polyphony.fork).
378
- orig_pid = Process.pid
379
- at_exit do
380
- next unless orig_pid == Process.pid
381
-
382
- Polyphony.terminate_threads
383
- Fiber.current.shutdown_all_children
384
- end
@@ -97,7 +97,7 @@ class ::IO
97
97
  # end
98
98
 
99
99
  alias_method :orig_read, :read
100
- def read(len = 1 << 30)
100
+ def read(len = nil)
101
101
  @read_buffer ||= +''
102
102
  result = Thread.current.agent.read(self, @read_buffer, len, true)
103
103
  return nil unless result
@@ -201,6 +201,10 @@ class ::IO
201
201
  buf ? readpartial(maxlen, buf) : readpartial(maxlen)
202
202
  end
203
203
 
204
+ def read_loop(&block)
205
+ Thread.current.agent.read_loop(self, &block)
206
+ end
207
+
204
208
  # alias_method :orig_read, :read
205
209
  # def read(length = nil, outbuf = nil)
206
210
  # if length
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'openssl'
4
-
5
4
  require_relative './socket'
6
5
 
7
6
  # Open ssl socket helper methods (to make it compatible with Socket API)
@@ -18,12 +17,36 @@ class ::OpenSSL::SSL::SSLSocket
18
17
  io.reuse_addr
19
18
  end
20
19
 
21
- def sysread(maxlen, buf)
20
+ alias_method :orig_accept, :accept
21
+ def accept
22
+ loop do
23
+ result = accept_nonblock(exception: false)
24
+ case result
25
+ when :wait_readable then Thread.current.agent.wait_io(io, false)
26
+ when :wait_writable then Thread.current.agent.wait_io(io, true)
27
+ else
28
+ return result
29
+ end
30
+ end
31
+ end
32
+
33
+ alias_method :orig_sysread, :sysread
34
+ def sysread(maxlen, buf = +'')
22
35
  loop do
23
36
  case (result = read_nonblock(maxlen, buf, exception: false))
24
37
  when :wait_readable then Thread.current.agent.wait_io(io, false)
38
+ else return result
39
+ end
40
+ end
41
+ end
42
+
43
+ alias_method :orig_syswrite, :syswrite
44
+ def syswrite(buf)
45
+ loop do
46
+ case (result = write_nonblock(buf, exception: false))
25
47
  when :wait_writable then Thread.current.agent.wait_io(io, true)
26
- else result
48
+ else
49
+ return result
27
50
  end
28
51
  end
29
52
  end
@@ -37,13 +60,14 @@ class ::OpenSSL::SSL::SSLSocket
37
60
  # @sync = osync
38
61
  end
39
62
 
40
- def syswrite(buf)
41
- loop do
42
- case (result = write_nonblock(buf, exception: false))
43
- when :wait_readable then Thread.current.agent.wait_io(io, false)
44
- when :wait_writable then Thread.current.agent.wait_io(io, true)
45
- else result
46
- end
63
+ def readpartial(maxlen, buf = +'')
64
+ result = sysread(maxlen, buf)
65
+ result || (raise EOFError)
66
+ end
67
+
68
+ def read_loop
69
+ while (data = sysread(8192))
70
+ yield data
47
71
  end
48
72
  end
49
73
  end
@@ -128,11 +128,11 @@ class ::TCPServer
128
128
 
129
129
  alias_method :orig_accept, :accept
130
130
  def accept
131
- @io ? @io.accept : orig_accept
131
+ @io.accept
132
132
  end
133
133
 
134
134
  alias_method :orig_close, :close
135
135
  def close
136
- @io ? @io.close : orig_close
136
+ @io.close
137
137
  end
138
138
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
- VERSION = '0.42'
4
+ VERSION = '0.43.4'
5
5
  end
@@ -21,10 +21,13 @@ class AgentTest < MiniTest::Test
21
21
  spin {
22
22
  @agent.sleep 0.01
23
23
  count += 1
24
- }
25
- suspend
26
- assert Time.now - t0 >= 0.01
27
- assert_equal 1, count
24
+ @agent.sleep 0.01
25
+ count += 1
26
+ @agent.sleep 0.01
27
+ count += 1
28
+ }.await
29
+ assert Time.now - t0 >= 0.03
30
+ assert_equal 3, count
28
31
  end
29
32
 
30
33
  def test_write_read_partial
@@ -88,10 +91,13 @@ class AgentTest < MiniTest::Test
88
91
  buf << :done
89
92
  end
90
93
 
94
+ # writing always causes snoozing
91
95
  o << 'foo'
92
96
  o << 'bar'
93
97
  o.close
94
- snooze
98
+
99
+ # read_loop will snooze after every read
100
+ 4.times { snooze }
95
101
 
96
102
  assert_equal [:ready, 'foo', 'bar', :done], buf
97
103
  end
@@ -105,12 +111,12 @@ class AgentTest < MiniTest::Test
105
111
  end
106
112
 
107
113
  c1 = TCPSocket.new('127.0.0.1', 1234)
108
- snooze
114
+ 10.times { snooze }
109
115
 
110
116
  assert_equal 1, clients.size
111
117
 
112
118
  c2 = TCPSocket.new('127.0.0.1', 1234)
113
- snooze
119
+ 10.times { snooze }
114
120
 
115
121
  assert_equal 2, clients.size
116
122