polyphony 0.34 → 0.41

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 (92) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +11 -2
  3. data/.gitignore +2 -2
  4. data/.rubocop.yml +30 -0
  5. data/CHANGELOG.md +34 -0
  6. data/Gemfile +0 -11
  7. data/Gemfile.lock +11 -10
  8. data/README.md +2 -1
  9. data/Rakefile +6 -2
  10. data/TODO.md +18 -95
  11. data/docs/_includes/head.html +40 -0
  12. data/docs/_includes/nav.html +5 -5
  13. data/docs/api-reference.md +1 -1
  14. data/docs/api-reference/fiber.md +18 -0
  15. data/docs/api-reference/gyro-async.md +57 -0
  16. data/docs/api-reference/gyro-child.md +29 -0
  17. data/docs/api-reference/gyro-queue.md +44 -0
  18. data/docs/api-reference/gyro-timer.md +51 -0
  19. data/docs/api-reference/gyro.md +25 -0
  20. data/docs/index.md +10 -7
  21. data/docs/main-concepts/design-principles.md +67 -9
  22. data/docs/main-concepts/extending.md +1 -1
  23. data/docs/main-concepts/fiber-scheduling.md +55 -72
  24. data/examples/core/xx-agent.rb +102 -0
  25. data/examples/core/xx-fork-cleanup.rb +22 -0
  26. data/examples/core/xx-sleeping.rb +14 -6
  27. data/examples/core/xx-timer-gc.rb +17 -0
  28. data/examples/io/tunnel.rb +48 -0
  29. data/examples/io/xx-irb.rb +1 -1
  30. data/examples/performance/thread-vs-fiber/polyphony_mt_server.rb +7 -6
  31. data/examples/performance/thread-vs-fiber/polyphony_server.rb +14 -25
  32. data/ext/{gyro → polyphony}/extconf.rb +2 -2
  33. data/ext/polyphony/fiber.c +112 -0
  34. data/ext/{gyro → polyphony}/libev.c +0 -0
  35. data/ext/{gyro → polyphony}/libev.h +0 -0
  36. data/ext/polyphony/libev_agent.c +503 -0
  37. data/ext/polyphony/libev_queue.c +214 -0
  38. data/ext/polyphony/polyphony.c +89 -0
  39. data/ext/{gyro/gyro.h → polyphony/polyphony.h} +49 -59
  40. data/ext/polyphony/polyphony_ext.c +23 -0
  41. data/ext/{gyro → polyphony}/socket.c +21 -19
  42. data/ext/{gyro → polyphony}/thread.c +55 -119
  43. data/ext/{gyro → polyphony}/tracing.c +1 -1
  44. data/lib/polyphony.rb +37 -44
  45. data/lib/polyphony/adapters/fs.rb +1 -4
  46. data/lib/polyphony/adapters/irb.rb +2 -2
  47. data/lib/polyphony/adapters/postgres.rb +6 -5
  48. data/lib/polyphony/adapters/process.rb +27 -23
  49. data/lib/polyphony/adapters/trace.rb +110 -105
  50. data/lib/polyphony/core/channel.rb +35 -35
  51. data/lib/polyphony/core/exceptions.rb +29 -29
  52. data/lib/polyphony/core/global_api.rb +94 -91
  53. data/lib/polyphony/core/resource_pool.rb +83 -83
  54. data/lib/polyphony/core/sync.rb +16 -16
  55. data/lib/polyphony/core/thread_pool.rb +49 -37
  56. data/lib/polyphony/core/throttler.rb +30 -23
  57. data/lib/polyphony/event.rb +27 -0
  58. data/lib/polyphony/extensions/core.rb +23 -14
  59. data/lib/polyphony/extensions/fiber.rb +269 -267
  60. data/lib/polyphony/extensions/io.rb +56 -26
  61. data/lib/polyphony/extensions/openssl.rb +5 -9
  62. data/lib/polyphony/extensions/socket.rb +29 -10
  63. data/lib/polyphony/extensions/thread.rb +19 -12
  64. data/lib/polyphony/net.rb +64 -60
  65. data/lib/polyphony/version.rb +1 -1
  66. data/polyphony.gemspec +3 -6
  67. data/test/helper.rb +14 -1
  68. data/test/stress.rb +17 -12
  69. data/test/test_agent.rb +77 -0
  70. data/test/{test_async.rb → test_event.rb} +17 -9
  71. data/test/test_ext.rb +25 -4
  72. data/test/test_fiber.rb +23 -14
  73. data/test/test_global_api.rb +5 -5
  74. data/test/test_io.rb +46 -24
  75. data/test/test_queue.rb +74 -0
  76. data/test/test_signal.rb +3 -40
  77. data/test/test_socket.rb +33 -0
  78. data/test/test_thread.rb +38 -16
  79. data/test/test_thread_pool.rb +3 -3
  80. data/test/test_throttler.rb +0 -1
  81. data/test/test_trace.rb +6 -5
  82. metadata +34 -39
  83. data/ext/gyro/async.c +0 -158
  84. data/ext/gyro/child.c +0 -117
  85. data/ext/gyro/gyro.c +0 -203
  86. data/ext/gyro/gyro_ext.c +0 -31
  87. data/ext/gyro/io.c +0 -447
  88. data/ext/gyro/queue.c +0 -142
  89. data/ext/gyro/selector.c +0 -183
  90. data/ext/gyro/signal.c +0 -108
  91. data/ext/gyro/timer.c +0 -154
  92. data/test/test_timer.rb +0 -56
