polyphony 0.44.0 → 0.45.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +8 -1
  3. data/CHANGELOG.md +41 -0
  4. data/Gemfile.lock +14 -8
  5. data/Rakefile +1 -1
  6. data/TODO.md +12 -15
  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/adapters/redis_client.rb +3 -1
  12. data/examples/adapters/redis_pubsub_perf.rb +11 -8
  13. data/examples/adapters/sequel_mysql.rb +1 -1
  14. data/examples/adapters/sequel_pg.rb +24 -0
  15. data/examples/core/{02-awaiting-fibers.rb → await.rb} +0 -0
  16. data/examples/core/{xx-channels.rb → channels.rb} +0 -0
  17. data/examples/core/deferring-an-operation.rb +16 -0
  18. data/examples/core/{xx-erlang-style-genserver.rb → erlang-style-genserver.rb} +16 -9
  19. data/examples/core/{xx-forking.rb → forking.rb} +1 -1
  20. data/examples/core/handling-signals.rb +11 -0
  21. data/examples/core/{03-interrupting.rb → interrupt.rb} +0 -0
  22. data/examples/core/{xx-pingpong.rb → pingpong.rb} +7 -5
  23. data/examples/core/{xx-recurrent-timer.rb → recurrent-timer.rb} +1 -1
  24. data/examples/core/{xx-resource_delegate.rb → resource_delegate.rb} +3 -4
  25. data/examples/core/{01-spinning-up-fibers.rb → spin.rb} +1 -1
  26. data/examples/core/{xx-spin_error_backtrace.rb → spin_error_backtrace.rb} +1 -1
  27. data/examples/core/{xx-supervise-process.rb → supervise-process.rb} +8 -5
  28. data/examples/core/supervisor.rb +20 -0
  29. data/examples/core/{xx-thread-sleep.rb → thread-sleep.rb} +0 -0
  30. data/examples/core/{xx-thread_pool.rb → thread_pool.rb} +0 -0
  31. data/examples/core/{xx-throttling.rb → throttling.rb} +0 -0
  32. data/examples/core/{xx-timeout.rb → timeout.rb} +0 -0
  33. data/examples/core/{xx-using-a-mutex.rb → using-a-mutex.rb} +0 -0
  34. data/examples/core/{xx-worker-thread.rb → worker-thread.rb} +2 -2
  35. data/examples/io/{xx-backticks.rb → backticks.rb} +0 -0
  36. data/examples/io/{xx-echo_client.rb → echo_client.rb} +1 -1
  37. data/examples/io/{xx-echo_client_from_stdin.rb → echo_client_from_stdin.rb} +2 -2
  38. data/examples/io/{xx-echo_pipe.rb → echo_pipe.rb} +1 -1
  39. data/examples/io/{xx-echo_server.rb → echo_server.rb} +0 -0
  40. data/examples/io/{xx-echo_server_with_timeout.rb → echo_server_with_timeout.rb} +1 -1
  41. data/examples/io/{xx-echo_stdin.rb → echo_stdin.rb} +0 -0
  42. data/examples/io/{xx-happy-eyeballs.rb → happy-eyeballs.rb} +0 -0
  43. data/examples/io/{xx-httparty.rb → httparty.rb} +4 -13
  44. data/examples/io/{xx-irb.rb → irb.rb} +0 -0
  45. data/examples/io/{xx-net-http.rb → net-http.rb} +0 -0
  46. data/examples/io/{xx-open.rb → open.rb} +0 -0
  47. data/examples/io/pry.rb +18 -0
  48. data/examples/io/rack_server.rb +71 -0
  49. data/examples/io/raw.rb +14 -0
  50. data/examples/io/reline.rb +18 -0
  51. data/examples/io/{xx-system.rb → system.rb} +1 -1
  52. data/examples/io/{xx-tcpserver.rb → tcpserver.rb} +0 -0
  53. data/examples/io/{xx-tcpsocket.rb → tcpsocket.rb} +0 -0
  54. data/examples/io/tunnel.rb +6 -1
  55. data/examples/io/{xx-zip.rb → zip.rb} +0 -0
  56. data/examples/performance/fiber_transfer.rb +2 -1
  57. data/examples/performance/fs_read.rb +5 -6
  58. data/examples/performance/multi_snooze.rb +0 -1
  59. data/examples/{io/xx-switch.rb → performance/switch.rb} +2 -1
  60. data/examples/performance/thread-vs-fiber/{xx-httparty_multi.rb → httparty_multi.rb} +3 -4
  61. data/examples/performance/thread-vs-fiber/{xx-httparty_threaded.rb → httparty_threaded.rb} +0 -0
  62. data/examples/performance/thread-vs-fiber/polyphony_mt_server.rb +1 -1
  63. data/examples/performance/thread-vs-fiber/polyphony_server.rb +1 -1
  64. data/examples/performance/thread-vs-fiber/polyphony_server_read_loop.rb +1 -1
  65. data/examples/performance/thread-vs-fiber/threaded_server.rb +1 -5
  66. data/examples/performance/thread_pool_perf.rb +6 -7
  67. data/ext/polyphony/backend.h +40 -0
  68. data/ext/polyphony/event.c +3 -3
  69. data/ext/polyphony/extconf.rb +1 -1
  70. data/ext/polyphony/fiber.c +90 -13
  71. data/ext/polyphony/{libev_agent.c → libev_backend.c} +226 -224
  72. data/ext/polyphony/polyphony.c +5 -7
  73. data/ext/polyphony/polyphony.h +18 -18
  74. data/ext/polyphony/polyphony_ext.c +5 -4
  75. data/ext/polyphony/queue.c +5 -6
  76. data/ext/polyphony/ring_buffer.c +0 -1
  77. data/ext/polyphony/runqueue.c +102 -0
  78. data/ext/polyphony/runqueue_ring_buffer.c +85 -0
  79. data/ext/polyphony/runqueue_ring_buffer.h +31 -0
  80. data/ext/polyphony/thread.c +53 -102
  81. data/lib/polyphony.rb +15 -14
  82. data/lib/polyphony/adapters/fs.rb +1 -1
  83. data/lib/polyphony/adapters/irb.rb +2 -17
  84. data/lib/polyphony/adapters/mysql2.rb +1 -1
  85. data/lib/polyphony/adapters/postgres.rb +5 -5
  86. data/lib/polyphony/adapters/process.rb +2 -5
  87. data/lib/polyphony/adapters/readline.rb +17 -0
  88. data/lib/polyphony/adapters/redis.rb +1 -1
  89. data/lib/polyphony/adapters/sequel.rb +1 -1
  90. data/lib/polyphony/core/global_api.rb +19 -14
  91. data/lib/polyphony/core/resource_pool.rb +2 -2
  92. data/lib/polyphony/core/sync.rb +43 -3
  93. data/lib/polyphony/core/throttler.rb +1 -1
  94. data/lib/polyphony/extensions/core.rb +25 -32
  95. data/lib/polyphony/extensions/fiber.rb +22 -45
  96. data/lib/polyphony/extensions/io.rb +60 -16
  97. data/lib/polyphony/extensions/openssl.rb +6 -6
  98. data/lib/polyphony/extensions/socket.rb +14 -15
  99. data/lib/polyphony/extensions/thread.rb +6 -5
  100. data/lib/polyphony/version.rb +1 -1
  101. data/polyphony.gemspec +5 -3
  102. data/test/helper.rb +1 -1
  103. data/test/{test_agent.rb → test_backend.rb} +22 -22
  104. data/test/test_fiber.rb +13 -12
  105. data/test/test_global_api.rb +29 -0
  106. data/test/test_io.rb +59 -1
  107. data/test/test_kernel.rb +5 -0
  108. data/test/test_signal.rb +14 -11
  109. data/test/test_socket.rb +17 -0
  110. data/test/test_sync.rb +73 -0
  111. metadata +99 -98
  112. data/.gitbook.yaml +0 -4
  113. data/examples/adapters/concurrent-ruby.rb +0 -9
  114. data/examples/core/04-handling-signals.rb +0 -19
  115. data/examples/core/xx-agent.rb +0 -102
  116. data/examples/core/xx-at_exit.rb +0 -29
  117. data/examples/core/xx-caller.rb +0 -12
  118. data/examples/core/xx-daemon.rb +0 -14
  119. data/examples/core/xx-deadlock.rb +0 -8
  120. data/examples/core/xx-deferring-an-operation.rb +0 -14
  121. data/examples/core/xx-exception-backtrace.rb +0 -40
  122. data/examples/core/xx-fork-cleanup.rb +0 -22
  123. data/examples/core/xx-fork-spin.rb +0 -42
  124. data/examples/core/xx-fork-terminate.rb +0 -27
  125. data/examples/core/xx-move_on.rb +0 -23
  126. data/examples/core/xx-queue-async.rb +0 -120
  127. data/examples/core/xx-readpartial.rb +0 -18
  128. data/examples/core/xx-signals.rb +0 -16
  129. data/examples/core/xx-sleep-forever.rb +0 -9
  130. data/examples/core/xx-sleeping.rb +0 -25
  131. data/examples/core/xx-snooze-starve.rb +0 -16
  132. data/examples/core/xx-spin-fork.rb +0 -49
  133. data/examples/core/xx-state-machine.rb +0 -51
  134. data/examples/core/xx-stop.rb +0 -20
  135. data/examples/core/xx-supervisors.rb +0 -21
  136. data/examples/core/xx-thread-selector-sleep.rb +0 -51
  137. data/examples/core/xx-thread-selector-snooze.rb +0 -46
  138. data/examples/core/xx-thread-snooze.rb +0 -34
  139. data/examples/core/xx-timer-gc.rb +0 -17
  140. data/examples/core/xx-trace.rb +0 -79
  141. data/examples/performance/xx-array.rb +0 -11
  142. data/examples/performance/xx-fiber-switch.rb +0 -9
  143. data/examples/performance/xx-snooze.rb +0 -15
  144. data/examples/xx-spin.rb +0 -32
  145. data/ext/polyphony/agent.h +0 -41
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'bundler/setup'
4
4
  require 'polyphony/adapters/sequel'
