polyphony 0.39 → 0.43.1

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 (117) 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 +23 -4
  6. data/Gemfile.lock +15 -12
  7. data/README.md +2 -1
  8. data/Rakefile +3 -3
  9. data/TODO.md +27 -97
  10. data/docs/_config.yml +56 -7
  11. data/docs/_sass/custom/custom.scss +6 -26
  12. data/docs/_sass/overrides.scss +0 -46
  13. data/docs/{user-guide → _user-guide}/all-about-timers.md +0 -0
  14. data/docs/_user-guide/index.md +9 -0
  15. data/docs/{user-guide → _user-guide}/web-server.md +0 -0
  16. data/docs/api-reference/fiber.md +2 -2
  17. data/docs/api-reference/index.md +9 -0
  18. data/docs/api-reference/polyphony-process.md +1 -1
  19. data/docs/api-reference/thread.md +1 -1
  20. data/docs/faq.md +21 -11
  21. data/docs/favicon.ico +0 -0
  22. data/docs/getting-started/index.md +10 -0
  23. data/docs/getting-started/installing.md +2 -6
  24. data/docs/getting-started/overview.md +486 -0
  25. data/docs/getting-started/tutorial.md +27 -19
  26. data/docs/index.md +6 -2
  27. data/docs/main-concepts/concurrency.md +0 -5
  28. data/docs/main-concepts/design-principles.md +69 -21
  29. data/docs/main-concepts/extending.md +1 -1
  30. data/docs/main-concepts/index.md +9 -0
  31. data/docs/polyphony-logo.png +0 -0
  32. data/examples/core/01-spinning-up-fibers.rb +1 -0
  33. data/examples/core/03-interrupting.rb +4 -1
  34. data/examples/core/04-handling-signals.rb +19 -0
  35. data/examples/core/xx-agent.rb +102 -0
  36. data/examples/core/xx-sleeping.rb +14 -6
  37. data/examples/io/tunnel.rb +48 -0
  38. data/examples/io/xx-irb.rb +1 -1
  39. data/examples/performance/thread-vs-fiber/polyphony_mt_server.rb +7 -6
  40. data/examples/performance/thread-vs-fiber/polyphony_server.rb +13 -36
  41. data/examples/performance/thread-vs-fiber/polyphony_server_read_loop.rb +58 -0
  42. data/examples/performance/xx-array.rb +11 -0
  43. data/examples/performance/xx-fiber-switch.rb +9 -0
  44. data/examples/performance/xx-snooze.rb +15 -0
  45. data/ext/{gyro → polyphony}/extconf.rb +2 -2
  46. data/ext/{gyro → polyphony}/fiber.c +17 -23
  47. data/ext/{gyro → polyphony}/libev.c +0 -0
  48. data/ext/{gyro → polyphony}/libev.h +0 -0
  49. data/ext/polyphony/libev_agent.c +718 -0
  50. data/ext/polyphony/libev_queue.c +216 -0
  51. data/ext/polyphony/polyphony.c +73 -0
  52. data/ext/{gyro/gyro.h → polyphony/polyphony.h} +19 -39
  53. data/ext/polyphony/polyphony_ext.c +21 -0
  54. data/ext/polyphony/thread.c +200 -0
  55. data/ext/{gyro → polyphony}/tracing.c +1 -1
  56. data/lib/polyphony.rb +19 -14
  57. data/lib/polyphony/adapters/irb.rb +1 -1
  58. data/lib/polyphony/adapters/postgres.rb +6 -5
  59. data/lib/polyphony/adapters/process.rb +5 -5
  60. data/lib/polyphony/adapters/trace.rb +28 -28
  61. data/lib/polyphony/core/channel.rb +3 -3
  62. data/lib/polyphony/core/exceptions.rb +1 -1
  63. data/lib/polyphony/core/global_api.rb +13 -11
  64. data/lib/polyphony/core/resource_pool.rb +3 -3
  65. data/lib/polyphony/core/sync.rb +2 -2
  66. data/lib/polyphony/core/thread_pool.rb +6 -6
  67. data/lib/polyphony/core/throttler.rb +13 -6
  68. data/lib/polyphony/event.rb +27 -0
  69. data/lib/polyphony/extensions/core.rb +22 -14
  70. data/lib/polyphony/extensions/fiber.rb +4 -4
  71. data/lib/polyphony/extensions/io.rb +59 -25
  72. data/lib/polyphony/extensions/openssl.rb +36 -16
  73. data/lib/polyphony/extensions/socket.rb +28 -10
  74. data/lib/polyphony/extensions/thread.rb +16 -9
  75. data/lib/polyphony/net.rb +9 -9
  76. data/lib/polyphony/version.rb +1 -1
  77. data/polyphony.gemspec +4 -4
  78. data/test/helper.rb +12 -8
  79. data/test/test_agent.rb +124 -0
  80. data/test/{test_async.rb → test_event.rb} +15 -7
  81. data/test/test_ext.rb +25 -4
  82. data/test/test_fiber.rb +19 -10
  83. data/test/test_global_api.rb +11 -11
  84. data/test/test_io.rb +44 -29
  85. data/test/test_queue.rb +74 -0
  86. data/test/test_signal.rb +3 -40
  87. data/test/test_socket.rb +34 -0
  88. data/test/test_thread.rb +38 -17
  89. data/test/test_thread_pool.rb +2 -2
  90. data/test/test_throttler.rb +5 -3
  91. data/test/test_trace.rb +6 -5
  92. metadata +41 -43
  93. data/docs/_includes/nav.html +0 -51
  94. data/docs/_includes/prevnext.html +0 -17
  95. data/docs/_layouts/default.html +0 -106
  96. data/docs/api-reference.md +0 -11
  97. data/docs/api-reference/gyro-async.md +0 -57
  98. data/docs/api-reference/gyro-child.md +0 -29
  99. data/docs/api-reference/gyro-queue.md +0 -44
  100. data/docs/api-reference/gyro-timer.md +0 -51
  101. data/docs/api-reference/gyro.md +0 -25
  102. data/docs/getting-started.md +0 -10
  103. data/docs/main-concepts.md +0 -10
  104. data/docs/user-guide.md +0 -10
  105. data/examples/core/forever_sleep.rb +0 -19
  106. data/ext/gyro/async.c +0 -162
  107. data/ext/gyro/child.c +0 -141
  108. data/ext/gyro/gyro.c +0 -103
  109. data/ext/gyro/gyro_ext.c +0 -33
  110. data/ext/gyro/io.c +0 -489
  111. data/ext/gyro/queue.c +0 -142
  112. data/ext/gyro/selector.c +0 -228
  113. data/ext/gyro/signal.c +0 -133
  114. data/ext/gyro/socket.c +0 -210
  115. data/ext/gyro/thread.c +0 -308
  116. data/ext/gyro/timer.c +0 -151
  117. data/test/test_timer.rb +0 -32
