polyphony 0.40 → 0.43.2

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 (115) 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 +29 -2
  6. data/Gemfile.lock +13 -10
  7. data/README.md +0 -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/adapters/redis_blpop.rb +12 -0
  33. data/examples/core/01-spinning-up-fibers.rb +1 -0
  34. data/examples/core/03-interrupting.rb +4 -1
  35. data/examples/core/04-handling-signals.rb +19 -0
  36. data/examples/core/xx-agent.rb +102 -0
  37. data/examples/core/xx-sleeping.rb +14 -6
  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 +15 -22
  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 +725 -0
  50. data/ext/polyphony/libev_queue.c +217 -0
  51. data/ext/{gyro/gyro.c → polyphony/polyphony.c} +12 -37
  52. data/ext/polyphony/polyphony.h +90 -0
  53. data/ext/polyphony/polyphony_ext.c +21 -0
  54. data/ext/{gyro → polyphony}/thread.c +34 -151
  55. data/ext/{gyro → polyphony}/tracing.c +1 -1
  56. data/lib/polyphony.rb +19 -12
  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/redis.rb +3 -2
  61. data/lib/polyphony/adapters/trace.rb +28 -28
  62. data/lib/polyphony/core/channel.rb +3 -3
  63. data/lib/polyphony/core/exceptions.rb +1 -1
  64. data/lib/polyphony/core/global_api.rb +13 -11
  65. data/lib/polyphony/core/resource_pool.rb +3 -3
  66. data/lib/polyphony/core/sync.rb +2 -2
  67. data/lib/polyphony/core/thread_pool.rb +6 -6
  68. data/lib/polyphony/core/throttler.rb +13 -6
  69. data/lib/polyphony/event.rb +27 -0
  70. data/lib/polyphony/extensions/core.rb +22 -14
  71. data/lib/polyphony/extensions/fiber.rb +4 -4
  72. data/lib/polyphony/extensions/io.rb +59 -25
  73. data/lib/polyphony/extensions/openssl.rb +36 -16
  74. data/lib/polyphony/extensions/socket.rb +28 -10
  75. data/lib/polyphony/extensions/thread.rb +16 -9
  76. data/lib/polyphony/net.rb +9 -9
  77. data/lib/polyphony/version.rb +1 -1
  78. data/polyphony.gemspec +3 -3
  79. data/test/helper.rb +12 -1
  80. data/test/test_agent.rb +130 -0
  81. data/test/{test_async.rb → test_event.rb} +13 -7
  82. data/test/test_ext.rb +25 -4
  83. data/test/test_fiber.rb +19 -10
  84. data/test/test_global_api.rb +6 -6
  85. data/test/test_io.rb +46 -24
  86. data/test/test_queue.rb +74 -0
  87. data/test/test_signal.rb +3 -40
  88. data/test/test_socket.rb +34 -0
  89. data/test/test_thread.rb +37 -16
  90. data/test/test_trace.rb +6 -5
  91. metadata +39 -41
  92. data/docs/_includes/nav.html +0 -51
  93. data/docs/_includes/prevnext.html +0 -17
  94. data/docs/_layouts/default.html +0 -106
  95. data/docs/api-reference.md +0 -11
  96. data/docs/api-reference/gyro-async.md +0 -57
  97. data/docs/api-reference/gyro-child.md +0 -29
  98. data/docs/api-reference/gyro-queue.md +0 -44
  99. data/docs/api-reference/gyro-timer.md +0 -51
  100. data/docs/api-reference/gyro.md +0 -25
  101. data/docs/getting-started.md +0 -10
  102. data/docs/main-concepts.md +0 -10
  103. data/docs/user-guide.md +0 -10
  104. data/examples/core/forever_sleep.rb +0 -19
  105. data/ext/gyro/async.c +0 -132
  106. data/ext/gyro/child.c +0 -108
  107. data/ext/gyro/gyro.h +0 -158
  108. data/ext/gyro/gyro_ext.c +0 -33
  109. data/ext/gyro/io.c +0 -457
  110. data/ext/gyro/queue.c +0 -146
  111. data/ext/gyro/selector.c +0 -205
  112. data/ext/gyro/signal.c +0 -99
  113. data/ext/gyro/socket.c +0 -213
  114. data/ext/gyro/timer.c +0 -115
  115. data/test/test_timer.rb +0 -56
@@ -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)
@@ -6,17 +6,24 @@ module Polyphony
6
6
  def initialize(rate)
7
7
  @rate = rate_from_argument(rate)