@@ -1,11 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- export :stat,
4
- :read
5
-
6
3
  require 'fileutils'
7
4
 
8
- ThreadPool = import('./core/thread_pool')
5
+ require_relative './core/thread_pool'
9
6
 
10
7
  ::File.singleton_class.instance_eval do
11
8
  alias_method :orig_stat, :stat
@@ -13,10 +13,10 @@ if Object.constants.include?(:Reline)
13
13
  fiber = Fiber.current
14
14
  timer = spin do
15
15
  sleep timeout
16
- fiber.cancel!
16
+ fiber.cancel
17
17
  end
18
18
  read_ios.each do |io|
19
- io.read_watcher.await
19
+ Thread.current.agent.wait_io(io, false)
20
20
  return [io]
21
21
  end
22
22
  rescue Polyphony::Cancel
@@ -10,12 +10,13 @@ module ::PG
10
10
  end
11
11
 
12
12
  def self.connect_async(conn)
13
+ socket_io = conn.socket_io
13
14
  loop do
14
15
  res = conn.connect_poll
15
16
  case res
16
17
  when PGRES_POLLING_FAILED then raise Error, conn.error_message
17
- when PGRES_POLLING_READING then conn.socket_io.read_watcher.await
18
- when PGRES_POLLING_WRITING then conn.socket_io.write_watcher.await
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)
19
20
  when PGRES_POLLING_OK then return conn.setnonblocking(true)
20
21
  end
21
22
  end
@@ -41,7 +42,7 @@ class ::PG::Connection
41
42
 
42
43
  def get_result(&block)
43
44
  while is_busy
44
- socket_io.read_watcher.await
45
+ Thread.current.agent.wait_io(socket_io, false)
45
46
  consume_input
46
47
  end
47
48
  orig_get_result(&block)
@@ -58,7 +59,7 @@ class ::PG::Connection
58
59
 
59
60
  def block(_timeout = 0)
60
61
  while is_busy
61
- socket_io.read_watcher.await
62
+ Thread.current.agent.wait_io(socket_io, false)
62
63
  consume_input
63
64
  end
64
65
  end
@@ -96,7 +97,7 @@ class ::PG::Connection
96
97
  return move_on_after(timeout) { wait_for_notify(&block) } if timeout
97
98
 
98
99
  loop do
99
- socket_io.read_watcher.await
100
+ Thread.current.agent.wait_io(socket_io, false)
100
101
  consume_input
101
102
  notice = notifies
102
103
  next unless notice
@@ -1,29 +1,33 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- export :watch
3
+ module Polyphony
4
+ # Process patches
5
+ module Process
6
+ class << self
7
+ def watch(cmd = nil, &block)
8
+ terminated = nil
9
+ pid = cmd ? Kernel.spawn(cmd) : Polyphony.fork(&block)
10
+ Thread.current.agent.waitpid(pid)
11
+ terminated = true
12
+ ensure
13
+ kill_process(pid) unless terminated || pid.nil?
14
+ end
4
15
 