@@ -1,4 +1,4 @@
1
- #include "gyro.h"
1
+ #include "polyphony.h"
2
2
 
3
3
  int __tracing_enabled__ = 0;
4
4
 
@@ -1,45 +1,50 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'fiber'
4
- require_relative './gyro_ext'
4
+ require_relative './polyphony_ext'
5
5
 
6
- Thread.event_selector = Gyro::Selector
7
- Thread.current.setup_fiber_scheduling
6
+ module Polyphony
7
+ # Map Queue to Libev queue implementation
8
+ Queue = LibevQueue
9
+ end
8
10
 
9
11
  require_relative './polyphony/extensions/core'
10
12
  require_relative './polyphony/extensions/thread'
11
13
  require_relative './polyphony/extensions/fiber'
12
14
  require_relative './polyphony/extensions/io'
13
15
 
16
+ Thread.current.setup_fiber_scheduling
17
+ Thread.current.agent = Polyphony::LibevAgent.new
18
+
14
19
  require_relative './polyphony/core/global_api'
15
20
  require_relative './polyphony/core/resource_pool'
16
21
  require_relative './polyphony/net'
17
-
18
22
  require_relative './polyphony/adapters/process'
23
+ require_relative './polyphony/event'
19
24
 
20
25
  # Main Polyphony API
21
26
  module Polyphony
22
27
  class << self
23
28
  def wait_for_signal(sig)
29
+ raise "should be reimplemented"
30
+
24
31
  fiber = Fiber.current
25
- Gyro.ref
32
+ # Polyphony.ref
26
33
  old_trap = trap(sig) do
27
- Gyro.unref
34
+ # Polyphony.unref
28
35
  fiber.schedule(sig)
