polyphony 0.44.0 → 0.45.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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +7 -1
  3. data/CHANGELOG.md +7 -0
  4. data/Gemfile.lock +9 -11
  5. data/Rakefile +1 -1
  6. data/TODO.md +12 -7
  7. data/docs/_posts/2020-07-26-polyphony-0.44.md +77 -0
  8. data/docs/api-reference/thread.md +1 -1
  9. data/docs/getting-started/overview.md +14 -14
  10. data/docs/getting-started/tutorial.md +1 -1
  11. data/examples/core/{xx-agent.rb → xx-backend.rb} +5 -5
  12. data/examples/io/xx-pry.rb +18 -0
  13. data/examples/io/xx-rack_server.rb +71 -0
  14. data/examples/performance/thread-vs-fiber/polyphony_server_read_loop.rb +1 -1
  15. data/ext/polyphony/backend.h +41 -0
  16. data/ext/polyphony/event.c +3 -3
  17. data/ext/polyphony/extconf.rb +1 -1
  18. data/ext/polyphony/{libev_agent.c → libev_backend.c} +175 -175
  19. data/ext/polyphony/polyphony.c +1 -1
  20. data/ext/polyphony/polyphony.h +4 -4
  21. data/ext/polyphony/polyphony_ext.c +2 -2
  22. data/ext/polyphony/queue.c +2 -2
  23. data/ext/polyphony/thread.c +21 -21
  24. data/lib/polyphony.rb +13 -12
  25. data/lib/polyphony/adapters/irb.rb +2 -17
  26. data/lib/polyphony/adapters/mysql2.rb +1 -1
  27. data/lib/polyphony/adapters/postgres.rb +5 -5
  28. data/lib/polyphony/adapters/process.rb +2 -2
  29. data/lib/polyphony/adapters/readline.rb +17 -0
  30. data/lib/polyphony/adapters/sequel.rb +1 -1
  31. data/lib/polyphony/core/global_api.rb +11 -6
  32. data/lib/polyphony/core/resource_pool.rb +2 -2
  33. data/lib/polyphony/core/sync.rb +38 -2
  34. data/lib/polyphony/core/throttler.rb +1 -1
  35. data/lib/polyphony/extensions/core.rb +31 -20
  36. data/lib/polyphony/extensions/fiber.rb +1 -1
  37. data/lib/polyphony/extensions/io.rb +7 -8
  38. data/lib/polyphony/extensions/openssl.rb +6 -6
  39. data/lib/polyphony/extensions/socket.rb +4 -14
  40. data/lib/polyphony/extensions/thread.rb +6 -5
  41. data/lib/polyphony/version.rb +1 -1
  42. data/polyphony.gemspec +4 -3
  43. data/test/helper.rb +1 -1
  44. data/test/{test_agent.rb → test_backend.rb} +22 -22
  45. data/test/test_fiber.rb +4 -4
  46. data/test/test_io.rb +1 -1
  47. data/test/test_kernel.rb +5 -0
  48. data/test/test_signal.rb +3 -3
  49. data/test/test_sync.rb +52 -0
  50. metadata +40 -30
  51. data/.gitbook.yaml +0 -4
  52. data/ext/polyphony/agent.h +0 -41
@@ -22,7 +22,7 @@ ID ID_R;
22
22
  ID ID_W;
23
23
  ID ID_RW;
24
24
 
25
- agent_interface_t agent_interface;
25
+ backend_interface_t backend_interface;
26
26
 
27
27
  VALUE Polyphony_snooze(VALUE self) {
28
28
  VALUE ret;
@@ -4,7 +4,7 @@
4
4
  #include "ruby.h"
5
5
  #include "ruby/io.h"
6
6
  #include "libev.h"
7
- #include "agent.h"
7
+ #include "backend.h"
8
8
 
9
9
  // debugging
10
10
  #define OBJ_ID(obj) (NUM2LONG(rb_funcall(obj, rb_intern("object_id"), 0)))
@@ -25,8 +25,8 @@
25
25
  }