5
- require 'polyphony/adapters/mysql2'
5
+ require 'polyphony/adapters/postgres'
6
6
 
7
7
  time_printer = spin do
8
8
  last = Time.now
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony/adapters/sequel'
5
+ require 'polyphony/adapters/postgres'
6
+
7
+ URL = ENV['SEQUEL_URL'] || 'postgres://localhost/test'
8
+
9
+ x = 10000
10
+ query_count = 0
11
+
12
+ spin do
13
+ db = Sequel.connect(URL)
14
+ x.times { query_count += 1; db.execute('select 1 as test') }
15
+ end
16
+
17
+ spin do
18
+ db = Sequel.connect(URL)
19
+ x.times { query_count += 1; db.execute('select 2 as test') }
20
+ end
21
+
22
+ t0 = Time.now
23
+ Fiber.current.await_all_children
24
+ puts "query rate: #{query_count / (Time.now - t0)} reqs/s; count = #{query_count}"
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ spin do
7
+ puts 'two'
8
+ # spinning a fiber from the parent fiber allows us to schedule an operation to
9
+ # be performed even after the current fiber is terminated
10
+ Fiber.current.parent.spin { puts 'four' }
11
+ puts 'three'
12
+ end
13
+
14
+ puts 'one'
15
+
16
+ suspend
@@ -3,8 +3,10 @@
3
3
  require 'bundler/setup'