29
36
  trap(sig, old_trap)
30
37
  end
31
38
  suspend
39
+
32
40
  end
33
41
 
34
42
  def fork(&block)
35
- old_threads = Thread.list - [Thread.current]
36
43
  Kernel.fork do
37
- old_threads.each(&:deactivate_all_watchers_post_fork)
38
-
39
- # Since the fiber doing the fork will become the main fiber of the
40
- # forked process, we leave it behind by transferring to a new fiber
41
- # created in the context of the forked process, which rescues *all*
42
- # exceptions, including Interrupt and SystemExit.
44
+ # # Since the fiber doing the fork will become the main fiber of the
45
+ # # forked process, we leave it behind by transferring to a new fiber
46
+ # # created in the context of the forked process, which rescues *all*
47
+ # # exceptions, including Interrupt and SystemExit.
43
48
  spin_forked_block(&block).transfer
44
49
  end
45
50
  end
@@ -65,9 +70,9 @@ module Polyphony
65
70
  trap('SIGTERM', 'DEFAULT')
66
71
  trap('SIGINT', 'DEFAULT')
67
72
 
68
- Thread.current.post_fork
69
73
  Thread.current.setup
70
74
  Fiber.current.setup_main_fiber
75
+ Thread.current.agent.post_fork
71
76
 
72
77
  install_terminating_signal_handlers
73
78
 
@@ -16,7 +16,7 @@ if Object.constants.include?(:Reline)
16
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,18 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
+ # Process patches
4
5
  module Process
5
6
  class << self
6
7
  def watch(cmd = nil, &block)
7
8
  terminated = nil
8
9
  pid = cmd ? Kernel.spawn(cmd) : Polyphony.fork(&block)
9
- watcher = Gyro::Child.new(pid)
10
- watcher.await
10
+ Thread.current.agent.waitpid(pid)
11
11
  terminated = true
12
12
  ensure
13
13
  kill_process(pid) unless terminated || pid.nil?
14
14
  end
15
-
15
+
16
16
  def kill_process(pid)
17
17
  cancel_after(5) do
18
18
  kill_and_await('TERM', pid)
@@ -20,10 +20,10 @@ module Polyphony
20
20
  rescue Polyphony::Cancel
21
21
  kill_and_await(-9, pid)
22
22
  end
23
-
23
+
24
24
  def kill_and_await(sig, pid)
25
25
  ::Process.kill(sig, pid)
26
- Gyro::Child.new(pid).await
26
+ Thread.current.agent.waitpid(pid)
27
27
  rescue SystemCallError
28
28
  # ignore
29
29
  puts 'SystemCallError in kill_and_await'
@@ -5,6 +5,7 @@ require_relative '../../polyphony'
5
5
  STOCK_EVENTS = %i[line call return c_call c_return b_call b_return].freeze
6
6
 
7
7
  module Polyphony
8
+ # Tracing functionality for Polyphony
8
9
  module Trace
9
10
  class << self
10
11
  def new(*events)
@@ -12,10 +13,10 @@ module Polyphony
12
13
  events = STOCK_EVENTS if events.empty?
13
14
  ::TracePoint.new(*events) { |tp| yield trace_record(tp, start_stamp) }
14
15
  end
15
-
16
+
16
17
  def trace_record(trp, start_stamp)
17
18
  stamp = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start_stamp
18
-
19
+
19
20
  { stamp: stamp, event: trp.event, location: "#{trp.path}:#{trp.lineno}",
20
21
  self: trp.self, binding: trp.binding, fiber: tp_fiber(trp),
21
22
  lineno: trp.lineno, method_id: trp.method_id,
@@ -23,103 +24,103 @@ module Polyphony
23
24
  return_value: tp_return_value(trp), schedule_value: tp_schedule_value(trp),
24
25
  exception: tp_raised_exception(trp) }
25
26
  end
26
-
27
+
27
28
  def tp_fiber(trp)
28
29
  trp.is_a?(FiberTracePoint) ? trp.fiber : Fiber.current
29
30
  end
30
-
31
+
31
32
  PARAMS_EVENTS = %i[call c_call b_call].freeze
32
-
33
+
33
34
  def tp_params(trp)
34
35
  PARAMS_EVENTS.include?(trp.event) ? trp.parameters : nil
35
36
  end
