polyphony 0.44.0 → 0.45.0

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