8
8
  @min_dt = 1.0 / @rate
9
+ @next_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
9
10
  end
10
11
 
11
- def call(&block)
12
- @timer ||= Gyro::Timer.new(0, @min_dt)
13
- @timer.await
14
- block.call(self)
12
+ def call
13
+ now = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
14
+ delta = @next_time - now
15
+ Thread.current.agent.sleep(delta) if delta > 0
16
+ yield self
17
+
18
+ loop do
19
+ @next_time += @min_dt
20
+ break if @next_time > now
21
+ end
15
22
  end
16
23
  alias_method :process, :call
17
24
 
18
25
  def stop
19
- @timer&.stop
26
+ @stop = true
20
27
  end
21
28
 
22
29
  private
@@ -31,4 +38,4 @@ module Polyphony
31
38
  raise "Invalid rate argument #{arg.inspect}"
32
39
  end
33
40
  end
34
- end
41
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Polyphony
4
+ # Event watcher for thread-safe synchronisation
5
+ class Event
6
+ def initialize
7
+ @i, @o = IO.pipe
8
+ end
9
+
10
+ def await
11
+ Thread.current.agent.read(@i, +'', 8192, false)
12
+ raise @value if @value.is_a?(Exception)
13
+
14
+ @value
15
+ end
16
+
17
+ def await_no_raise
18
+ Thread.current.agent.read(@i, +'', 8192, false)
19
+ @value
20
+ end
21
+
22
+ def signal(value = nil)
23
+ @value = value
24
+ Thread.current.agent.write(@o, '1')
25
+ end
26
+ end
27
+ end
@@ -8,8 +8,6 @@ require_relative '../core/exceptions'
8
8
 
9
9
  # Exeption overrides
10
10
  class ::Exception
11
- EXIT_EXCEPTION_CLASSES = [::Interrupt, ::SystemExit].freeze
12
-
13
11
  class << self
14
12
  attr_accessor :__disable_sanitized_backtrace__
15
13
  end
@@ -24,7 +22,7 @@ class ::Exception
24
22
 
25
23
  alias_method :orig_backtrace, :backtrace
26
24
  def backtrace
27
- unless @first_backtrace_call || EXIT_EXCEPTION_CLASSES.include?(self.class)
25
+ unless @first_backtrace_call
28
26
  @first_backtrace_call = true
29
27
  return orig_backtrace
30
28
  end
@@ -52,10 +50,13 @@ end
52
50
 
53
51
  # Overrides for Process
54
52
  module ::Process
55
- def self.detach(pid)
56
- fiber = spin { Gyro::Child.new(pid).await }
57
- fiber.define_singleton_method(:pid) { pid }
58
- fiber
53
+ class << self
54
+ alias_method :orig_detach, :detach
55
+ def detach(pid)
56
+ fiber = spin { Thread.current.agent.waitpid(pid) }
57
+ fiber.define_singleton_method(:pid) { pid }
58
+ fiber
59
+ end
59
60
  end
60
61
  end
61
62
 