5
- def watch(cmd = nil, &block)
6
- terminated = nil
7
- pid = cmd ? Kernel.spawn(cmd) : Polyphony.fork(&block)
8
- watcher = Gyro::Child.new(pid)
9
- watcher.await
10
- terminated = true
11
- ensure
12
- kill_process(pid) unless terminated || pid.nil?
13
- end
16
+ def kill_process(pid)
17
+ cancel_after(5) do
18
+ kill_and_await('TERM', pid)
19
+ end
20
+ rescue Polyphony::Cancel
21
+ kill_and_await(-9, pid)
22
+ end
14
23
 
15
- def kill_process(pid)
16
- cancel_after(5) do
17
- kill_and_await('TERM', pid)
24
+ def kill_and_await(sig, pid)
25
+ ::Process.kill(sig, pid)
26
+ Thread.current.agent.waitpid(pid)
27
+ rescue SystemCallError
28
+ # ignore
29
+ puts 'SystemCallError in kill_and_await'
30
+ end
31
+ end
18
32
  end
19
- rescue Polyphony::Cancel
20
- kill_and_await(-9, pid)
21
- end
22
-
23
- def kill_and_await(sig, pid)
24
- Process.kill(sig, pid)
25
- Gyro::Child.new(pid).await
26
- rescue SystemCallError
27
- # ignore
28
- puts 'SystemCallError in kill_and_await'
29
33
  end
@@ -1,132 +1,137 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- export :new, :analyze, :STOCK_EVENTS
4
-
5
- require 'polyphony'
3
+ require_relative '../../polyphony'
6
4
 
7
5
  STOCK_EVENTS = %i[line call return c_call c_return b_call b_return].freeze
8
6
 
9
- def new(*events)
10
- start_stamp = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
11
- events = STOCK_EVENTS if events.empty?
12
- ::TracePoint.new(*events) { |tp| yield trace_record(tp, start_stamp) }
13
- end
14
-
15
- def trace_record(trp, start_stamp)
16
- stamp = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start_stamp
17
-
18
- { stamp: stamp, event: trp.event, location: "#{trp.path}:#{trp.lineno}",
19
- self: trp.self, binding: trp.binding, fiber: tp_fiber(trp),
20
- lineno: trp.lineno, method_id: trp.method_id,
21
- path: trp.path, parameters: tp_params(trp),
22
- return_value: tp_return_value(trp), schedule_value: tp_schedule_value(trp),
23
- exception: tp_raised_exception(trp) }
24
- end
25
-
26
- def tp_fiber(trp)
27
- trp.is_a?(FiberTracePoint) ? trp.fiber : Fiber.current
28
- end
29
-
30
- PARAMS_EVENTS = %i[call c_call b_call].freeze
31
-
32
- def tp_params(trp)
33
- PARAMS_EVENTS.include?(trp.event) ? trp.parameters : nil
34
- end
35
-
36
- RETURN_VALUE_EVENTS = %i[return c_return b_return].freeze
37
-
38
- def tp_return_value(trp)
39
- RETURN_VALUE_EVENTS.include?(trp.event) ? trp.return_value : nil
40
- end
7
+ module Polyphony
8
+ # Tracing functionality for Polyphony
9
+ module Trace
10
+ class << self
11
+ def new(*events)
12
+ start_stamp = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
13
+ events = STOCK_EVENTS if events.empty?
14
+ ::TracePoint.new(*events) { |tp| yield trace_record(tp, start_stamp) }
15
+ end
41
16
 
42
- SCHEDULE_VALUE_EVENTS = %i[fiber_schedule fiber_run].freeze
17
+ def trace_record(trp, start_stamp)
18
+ stamp = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start_stamp
43
19
 
44
- def tp_schedule_value(trp)
45
- SCHEDULE_VALUE_EVENTS.include?(trp.event) ? trp.value : nil
46
- end
20
+ { stamp: stamp, event: trp.event, location: "#{trp.path}:#{trp.lineno}",
21
+ self: trp.self, binding: trp.binding, fiber: tp_fiber(trp),
22
+ lineno: trp.lineno, method_id: trp.method_id,
23
+ path: trp.path, parameters: tp_params(trp),
24
+ return_value: tp_return_value(trp), schedule_value: tp_schedule_value(trp),
25
+ exception: tp_raised_exception(trp) }
26
+ end
47
27
 
48
- def tp_raised_exception(trp)
49
- trp.event == :raise && trp.raised_exception
50
- end
28
+ def tp_fiber(trp)
29
+ trp.is_a?(FiberTracePoint) ? trp.fiber : Fiber.current
30
+ end
51
31
 