36
-
37
+
37
38
  RETURN_VALUE_EVENTS = %i[return c_return b_return].freeze
38
-
39
+
39
40
  def tp_return_value(trp)
40
41
  RETURN_VALUE_EVENTS.include?(trp.event) ? trp.return_value : nil
41
42
  end
42
-
43
+
43
44
  SCHEDULE_VALUE_EVENTS = %i[fiber_schedule fiber_run].freeze
44
-
45
+
45
46
  def tp_schedule_value(trp)
46
47
  SCHEDULE_VALUE_EVENTS.include?(trp.event) ? trp.value : nil
47
48
  end
48
-
49
+
49
50
  def tp_raised_exception(trp)
50
51
  trp.event == :raise && trp.raised_exception
51
52
  end
52
-
53
+
53
54
  def analyze(records)
54
55
  by_fiber = Hash.new { |h, f| h[f] = [] }
55
56
  records.each_with_object(by_fiber) { |r, h| h[r[:fiber]] << r }
56
57
  { by_fiber: by_fiber }
57
58
  end
58
-
59
+
59
60
  # Implements fake TracePoint instances for fiber-related events
60
61
  class FiberTracePoint
61
62
  attr_reader :event, :fiber, :value
62
-
63
+
63
64
  def initialize(tpoint)
64
65
  @tp = tpoint
65
66
  @event = tpoint.return_value[0]
66
67
  @fiber = tpoint.return_value[1]
67
68
  @value = tpoint.return_value[2]
68
69
  end
69
-
70
+
70
71
  def lineno
71
72
  @tp.lineno
72
73
  end
73
-
74
+
74
75
  def method_id
75
76
  @tp.method_id
76
77
  end
77
-
78
+
78
79
  def path
79
80
  @tp.path
80
81
  end
81
-
82
+
82
83
  def self
83
84
  @tp.self
84
85
  end
85
-
86
+
86
87
  def binding
87
88
  @tp.binding
88
89
  end
89
90
  end
90
-
91
+
91
92
  class << ::TracePoint
92
93
  POLYPHONY_FILE_REGEXP = /^#{::Exception::POLYPHONY_DIR}/.freeze
93
-
94
+
94
95
  alias_method :orig_new, :new
95
96
  def new(*args, &block)
96
97
  events_mask, fiber_events_mask = event_masks(args)
97
-
98
+
98
99
  orig_new(*events_mask) do |tp|
99
100
  handle_tp_event(tp, fiber_events_mask, &block)
100
101
  end
101
102
  end
102
-
103
+
103
104
  def handle_tp_event(tpoint, fiber_events_mask)
104
105
  # next unless !$watched_fiber || Fiber.current == $watched_fiber
105
-
106
+
106
107
  if tpoint.method_id == :__fiber_trace__
107
108
  return if tpoint.event != :c_return
108
109
  return unless fiber_events_mask.include?(tpoint.return_value[0])
109
-
110
+
110
111
  tpoint = FiberTracePoint.new(tpoint)
111
112
  elsif tpoint.path =~ POLYPHONY_FILE_REGEXP
112
113
  return
113
114
  end
114
-
115
+
115
116
  yield tpoint
116
117
  end
117
-
118
+
118
119
  ALL_FIBER_EVENTS = %i[
119
120
  fiber_create fiber_terminate fiber_schedule fiber_switchpoint fiber_run
120
121
  fiber_ev_loop_enter fiber_ev_loop_leave
121
122
  ].freeze
122
-
123
+
123
124
  def event_masks(events)
124
125
  events.each_with_object([[], []]) do |e, masks|
125
126
  case e
@@ -135,4 +136,3 @@ module Polyphony
135
136
  end
136
137
  end
137
138
  end
138
-
@@ -26,7 +26,7 @@ module Polyphony
26
26
  end
27
27
 
28
28
  def receive
29
- Gyro.ref
29
+ Thread.current.agent.ref
30
30
  if @payload_queue.empty?
31
31
  @waiting_queue << Fiber.current
32
32
  suspend
@@ -34,7 +34,7 @@ module Polyphony
34
34
  receive_from_queue
35
35
  end
36
36
  ensure
37
- Gyro.unref
37
+ Thread.current.agent.unref
38
38
  end
39
39
 
40
40
  def receive_from_queue
@@ -43,4 +43,4 @@ module Polyphony
43
43
  payload