4
4
  require 'polyphony'
5
5
 
6
- class GenServer
7
- def self.start(receiver, *args)
6
+ module GenServer
7
+ module_function
8
+
9
+ def start(receiver, *args)
8
10
  fiber = spin do
9
11
  state = receiver.initial_state(*args)
10
12
  loop do
@@ -14,11 +16,10 @@ class GenServer
14
16
  end
15
17
  end
16
18
  build_api(fiber, receiver)
17
- snooze
18
19
  fiber
19
20
  end
20
21
 
21
- def self.build_api(fiber, receiver)
22
+ def build_api(fiber, receiver)
22
23
  receiver.methods(false).each do |m|
23
24
  if m =~ /!$/
24
25
  fiber.define_singleton_method(m) do |*args|
@@ -32,7 +33,7 @@ class GenServer
32
33
  end
33
34
  end
34
35
 
35
- def self.cast(process, method, *args)
36
+ def cast(process, method, *args)
36
37
  process << {
37
38
  from: Fiber.current,
38
39
  method: method,
@@ -40,7 +41,7 @@ class GenServer
40
41
  }
41
42
  end
42
43
 
43
- def self.call(process, method, *args)
44
+ def call(process, method, *args)
44
45
  process << {
45
46
  from: Fiber.current,
46
47
  method: method,
@@ -50,21 +51,27 @@ class GenServer
50
51
  end
51
52
  end
52
53
 
54
+ # In a generic server the state is not held in an instance variable but rather
55
+ # passed as the first parameter to method calls. The return value of each method
56
+ # is an array consisting of the result and the potentially mutated state.
53
57
  module Map
54
- def self.initial_state(hash = {})
58
+ module_function
59
+
60
+ def initial_state(hash = {})
55
61
  hash
56
62
  end
57
63
 
58
- def self.get(state, key)
64
+ def get(state, key)
59
65
  [state[key], state]
60
66
  end
61
67
 
62
- def self.put!(state, key, value)
68
+ def put!(state, key, value)
63
69
  state[key] = value
64
70
  [:noreply, state]
65
71
  end
66
72
  end
67
73
 
74
+ # start server with initial state
68
75
  map_server = GenServer.start(Map, {foo: :bar})
69
76
 
70
77
  puts 'getting value from map server'
@@ -20,5 +20,5 @@ end
20
20
  puts "got child pid #{pid}"
21
21
 
22
22
  puts 'parent waiting for child'
23
- Gyro::Child.new(pid).await
23
+ Thread.current.backend.waitpid(pid)
24
24
  puts 'parent done waiting'
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ puts "going to sleep (press Ctrl-C to stop)"
7
+ begin
8
+ sleep
9
+ ensure
10
+ puts "done sleeping"
11
+ end
@@ -9,10 +9,12 @@ pong = spin_loop do
9
9
  ping << 'pong'
10
10
  end
11
11
 
12
- ping = spin_loop do
13
- pong << ['ping', Fiber.current]
14
- msg = receive
15
- puts msg
12
+ ping = spin do
13
+ 3.times do
14
+ pong << ['ping', Fiber.current]
15
+ msg = receive
16
+ puts msg
17
+ end
16
18
  end
17
19
 
18
- suspend
20
+ ping.await
@@ -3,7 +3,7 @@
3
3
  require 'bundler/setup'
4
4
  require 'polyphony'
5
5
 
6
- move_on_after(3) do
6
+ move_on_after(3.1) do
7
7
  puts 'Start...'
8
8
  every(1) do
9
9
  puts Time.now
@@ -10,7 +10,7 @@ class Number
10
10
 
11
11
  def greet(other)
12
12
  puts "You are number #{other}, I am number #{@id}"
13
- sleep(0.05 + rand * 0.2)
13
+ sleep rand(0.2..0.3)
14
14
  end
15
15
  end
16
16
 
@@ -25,7 +25,6 @@ def meet(number)
25
25
  end
26
26
  end
27
27
 
28
- 3.times { |x| spin { meet(x) } }
28
+ (4..10).each { |x| spin { meet(x) } }
29
29
 
30
- t0 = Time.now
31
- every(10) { puts "uptime: #{Time.now - t0}" }
30
+ sleep 1
@@ -14,5 +14,5 @@ end
14
14
  spin { nap(:a, 1) }
15
15
  spin { nap(:b, 2) }
16
16
 
17
- # Calling suspend will block until all child fibers have terminated
17
+ # Calling suspend will block until no work is left to do
18
18
  suspend
@@ -9,7 +9,7 @@ end
9
9
 
10
10
  def deferred_error(t)
11
11
  puts "deferred_error"
12
- spin { de2(t) }
12
+ spin { de2(t) }.await
13
13
  end
14
14
 
15
15
  def de2(t)
@@ -8,7 +8,7 @@ Exception.__disable_sanitized_backtrace__ = true
8
8
  supervisor = spin do
9
9
  puts "parent pid #{Process.pid}"
10
10
 
11
- Polyphony::ProcessSupervisor.supervise do
11
+ Polyphony.watch_process do
12
12
  puts "child pid #{Process.pid}"
13
13
  puts "go to sleep"
14
14
  sleep 5
@@ -22,9 +22,12 @@ supervisor = spin do
22
22
  end
23
23
 
24
24
  begin
25
+ spin do
26
+ sleep 2.5
27
+ Process.kill('TERM', Process.pid)
28
+ end
29
+ supervisor.await
30
+ rescue SystemExit
31
+ supervisor.terminate
25
32
  supervisor.await
26
- rescue Interrupt
27
- exit!
28
- # supervisor.terminate
29
- # supervisor.await
30
33
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ def my_sleep(t)
7
+ puts "#{t} start"
8
+ sleep(t)
9
+ puts "#{t} done"
10
+ end
11
+
12
+ spin { my_sleep(1) }
13
+ spin { my_sleep(2) }
14
+ spin { my_sleep(3) }
15
+ spin { puts "fiber count: #{Fiber.current.children.count}" }
16
+ snooze
17
+
18
+ puts "#{Time.now} supervising..."
19
+ supervise
20
+ puts "#{Time.now} done supervising"
@@ -5,9 +5,9 @@ require 'polyphony'
5
5
 
6
6
  def do_work(client)
7
7
  result = yield
8
- client.schedule(result)
8
+ client << result
9
9
  rescue Exception => e
10
- client.schedule(e)
10
+ client << e
11
11
  end
12
12
 
13
13
  $worker = Thread.new do
@@ -15,7 +15,7 @@ end
15
15
 
16
16
  reader = spin do
17
17
  puts 'received from echo server:'
18
- while (data = socket.readpartial(8192))
18
+ socket.read_loop do |data|
19
19
  STDOUT << data
20
20
  end
21
21
  end
@@ -11,8 +11,8 @@ writer = spin do
11
11
  end
12
12
  end
13
13
 
14
- spin do
15
- while (data = socket.readpartial(8192))
14
+ reader = spin do
15
+ socket.read_loop do |data|
16
16
  STDOUT << 'received: ' + data
17
17
  end
18
18
  writer.interrupt
@@ -11,6 +11,6 @@ spin do
11
11
  o.close
12
12
  end
13
13
 
14
- while (data = i.readpartial(8192))
14
+ i.read_loop do |data|
15
15
  STDOUT << "You said: #{data}"
16
16
  end
@@ -17,7 +17,7 @@ begin
17
17
  scope.when_cancelled do
18
18
  client.write "Disconnecting due to inactivity\n"
19
19
  end
20
- while (data = client.readpartial(8192))
20
+ client.read_loop do |data|
21
21
  scope.reset_timeout
22
22
  client.write "You said: #{data}"
23
23
  end
@@ -16,23 +16,14 @@ end
16
16
  zones = %w{
17
17
  Europe/London Europe/Paris Europe/Bucharest America/New_York Asia/Bangkok
18
18
  }
19
- # zones.each do |tzone|
20
- # spin do
21
- # time = get_time(tzone)
22
- # puts "Time in #{tzone}: #{time}"
23
- # end
24
- # end
25
-
26
- # suspend
27
19
 
28
20
  def get_times(zones)
29
- Polyphony::Supervisor.new do |s|
30
- zones.each do |tzone|
31
- s.spin { [tzone, get_time(tzone)] }
32
- end
21
+ fibers = zones.map do |tzone|
22
+ spin { [tzone, get_time(tzone)] }
33
23
  end
24
+ Fiber.await(*fibers)
34
25
  end
35
26
 
36
- get_times(zones).await.each do |tzone, time|
27
+ get_times(zones).each do |tzone, time|
37
28
  puts "Time in #{tzone}: #{time}"
38
29
  end
File without changes
File without changes
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+ require 'polyphony/adapters/readline'
6
+ require 'pry'
7
+
8
+ $counter = 0
9
+ timer = spin do
10
+ throttled_loop(5) do
11
+ $counter += 1
12
+ end
13
+ end
14
+
15
+ at_exit { timer.stop }
16
+
17
+ puts 'try typing $counter to see the counter incremented in the background'
18
+ binding.pry
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+ require 'http/parser'
6
+ require 'rack'
7
+
8
+ module RackAdapter
9
+ class << self
10
+ def run(app)
11
+ ->(socket, req) { respond(socket, req, app.(env(req))) }
12
+ end
13
+
14
+ def env(req)
15
+ {}
16
+ end
17
+
18
+ def respond(socket, request, (status_code, headers, body))
19
+ body = body.join
20
+ headers = "Content-Type: text/plain\r\nContent-Length: #{body.bytesize}\r\n"
21
+ socket.write "HTTP/1.1 #{status_code}\r\n#{headers}\r\n#{body}"
22
+ end
23
+ end
24
+ end
25
+
26
+ $connection_count = 0
27
+
28
+ def handle_client(socket, &handler)
29
+ $connection_count += 1
30
+ parser = Http::Parser.new
31
+ reqs = []
32
+ parser.on_message_complete = proc do |env|
33
+ reqs << Object.new # parser
34
+ end
35
+ socket.read_loop do |data|
36
+ parser << data
37
+ while (req = reqs.shift)
38
+ handler.call(socket, req)
39
+ req = nil
40
+ end
41
+ end
42
+ rescue IOError, SystemCallError => e
43
+ # do nothing
44
+ ensure
45
+ $connection_count -= 1
46
+ socket&.close
47
+ end
48
+
49
+ def handle_request(client, parser)
50
+ status_code = "200 OK"
51
+ data = "Hello world!\n"
52
+ headers = "Content-Type: text/plain\r\nContent-Length: #{data.bytesize}\r\n"
53
+ client.write "HTTP/1.1 #{status_code}\r\n#{headers}\r\n#{data}"
54
+ end
55
+
56
+ server = TCPServer.open('0.0.0.0', 1234)
57
+ puts "pid #{Process.pid}"
58
+ puts "listening on port 1234"
59
+
60
+ app = RackAdapter.run(lambda { |env|
61
+ [
62
+ 200,
63
+ {"Content-Type" => "text/plain"},
64
+ ["Hello, world!\n"]
65
+ ]
66
+ })
67
+
68
+ loop do
69
+ client = server.accept
70
+ spin { handle_client(client, &app) }
71
+ end