@@ -67,10 +68,9 @@ module ::Kernel
67
68
  def `(cmd)
68
69
  Open3.popen3(cmd) do |i, o, e, _t|
69
70
  i.close
70
- while (l = e.readpartial(8192))
71
- $stderr << l
72
- end
73
- o.read
71
+ err = e.read
72
+ $stderr << err if err
73
+ o.read || ''
74
74
  end
75
75
  end
76
76
 
@@ -91,6 +91,7 @@ module ::Kernel
91
91
  def gets(*_args)
92
92
  if !ARGV.empty? || @gets_fiber
93
93
  @gets_fiber ||= Fiber.new(&ARGV_GETS_LOOP)
94
+ @gets_fiber.thread = Thread.current
94
95
  result = @gets_fiber.alive? && @gets_fiber.safe_transfer(Fiber.current)
95
96
  return result if result
96
97
 
@@ -104,14 +105,21 @@ module ::Kernel
104
105
  def system(*args)
105
106
  Open3.popen2(*args) do |i, o, _t|
106
107
  i.close
107
- while (l = o.readpartial(8192))
108
- $stdout << l
109
- end
108
+ pipe_to_eof(o, $stdout)
110
109
  end
111
110
  true
112
111
  rescue SystemCallError
113
112
  nil
114
113
  end
114
+
115
+ def pipe_to_eof(src, dest)
116
+ loop do
117
+ data = src.readpartial(8192)
118
+ dest << data
119
+ rescue EOFError
120
+ break
121
+ end
122
+ end
115
123
  end
116
124
 
117
125
  # Override Timeout to use cancel scope
@@ -238,7 +238,7 @@ module Polyphony
238
238
  @parent = parent
239
239
  @caller = caller
240
240
  @block = block
241
- @mailbox = Gyro::Queue.new
241
+ @mailbox = Polyphony::Queue.new
242
242
  __fiber_trace__(:fiber_create, self)
243
243
  schedule
244
244
  end
@@ -268,7 +268,7 @@ module Polyphony
268
268
  # allows the fiber to be scheduled and to receive messages.
269
269
  def setup_raw
270
270
  @thread = Thread.current
271
- @mailbox = Gyro::Queue.new
271
+ @mailbox = Polyphony::Queue.new
272
272
  end
273
273
 
274
274
  def setup_main_fiber
@@ -277,11 +277,11 @@ module Polyphony
277
277
  @thread = Thread.current
278
278
  @running = true
279
279
  @children&.clear
280
- @mailbox = Gyro::Queue.new
280
+ @mailbox = Polyphony::Queue.new
281
281
  end
282
282
 
283
283
  def restart_self(first_value)
284
- @mailbox = Gyro::Queue.new
284
+ @mailbox = Polyphony::Queue.new
285
285
  @when_done_procs = nil
286
286
  @waiting_fibers = nil
287
287
  run(first_value)
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'open3'
4
4
 
5
- # IO overrides
5
+ # IO class method patches
6
6
  class ::IO
7
7
  class << self
8
8
  alias_method :orig_binread, :binread
@@ -72,7 +72,10 @@ class ::IO
72
72
  Open3.popen2(cmd) { |_i, o, _t| yield o }
73
73
  end
74
74
  end
75
+ end
75
76
 
77
+ # IO instance method patches
78
+ class ::IO
76
79
  # def each(sep = $/, limit = nil, chomp: nil)
77
80
  # sep, limit = $/, sep if sep.is_a?(Integer)
78
81
  # end
@@ -93,6 +96,39 @@ class ::IO
93
96
  # def getc
94
97
  # end
95
98
 
99
+ alias_method :orig_read, :read
100
+ def read(len = 1 << 30)
101
+ @read_buffer ||= +''
102
+ result = Thread.current.agent.read(self, @read_buffer, len, true)
103
+ return nil unless result
104
+
105
+ already_read = @read_buffer
106
+ @read_buffer = +''
107
+ already_read
108
+ end
109
+
110
+ alias_method :orig_readpartial, :read
111
+ def readpartial(len)
112
+ @read_buffer ||= +''
113
+ result = Thread.current.agent.read(self, @read_buffer, len, false)
114
+ raise EOFError unless result
115
+
116
+ already_read = @read_buffer
117
+ @read_buffer = +''
118
+ already_read
119
+ end
120
+
121
+ alias_method :orig_write, :write
122
+ def write(str)
123
+ Thread.current.agent.write(self, str)
124
+ end
125
+
126
+ alias_method :orig_write_chevron, :<<
127
+ def <<(str)
128
+ Thread.current.agent.write(self, str)
129
+ self
130
+ end
131
+
96
132
  alias_method :orig_gets, :gets
97
133
  def gets(sep = $/, _limit = nil, _chomp: nil)
98
134
  if sep.is_a?(Integer)
@@ -101,23 +137,17 @@ class ::IO
101
137
  end
102
138
  sep_size = sep.bytesize
103
139
 
104
- @gets_buffer ||= +''
140
+ @read_buffer ||= +''
105
141
 
106
142
  loop do
107
- idx = @gets_buffer.index(sep)
108
- return @gets_buffer.slice!(0, idx + sep_size) if idx
109
-
110
- if (data = readpartial(8192))
111
- @gets_buffer << data
112
- else
113
- return nil if @gets_buffer.empty?
143
+ idx = @read_buffer.index(sep)
144
+ return @read_buffer.slice!(0, idx + sep_size) if idx
114
145
 
115
- line = @gets_buffer.freeze
116
- @gets_buffer = +''
117
- return line
118
- end
146
+ data = readpartial(8192)
147
+ @read_buffer << data
148
+ rescue EOFError
149
+ return nil
119
150
  end
120
- # orig_gets(sep, limit, chomp: chomp)
121
151
  end
122
152
 
123
153
  # def print(*args)
@@ -171,16 +201,20 @@ class ::IO
171
201
  buf ? readpartial(maxlen, buf) : readpartial(maxlen)
172
202
  end
173
203
 
174
- alias_method :orig_read, :read
175
- def read(length = nil, outbuf = nil)
176
- if length
177
- return outbuf ? readpartial(length) : readpartial(length, outbuf)
178
- end
179
-
180
- until eof?
181
- outbuf ||= +''
182
- outbuf << readpartial(8192)
183
- end
184
- outbuf
204
+ def read_loop(&block)
205
+ Thread.current.agent.read_loop(self, &block)
185
206
  end
207
+
208
+ # alias_method :orig_read, :read
209
+ # def read(length = nil, outbuf = nil)
210
+ # if length
211
+ # return outbuf ? readpartial(length) : readpartial(length, outbuf)
212
+ # end
213
+
214
+ # until eof?
215
+ # outbuf ||= +''
216
+ # outbuf << readpartial(8192)
217
+ # end
218
+ # outbuf
219
+ # end
186
220
  end
@@ -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,14 +17,36 @@ class ::OpenSSL::SSL::SSLSocket
18
17
  io.reuse_addr
19
18
  end
20
19
 
21
- def sysread(maxlen, buf)
22
- read_watcher = nil
23
- write_watcher = nil
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 = +'')
24
35
  loop do
25
36
  case (result = read_nonblock(maxlen, buf, exception: false))
26
- when :wait_readable then (read_watcher ||= io.read_watcher).await
27
- when :wait_writable then (write_watcher ||= io.write_watcher).await
28
- else result
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))
47
+ when :wait_writable then Thread.current.agent.wait_io(io, true)
48
+ else
49
+ return result
29
50
  end
30
51
  end
31
52
  end
@@ -39,15 +60,14 @@ class ::OpenSSL::SSL::SSLSocket
39
60
  # @sync = osync
40
61
  end
41
62
 
42
- def syswrite(buf)
43
- read_watcher = nil
44
- write_watcher = nil
45
- loop do
46
- case (result = write_nonblock(buf, exception: false))
47
- when :wait_readable then (read_watcher ||= io.read_watcher).await
48
- when :wait_writable then (write_watcher ||= io.write_watcher).await
49
- else result
50
- 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
51
71
  end
52
72
  end
53
73
  end
@@ -7,14 +7,21 @@ require_relative '../core/thread_pool'
7
7
 
8
8
  # Socket overrides (eventually rewritten in C)
9
9
  class ::Socket
10
+ def accept
11
+ Thread.current.agent.accept(self)
12
+ end
13
+
10
14
  NO_EXCEPTION = { exception: false }.freeze
11
15
 
12
16
  def connect(remotesockaddr)
13
17
  loop do
14
18
  result = connect_nonblock(remotesockaddr, **NO_EXCEPTION)
15
- return if result == 0
16
-
17
- result == :wait_writable ? write_watcher.await : (raise IOError)
19
+ case result
20
+ when 0 then return
21
+ when :wait_writable then Thread.current.agent.wait_io(self, true)
22
+ else
23
+ raise IOError
24
+ end
18
25
  end
19
26
  end
20
27
 
@@ -22,9 +29,12 @@ class ::Socket
22
29
  outbuf ||= +''
23
30
  loop do
24
31
  result = recv_nonblock(maxlen, flags, outbuf, **NO_EXCEPTION)
25
- raise IOError unless result
26
-
27
- result == :wait_readable ? read_watcher.await : (return result)
32
+ case result
33
+ when nil then raise IOError
34
+ when :wait_readable then Thread.current.agent.wait_io(self, false)
35
+ else
36
+ return result
37
+ end
28
38
  end
29
39
  end
30
40
 
@@ -32,9 +42,12 @@ class ::Socket
32
42
  @read_buffer ||= +''
33
43
  loop do
34
44
  result = recvfrom_nonblock(maxlen, flags, @read_buffer, **NO_EXCEPTION)
35
- raise IOError unless result
36
-
37
- result == :wait_readable ? read_watcher.await : (return result)
45
+ case result
46
+ when nil then raise IOError
47
+ when :wait_readable then Thread.current.agent.wait_io(self, false)
48
+ else
49
+ return result
50
+ end
38
51
  end
39
52
  end
40
53
 
@@ -115,6 +128,11 @@ class ::TCPServer
115
128
 
116
129
  alias_method :orig_accept, :accept
117
130
  def accept
118
- @io ? @io.accept : orig_accept
131
+ @io.accept
132
+ end
133
+
134
+ alias_method :orig_close, :close
135
+ def close
136
+ @io.close
119
137
  end
120
138
  end