44
44
  end
45
45
  end
46
- end
46
+ end
@@ -33,4 +33,4 @@ module Polyphony
33
33
 
34
34
  # Restart is used to restart a fiber
35
35
  class Restart < BaseException; end
36
- end
36
+ end
@@ -45,13 +45,16 @@ module Polyphony
45
45
  end
46
46
 
47
47
  def every(interval)
48
- timer = Gyro::Timer.new(interval, interval)
48
+ next_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + interval
49
49
  loop do
50
- timer.await
50
+ now = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
51
+ Thread.current.agent.sleep(next_time - now)
51
52
  yield
53
+ loop do
54
+ next_time += interval
55
+ break if next_time > now
56
+ end
52
57
  end
53
- ensure
54
- timer.stop
55
58
  end
56
59
 
57
60
  def move_on_after(interval, with_value: nil, &block)
@@ -93,28 +96,27 @@ module Polyphony
93
96
  def sleep(duration = nil)
94
97
  return sleep_forever unless duration
95
98
 
96
- timer = Gyro::Timer.new(duration, 0)
97
- timer.await
99
+ Thread.current.agent.sleep duration
98
100
  end
99
101
 
100
102
  def sleep_forever
101
- Thread.current.fiber_ref
103
+ Thread.current.agent.ref
102
104
  suspend
103
105
  ensure
104
- Thread.current.fiber_unref
106
+ Thread.current.agent.unref
105
107
  end
106
108
 
107
109
  def throttled_loop(rate, count: nil, &block)
108
110
  throttler = Polyphony::Throttler.new(rate)
109
111
  if count
110
- count.times { throttler.(&block) }
112
+ count.times { |_i| throttler.(&block) }
111
113
  else
112
114
  loop { throttler.(&block) }
113
115
  end
114
116
  ensure
115
- throttler.stop
117
+ throttler&.stop
116
118
  end
117
119
  end
118
120
  end
119
121
 
120
- Object.include Polyphony::GlobalAPI
122
+ Object.include Polyphony::GlobalAPI
@@ -23,13 +23,13 @@ module Polyphony
23
23
  end
24
24
 
25
25
  def acquire
26
- Gyro.ref
26
+ Thread.current.agent.ref
27
27
  resource = wait_for_resource
28
28
  return unless resource
29
29
 
30
30
  yield resource
31
31
  ensure
32
- Gyro.unref
32
+ Thread.current.agent.unref
33
33
  release(resource) if resource
34
34
  end
35
35
 
@@ -104,4 +104,4 @@ module Polyphony
104
104
  (@limit - @size).times { @stock << allocate }
105
105
  end
106
106
  end
107
- end
107
+ end
@@ -4,7 +4,7 @@ module Polyphony
4
4
  # Implements mutex lock for synchronizing access to a shared resource
5
5
  class Mutex
6
6
  def initialize
7
- @waiting_fibers = Gyro::Queue.new
7
+ @waiting_fibers = Polyphony::Queue.new
8
8
  end
9
9
 
10
10
  def synchronize
@@ -18,4 +18,4 @@ module Polyphony
18
18
  snooze
19
19
  end
20
20
  end
21
- end
21
+ end
@@ -8,29 +8,29 @@ module Polyphony
8
8
  attr_reader :size
9
9
 
10
10
  def self.process(&block)
11
- @default_pool ||= self.new
11
+ @default_pool ||= new
12
12
  @default_pool.process(&block)
13
13
  end
14
14
 
15
15
  def self.reset
16
16
  return unless @default_pool
17
-
17
+
18
18
  @default_pool.stop
19
19
  @default_pool = nil
20
20
  end
21
21
 
22
22
  def initialize(size = Etc.nprocessors)
23
23
  @size = size
24
- @task_queue = Gyro::Queue.new
24
+ @task_queue = Polyphony::Queue.new
25
25
  @threads = (1..@size).map { Thread.new { thread_loop } }
26
26
  end
27
27
 
28
28
  def process(&block)
29
29
  setup unless @task_queue
30
30
 
31
- async = Fiber.current.auto_async
32
- @task_queue << [block, async]
33
- async.await
31
+ watcher = Fiber.current.auto_watcher
32
+ @task_queue << [block, watcher]
33
+ watcher.await
34
34
  end
35
35
 
36
36
  def cast(&block)