52
- def analyze(records)
53
- by_fiber = Hash.new { |h, f| h[f] = [] }
54
- records.each_with_object(by_fiber) { |r, h| h[r[:fiber]] << r }
55
- { by_fiber: by_fiber }
56
- end
32
+ PARAMS_EVENTS = %i[call c_call b_call].freeze
57
33
 
58
- # Implements fake TracePoint instances for fiber-related events
59
- class FiberTracePoint
60
- attr_reader :event, :fiber, :value
34
+ def tp_params(trp)
35
+ PARAMS_EVENTS.include?(trp.event) ? trp.parameters : nil
36
+ end
61
37
 
62
- def initialize(tpoint)
63
- @tp = tpoint
64
- @event = tpoint.return_value[0]
65
- @fiber = tpoint.return_value[1]
66
- @value = tpoint.return_value[2]
67
- end
38
+ RETURN_VALUE_EVENTS = %i[return c_return b_return].freeze
68
39
 
69
- def lineno
70
- @tp.lineno
71
- end
40
+ def tp_return_value(trp)
41
+ RETURN_VALUE_EVENTS.include?(trp.event) ? trp.return_value : nil
42
+ end
72
43
 
73
- def method_id
74
- @tp.method_id
75
- end
44
+ SCHEDULE_VALUE_EVENTS = %i[fiber_schedule fiber_run].freeze
76
45
 
77
- def path
78
- @tp.path
79
- end
46
+ def tp_schedule_value(trp)
47
+ SCHEDULE_VALUE_EVENTS.include?(trp.event) ? trp.value : nil
48
+ end
80
49
 
81
- def self
82
- @tp.self
83
- end
50
+ def tp_raised_exception(trp)
51
+ trp.event == :raise && trp.raised_exception
52
+ end
84
53
 
85
- def binding
86
- @tp.binding
87
- end
88
- end
54
+ def analyze(records)
55
+ by_fiber = Hash.new { |h, f| h[f] = [] }
56
+ records.each_with_object(by_fiber) { |r, h| h[r[:fiber]] << r }
57
+ { by_fiber: by_fiber }
58
+ end
89
59
 
90
- class << ::TracePoint
91
- POLYPHONY_FILE_REGEXP = /^#{::Exception::POLYPHONY_DIR}/.freeze
60
+ # Implements fake TracePoint instances for fiber-related events
61
+ class FiberTracePoint
62
+ attr_reader :event, :fiber, :value
92
63
 
93
- alias_method :orig_new, :new
94
- def new(*args, &block)
95
- events_mask, fiber_events_mask = event_masks(args)
64
+ def initialize(tpoint)
65
+ @tp = tpoint
66
+ @event = tpoint.return_value[0]
67
+ @fiber = tpoint.return_value[1]
68
+ @value = tpoint.return_value[2]
69
+ end
96
70
 
97
- orig_new(*events_mask) do |tp|
98
- handle_tp_event(tp, fiber_events_mask, &block)
99
- end
100
- end
71
+ def lineno
72
+ @tp.lineno
73
+ end
101
74
 
102
- def handle_tp_event(tpoint, fiber_events_mask)
103
- # next unless !$watched_fiber || Fiber.current == $watched_fiber
75
+ def method_id
76
+ @tp.method_id
77
+ end
104
78
 
105
- if tpoint.method_id == :__fiber_trace__
106
- return if tpoint.event != :c_return
107
- return unless fiber_events_mask.include?(tpoint.return_value[0])
79
+ def path
80
+ @tp.path
81
+ end
108
82
 
109
- tpoint = FiberTracePoint.new(tpoint)
110
- elsif tpoint.path =~ POLYPHONY_FILE_REGEXP
111
- return
112
- end
83
+ def self
84
+ @tp.self
85
+ end
113
86
 
114
- yield tpoint
115
- end
87
+ def binding
88
+ @tp.binding
89
+ end
90
+ end
116
91
 