26
26
 
27
27
 
28
- extern agent_interface_t agent_interface;
29
- #define __AGENT__ (agent_interface)
28
+ extern backend_interface_t backend_interface;
29
+ #define __BACKEND__ (backend_interface)
30
30
 
31
31
  extern VALUE mPolyphony;
32
32
  extern VALUE cQueue;
@@ -39,7 +39,7 @@ extern ID ID_each;
39
39
  extern ID ID_fiber_trace;
40
40
  extern ID ID_inspect;
41
41
  extern ID ID_invoke;
42
- extern ID ID_ivar_agent;
42
+ extern ID ID_ivar_backend;
43
43
  extern ID ID_ivar_running;
44
44
  extern ID ID_ivar_thread;
45
45
  extern ID ID_new;
@@ -2,7 +2,7 @@
2
2
 
3
3
  void Init_Fiber();
4
4
  void Init_Polyphony();
5
- void Init_LibevAgent();
5
+ void Init_LibevBackend();
6
6
  void Init_Queue();
7
7
  void Init_Event();
8
8
  void Init_Thread();
@@ -12,7 +12,7 @@ void Init_polyphony_ext() {
12
12
  ev_set_allocator(xrealloc);
13
13
 
14
14
  Init_Polyphony();
15
- Init_LibevAgent();
15
+ Init_LibevBackend();
16
16
  Init_Queue();
17
17
  Init_Event();
18
18
 
@@ -80,13 +80,13 @@ VALUE Queue_shift(VALUE self) {
80
80
 
81
81
  VALUE fiber = rb_fiber_current();
82
82
  VALUE thread = rb_thread_current();
83
- VALUE agent = rb_ivar_get(thread, ID_ivar_agent);
83
+ VALUE backend = rb_ivar_get(thread, ID_ivar_backend);
84
84
 
85
85
  while (1) {
86
86
  ring_buffer_push(&queue->shift_queue, fiber);
87
87
  if (queue->values.count > 0) Fiber_make_runnable(fiber, Qnil);
88
88
 
89
- VALUE switchpoint_result = __AGENT__.wait_event(agent, Qnil);
89
+ VALUE switchpoint_result = __BACKEND__.wait_event(backend, Qnil);
90
90
  ring_buffer_delete(&queue->shift_queue, fiber);
91
91
 
92
92
  if (RTEST(rb_obj_is_kind_of(switchpoint_result, rb_eException)))
@@ -1,7 +1,7 @@
1
1
  #include "polyphony.h"
2
2
 
3
3
  ID ID_deactivate_all_watchers_post_fork;
4
- ID ID_ivar_agent;
4
+ ID ID_ivar_backend;
5
5
  ID ID_ivar_join_wait_queue;
6
6
  ID ID_ivar_main_fiber;
7
7
  ID ID_ivar_result;
@@ -20,20 +20,20 @@ static VALUE Thread_setup_fiber_scheduling(VALUE self) {
20
20
  }
21
21
 
22
22
  int Thread_fiber_ref_count(VALUE self) {
23
- VALUE agent = rb_ivar_get(self, ID_ivar_agent);
24
- return NUM2INT(__AGENT__.ref_count(agent));
23
+ VALUE backend = rb_ivar_get(self, ID_ivar_backend);
24
+ return NUM2INT(__BACKEND__.ref_count(backend));
25
25
  }
26
26
 
27
27
  inline void Thread_fiber_reset_ref_count(VALUE self) {
28
- VALUE agent = rb_ivar_get(self, ID_ivar_agent);
29
- __AGENT__.reset_ref_count(agent);
28
+ VALUE backend = rb_ivar_get(self, ID_ivar_backend);
29
+ __BACKEND__.reset_ref_count(backend);
30
30
  }
31
31
 
32
32
  static VALUE SYM_scheduled_fibers;
33
33
  static VALUE SYM_pending_watchers;
34
34
 
35
35
  static VALUE Thread_fiber_scheduling_stats(VALUE self) {
36
- VALUE agent = rb_ivar_get(self,ID_ivar_agent);
36
+ VALUE backend = rb_ivar_get(self,ID_ivar_backend);
37
37
  VALUE stats = rb_hash_new();
38
38
  VALUE queue = rb_ivar_get(self, ID_run_queue);
39
39
  long pending_count;
@@ -41,7 +41,7 @@ static VALUE Thread_fiber_scheduling_stats(VALUE self) {
41
41
  long scheduled_count = RARRAY_LEN(queue);
42
42
  rb_hash_aset(stats, SYM_scheduled_fibers, INT2NUM(scheduled_count));
43
43
 
44
- pending_count = __AGENT__.pending_count(agent);
44
+ pending_count = __BACKEND__.pending_count(backend);
45
45
  rb_hash_aset(stats, SYM_pending_watchers, INT2NUM(pending_count));
46
46
 
47
47
  return stats;
@@ -77,8 +77,8 @@ VALUE Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
77
77
  // event selector. Otherwise it's gonna be stuck waiting for an event to
78
78
  // happen, not knowing that it there's already a fiber ready to run in its
79
79
  // run queue.
80
- VALUE agent = rb_ivar_get(self,ID_ivar_agent);
81
- __AGENT__.wakeup(agent);
80
+ VALUE backend = rb_ivar_get(self,ID_ivar_backend);
81
+ __BACKEND__.wakeup(backend);
82
82
  }
83
83
  }
84
84
  return self;
@@ -110,8 +110,8 @@ VALUE Thread_schedule_fiber_with_priority(VALUE self, VALUE fiber, VALUE value)
110
110
  // event loop. Otherwise it's gonna be stuck waiting for an event to
111
111
  // happen, not knowing that it there's already a fiber ready to run in its
112
112
  // run queue.
113
- VALUE agent = rb_ivar_get(self, ID_ivar_agent);
114
- __AGENT__.wakeup(agent);
113
+ VALUE backend = rb_ivar_get(self, ID_ivar_backend);
114
+ __BACKEND__.wakeup(backend);
115
115
  }
116
116
  return self;
117
117
  }
@@ -121,28 +121,28 @@ VALUE Thread_switch_fiber(VALUE self) {
121
121
  VALUE queue = rb_ivar_get(self, ID_run_queue);
122
122
  VALUE next_fiber;
123
123
  VALUE value;
124
- VALUE agent = rb_ivar_get(self, ID_ivar_agent);
124
+ VALUE backend = rb_ivar_get(self, ID_ivar_backend);
125
125
  int ref_count;
126
- int agent_was_polled = 0;
126
+ int backend_was_polled = 0;
127
127
 
128
128
  if (__tracing_enabled__ && (rb_ivar_get(current_fiber, ID_ivar_running) != Qfalse))
129
129
  TRACE(2, SYM_fiber_switchpoint, current_fiber);
130
130
 
131
- ref_count = __AGENT__.ref_count(agent);
131
+ ref_count = __BACKEND__.ref_count(backend);
132
132
  while (1) {
133
133
  next_fiber = Queue_shift_no_wait(queue);
134
134
  if (next_fiber != Qnil) {
135
- if (agent_was_polled == 0 && ref_count > 0) {
135
+ if (backend_was_polled == 0 && ref_count > 0) {
136
136
  // this mechanism prevents event starvation in case the run queue never
137
137
  // empties
138
- __AGENT__.poll(agent, Qtrue, current_fiber, queue);
138
+ __BACKEND__.poll(backend, Qtrue, current_fiber, queue);
139
139
  }
140
140
  break;
141
141
  }
142
142
  if (ref_count == 0) break;
143
143
 
144
- __AGENT__.poll(agent, Qnil, current_fiber, queue);
145
- agent_was_polled = 1;
144
+ __BACKEND__.poll(backend, Qnil, current_fiber, queue);
145
+ backend_was_polled = 1;
146
146
  }
147
147
 
148
148
  if (next_fiber == Qnil) return Qnil;
@@ -172,12 +172,12 @@ VALUE Thread_reset_fiber_scheduling(VALUE self) {
172
172
  }
173
173
 
174
174
  VALUE Thread_fiber_break_out_of_ev_loop(VALUE self, VALUE fiber, VALUE resume_obj) {
175
- VALUE agent = rb_ivar_get(self, ID_ivar_agent);
175
+ VALUE backend = rb_ivar_get(self, ID_ivar_backend);
176
176
  if (fiber != Qnil) {
177
177
  Thread_schedule_fiber_with_priority(self, fiber, resume_obj);
178
178
  }
179
179
 
180
- if (__AGENT__.wakeup(agent) == Qnil) {
180
+ if (__BACKEND__.wakeup(backend) == Qnil) {
181
181
  // we're not inside the ev_loop, so we just do a switchpoint
182
182
  Thread_switch_fiber(self);
183
183
  }
@@ -205,7 +205,7 @@ void Init_Thread() {
205
205
  rb_define_method(rb_cThread, "debug!", Thread_debug, 0);
206
206
 
207
207
  ID_deactivate_all_watchers_post_fork = rb_intern("deactivate_all_watchers_post_fork");
208
- ID_ivar_agent = rb_intern("@agent");
208
+ ID_ivar_backend = rb_intern("@backend");
209
209
  ID_ivar_join_wait_queue = rb_intern("@join_wait_queue");
210
210
  ID_ivar_main_fiber = rb_intern("@main_fiber");
211
211
  ID_ivar_result = rb_intern("@result");
@@ -3,28 +3,21 @@
3
3
  require 'fiber'
4
4
  require_relative './polyphony_ext'
5
5
 
6
- module Polyphony
7
- # replace core Queue class with our own
8
- verbose = $VERBOSE
9
- $VERBOSE = nil
10
- Object.const_set(:Queue, Polyphony::Queue)
11
- $VERBOSE = verbose
12
- end
13
-
14
6
  require_relative './polyphony/extensions/core'
15
7
  require_relative './polyphony/extensions/thread'
16
8
  require_relative './polyphony/extensions/fiber'
17
9
  require_relative './polyphony/extensions/io'
18
10
 
19
11
  Thread.current.setup_fiber_scheduling
20
- Thread.current.agent = Polyphony::Agent.new
12
+ Thread.current.backend = Polyphony::Backend.new
21
13
 
22
14
  require_relative './polyphony/core/global_api'
23
15
  require_relative './polyphony/core/resource_pool'
16
+ require_relative './polyphony/core/sync'
24
17
  require_relative './polyphony/net'
25
18
  require_relative './polyphony/adapters/process'
26
19
 
27
- # Main Polyphony API
20
+ # Polyphony API
28
21
  module Polyphony
29
22
  class << self
30
23
  def fork(&block)
@@ -60,7 +53,7 @@ module Polyphony
60
53
  def run_forked_block(&block)
61
54
  Thread.current.setup
62
55
  Fiber.current.setup_main_fiber
63
- Thread.current.agent.post_fork
56
+ Thread.current.backend.post_fork
64
57
 
65
58
  install_terminating_signal_handlers
66
59
 
@@ -109,12 +102,20 @@ module Polyphony
109
102
  # processes (see Polyphony.fork).
110
103
  at_exit do
111
104
  next unless @original_pid == ::Process.pid
112
-
105
+
113
106
  Polyphony.terminate_threads
114
107
  Fiber.current.shutdown_all_children
115
108
  end
116
109
  end
117
110
  end
111
+
112
+ # replace core Queue class with our own
113
+ verbose = $VERBOSE
114
+ $VERBOSE = nil
115
+ Object.const_set(:Queue, Polyphony::Queue)
116
+ Object.const_set(:Mutex, Polyphony::Mutex)
117
+ Object.const_set(:ConditionVariable, Polyphony::ConditionVariable)
118
+ $VERBOSE = verbose
118
119
  end
119
120
 
120
121
  Polyphony.install_terminating_signal_handlers
@@ -16,7 +16,7 @@ if Object.constants.include?(:Reline)
16
16
  fiber.cancel
17
17
  end
18
18
  read_ios.each do |io|
19
- Thread.current.agent.wait_io(io, false)
19
+ Thread.current.backend.wait_io(io, false)
20
20
  return [io]
21
21
  end
22
22
  rescue Polyphony::Cancel
@@ -26,22 +26,7 @@ if Object.constants.include?(:Reline)
26
26
  end
27
27
  end
28
28
  else
29
- # readline blocks the current thread, so we offload it to the blocking-ops
30
- # thread pool. That way, the reactor loop can keep running while waiting for
31
- # readline to return
32
- module ::Readline
33
- alias_method :orig_readline, :readline
34
-
35
- Workers = Polyphony::ThreadPool.new
36
-
37
- def readline(*args)
38
- p :readline
39
- # caller.each do |l|
40
- # STDOUT.orig_puts l
41
- # end
42
- Workers.process { orig_readline(*args) }
43
- end
44
- end
29
+ require_relative './readline'
45
30
 
46
31
  # RubyLex patches
47
32
  class ::RubyLex
@@ -13,7 +13,7 @@ Mysql2::Client.prepend(Module.new do
13
13
 
14
14
  def query(sql, **options)
15
15
  super
16
- Thread.current.agent.wait_io(@io, false)
16
+ Thread.current.backend.wait_io(@io, false)
17
17
  async_result
18
18
  end
19
19
  end)
@@ -15,8 +15,8 @@ module ::PG
15
15
  res = conn.connect_poll
16
16
  case res
17
17
  when PGRES_POLLING_FAILED then raise Error, conn.error_message
18
- when PGRES_POLLING_READING then Thread.current.agent.wait_io(socket_io, false)
19
- when PGRES_POLLING_WRITING then Thread.current.agent.wait_io(socket_io, true)
18
+ when PGRES_POLLING_READING then Thread.current.backend.wait_io(socket_io, false)
19
+ when PGRES_POLLING_WRITING then Thread.current.backend.wait_io(socket_io, true)
20
20
  when PGRES_POLLING_OK then return conn.setnonblocking(true)
21
21
  end
22
22
  end
@@ -42,7 +42,7 @@ class ::PG::Connection
42
42
 
43
43
  def get_result(&block)
44
44
  while is_busy
45
- Thread.current.agent.wait_io(socket_io, false)
45
+ Thread.current.backend.wait_io(socket_io, false)
46
46
  consume_input
47
47
  end
48
48
  orig_get_result(&block)
@@ -59,7 +59,7 @@ class ::PG::Connection
59
59
 
60
60
  def block(_timeout = 0)
61
61
  while is_busy
62
- Thread.current.agent.wait_io(socket_io, false)
62
+ Thread.current.backend.wait_io(socket_io, false)
63
63
  consume_input
64
64
  end
65
65
  end
@@ -97,7 +97,7 @@ class ::PG::Connection
97
97
  return move_on_after(timeout) { wait_for_notify(&block) } if timeout
98
98
 
99
99
  loop do
100
- Thread.current.agent.wait_io(socket_io, false)
100
+ Thread.current.backend.wait_io(socket_io, false)
101
101
  consume_input
102
102
  notice = notifies
103
103
  next unless notice
@@ -7,7 +7,7 @@ module Polyphony
7
7
  def watch(cmd = nil, &block)
8
8
  terminated = nil
9
9
  pid = cmd ? Kernel.spawn(cmd) : Polyphony.fork(&block)
10
- Thread.current.agent.waitpid(pid)
10
+ Thread.current.backend.waitpid(pid)
11
11
  terminated = true
12
12
  ensure
13
13
  kill_process(pid) unless terminated || pid.nil?
@@ -23,7 +23,7 @@ module Polyphony
23
23
 
24
24
  def kill_and_await(sig, pid)
25
25
  ::Process.kill(sig, pid)
26
- Thread.current.agent.waitpid(pid)
26
+ Thread.current.backend.waitpid(pid)
27
27
  rescue SystemCallError
28
28
  # ignore
29
29
  puts 'SystemCallError in kill_and_await'
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'polyphony'
4
+ require 'readline'
5
+
6
+ # readline blocks the current thread, so we offload it to the blocking-ops
7
+ # thread pool. That way, the reactor loop can keep running while waiting for
8
+ # readline to return
9
+ module ::Readline
10
+ alias_method :orig_readline, :readline
11
+
12
+ Worker = Polyphony::ThreadPool.new(1)
13
+
14
+ def readline(*args)
15
+ Worker.process { orig_readline(*args) }
16
+ end
17
+ end
@@ -39,7 +39,7 @@ module Polyphony
39
39
  # Override Sequel::Database to use FiberConnectionPool by default.
40
40
  Sequel::Database.prepend(Module.new do
41
41
  def connection_pool_default_options
42
- {pool_class: FiberConnectionPool}
42
+ { pool_class: FiberConnectionPool }
43
43
  end
44
44
  end)
45
45
  end
@@ -19,13 +19,18 @@ module Polyphony
19
19
  fiber = ::Fiber.current
20
20
  canceller = spin do
21
21
  sleep interval
22
- exception = with_exception.is_a?(Class) ?
23
- with_exception.new : RuntimeError.new(with_exception)
22
+ exception = cancel_exception(with_exception)
24
23
  fiber.schedule exception
25
24
  end
26
25
  block ? cancel_after_wrap_block(canceller, &block) : canceller
27
26
  end
28
27
 
28
+ def cancel_exception(exception)
29
+ return exception.new if exception.is_a?(Class)
30
+
31
+ RuntimeError.new(exception)
32
+ end
33
+
29
34
  def cancel_after_wrap_block(canceller, &block)
30
35
  block.call
31
36
  ensure
@@ -50,7 +55,7 @@ module Polyphony
50
55
  next_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + interval
51
56
  loop do
52
57
  now = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
53
- Thread.current.agent.sleep(next_time - now)
58
+ Thread.current.backend.sleep(next_time - now)
54
59
  yield
55
60
  loop do
56
61
  next_time += interval
@@ -98,14 +103,14 @@ module Polyphony
98
103
  def sleep(duration = nil)
99
104
  return sleep_forever unless duration
100
105
 
101
- Thread.current.agent.sleep duration
106
+ Thread.current.backend.sleep duration
102
107
  end
103
108
 
104
109
  def sleep_forever
105
- Thread.current.agent.ref
110
+ Thread.current.backend.ref
106
111
  loop { sleep 60 }
107
112
  ensure
108
- Thread.current.agent.unref
113
+ Thread.current.backend.unref
109
114
  end
110
115
 
111
116
  def throttled_loop(rate, count: nil, &block)
@@ -28,7 +28,7 @@ module Polyphony
28
28
  end
29
29
 
30
30
  def acquire_from_stock(fiber)
31
- add_to_stock if (@stock.empty? || @stock.pending?) && @size < @limit
31
+ add_to_stock if (@stock.empty? || @stock.pending?) && @size < @limit
32
32
  resource = @stock.shift
33
33
  @acquired_resources[fiber] = resource
34
34
  yield resource
@@ -38,7 +38,7 @@ module Polyphony
38
38
  @stock.push resource
39
39
  end
40
40
  end
41
-
41
+
42
42
  def method_missing(sym, *args, &block)
43
43
  acquire { |r| r.send(sym, *args, &block) }
44
44
  end