117
- ALL_FIBER_EVENTS = %i[
118
- fiber_create fiber_terminate fiber_schedule fiber_switchpoint fiber_run
119
- fiber_ev_loop_enter fiber_ev_loop_leave
120
- ].freeze
121
-
122
- def event_masks(events)
123
- events.each_with_object([[], []]) do |e, masks|
124
- case e
125
- when /fiber_/
126
- masks[1] += e == :fiber_all ? ALL_FIBER_EVENTS : [e]
127
- masks[0] << :c_return unless masks[0].include?(:c_return)
128
- else
129
- masks[0] << e
92
+ class << ::TracePoint
93
+ POLYPHONY_FILE_REGEXP = /^#{::Exception::POLYPHONY_DIR}/.freeze
94
+
95
+ alias_method :orig_new, :new
96
+ def new(*args, &block)
97
+ events_mask, fiber_events_mask = event_masks(args)
98
+
99
+ orig_new(*events_mask) do |tp|
100
+ handle_tp_event(tp, fiber_events_mask, &block)
101
+ end
102
+ end
103
+
104
+ def handle_tp_event(tpoint, fiber_events_mask)
105
+ # next unless !$watched_fiber || Fiber.current == $watched_fiber
106
+
107
+ if tpoint.method_id == :__fiber_trace__
108
+ return if tpoint.event != :c_return
109
+ return unless fiber_events_mask.include?(tpoint.return_value[0])
110
+
111
+ tpoint = FiberTracePoint.new(tpoint)
112
+ elsif tpoint.path =~ POLYPHONY_FILE_REGEXP
113
+ return
114
+ end
115
+
116
+ yield tpoint
117
+ end
118
+
119
+ ALL_FIBER_EVENTS = %i[
120
+ fiber_create fiber_terminate fiber_schedule fiber_switchpoint fiber_run
121
+ fiber_ev_loop_enter fiber_ev_loop_leave
122
+ ].freeze
123
+
124
+ def event_masks(events)
125
+ events.each_with_object([[], []]) do |e, masks|
126
+ case e
127
+ when /fiber_/
128
+ masks[1] += e == :fiber_all ? ALL_FIBER_EVENTS : [e]
129
+ masks[0] << :c_return unless masks[0].include?(:c_return)
130
+ else
131
+ masks[0] << e
132
+ end
133
+ end
134
+ end
130
135
  end
131
136
  end
132
137
  end
@@ -1,46 +1,46 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- export_default :Channel
3
+ require_relative './exceptions'
4
4
 
5
- Exceptions = import('./exceptions')
6
-
7
- # Implements a unidirectional communication channel along the lines of Go
8
- # (buffered) channels.
9
- class Channel
10
- def initialize
11
- @payload_queue = []
12
- @waiting_queue = []
13
- end
5
+ module Polyphony
6
+ # Implements a unidirectional communication channel along the lines of Go
7
+ # (buffered) channels.
8
+ class Channel
9
+ def initialize
10
+ @payload_queue = []
11
+ @waiting_queue = []
12
+ end
14
13
 
15
- def close
16
- stop = Exceptions::MoveOn.new
17
- @waiting_queue.slice(0..-1).each { |f| f.schedule(stop) }
18
- end
14
+ def close
15
+ stop = Polyphony::MoveOn.new
16
+ @waiting_queue.slice(0..-1).each { |f| f.schedule(stop) }
17
+ end
19
18
 
20
- def <<(value)
21
- if @waiting_queue.empty?
22
- @payload_queue << value
23
- else
24
- @waiting_queue.shift&.schedule(value)
19
+ def <<(value)
20
+ if @waiting_queue.empty?
21
+ @payload_queue << value
22
+ else
23
+ @waiting_queue.shift&.schedule(value)
24
+ end
25
+ snooze
25
26
  end
26
- snooze
27
- end
28
27
 
29
- def receive
30
- Gyro.ref
31
- if @payload_queue.empty?
32
- @waiting_queue << Fiber.current
33
- suspend
34
- else
35
- receive_from_queue
28
+ def receive
29
+ Polyphony.ref
30
+ if @payload_queue.empty?
31
+ @waiting_queue << Fiber.current
32
+ suspend
33
+ else
34
+ receive_from_queue
35
+ end
36
+ ensure
37
+ Polyphony.unref
36
38
  end
37
- ensure
38
- Gyro.unref
39
- end
40
39
 
41
- def receive_from_queue
42
- payload = @payload_queue.shift
43
- snooze
44
- payload
40
+ def receive_from_queue
41
+ payload = @payload_queue.shift
42
+ snooze
43
+ payload
44
+ end
45
45
  end
46
46
  end