polyphony 0.36 → 0.42

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 (118) 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 +28 -2
  6. data/Gemfile +0 -11
  7. data/Gemfile.lock +15 -14
  8. data/README.md +2 -1
  9. data/Rakefile +7 -3
  10. data/TODO.md +28 -95
  11. data/docs/_config.yml +56 -7
  12. data/docs/_sass/custom/custom.scss +0 -30
  13. data/docs/_sass/overrides.scss +0 -46
  14. data/docs/{user-guide → _user-guide}/all-about-timers.md +0 -0
  15. data/docs/_user-guide/index.md +9 -0
  16. data/docs/{user-guide → _user-guide}/web-server.md +0 -0
  17. data/docs/api-reference/fiber.md +2 -2
  18. data/docs/api-reference/index.md +9 -0
  19. data/docs/api-reference/polyphony-process.md +1 -1
  20. data/docs/api-reference/thread.md +1 -1
  21. data/docs/faq.md +21 -11
  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 +507 -0
  25. data/docs/getting-started/tutorial.md +27 -19
  26. data/docs/index.md +3 -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/examples/core/01-spinning-up-fibers.rb +1 -0
  32. data/examples/core/03-interrupting.rb +4 -1
  33. data/examples/core/04-handling-signals.rb +19 -0
  34. data/examples/core/xx-agent.rb +102 -0
  35. data/examples/core/xx-fork-cleanup.rb +22 -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 +18 -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 +718 -0
  50. data/ext/polyphony/libev_queue.c +216 -0
  51. data/ext/{gyro/gyro.c → polyphony/polyphony.c} +16 -46
  52. data/ext/{gyro/gyro.h → polyphony/polyphony.h} +25 -39
  53. data/ext/polyphony/polyphony_ext.c +23 -0
  54. data/ext/{gyro → polyphony}/socket.c +21 -18
  55. data/ext/polyphony/thread.c +206 -0
  56. data/ext/{gyro → polyphony}/tracing.c +1 -1
  57. data/lib/polyphony.rb +40 -44
  58. data/lib/polyphony/adapters/fs.rb +1 -4
  59. data/lib/polyphony/adapters/irb.rb +1 -1
  60. data/lib/polyphony/adapters/postgres.rb +6 -5
  61. data/lib/polyphony/adapters/process.rb +27 -23
  62. data/lib/polyphony/adapters/trace.rb +110 -105
  63. data/lib/polyphony/core/channel.rb +35 -35
  64. data/lib/polyphony/core/exceptions.rb +29 -29
  65. data/lib/polyphony/core/global_api.rb +94 -91
  66. data/lib/polyphony/core/resource_pool.rb +83 -83
  67. data/lib/polyphony/core/sync.rb +16 -16
  68. data/lib/polyphony/core/thread_pool.rb +49 -37
  69. data/lib/polyphony/core/throttler.rb +30 -23
  70. data/lib/polyphony/event.rb +27 -0
  71. data/lib/polyphony/extensions/core.rb +25 -17
  72. data/lib/polyphony/extensions/fiber.rb +269 -267
  73. data/lib/polyphony/extensions/io.rb +56 -26
  74. data/lib/polyphony/extensions/openssl.rb +5 -9
  75. data/lib/polyphony/extensions/socket.rb +29 -10
  76. data/lib/polyphony/extensions/thread.rb +19 -12
  77. data/lib/polyphony/net.rb +64 -60
  78. data/lib/polyphony/version.rb +1 -1
  79. data/polyphony.gemspec +4 -7
  80. data/test/helper.rb +14 -1
  81. data/test/stress.rb +17 -12
  82. data/test/test_agent.rb +124 -0
  83. data/test/{test_async.rb → test_event.rb} +15 -7
  84. data/test/test_ext.rb +25 -4
  85. data/test/test_fiber.rb +19 -10
  86. data/test/test_global_api.rb +4 -4
  87. data/test/test_io.rb +46 -24
  88. data/test/test_queue.rb +74 -0
  89. data/test/test_signal.rb +3 -40
  90. data/test/test_socket.rb +33 -0
  91. data/test/test_thread.rb +38 -16
  92. data/test/test_thread_pool.rb +2 -2
  93. data/test/test_throttler.rb +0 -1
  94. data/test/test_trace.rb +6 -5
  95. metadata +41 -57
  96. data/docs/_includes/nav.html +0 -51
  97. data/docs/_includes/prevnext.html +0 -17
  98. data/docs/_layouts/default.html +0 -106
  99. data/docs/api-reference.md +0 -11
  100. data/docs/api-reference/gyro-async.md +0 -57
  101. data/docs/api-reference/gyro-child.md +0 -29
  102. data/docs/api-reference/gyro-queue.md +0 -44
  103. data/docs/api-reference/gyro-timer.md +0 -51
  104. data/docs/api-reference/gyro.md +0 -25
  105. data/docs/getting-started.md +0 -10
  106. data/docs/main-concepts.md +0 -10
  107. data/docs/user-guide.md +0 -10
  108. data/examples/core/forever_sleep.rb +0 -19
  109. data/ext/gyro/async.c +0 -148
  110. data/ext/gyro/child.c +0 -127
  111. data/ext/gyro/gyro_ext.c +0 -33
  112. data/ext/gyro/io.c +0 -474
  113. data/ext/gyro/queue.c +0 -142
  114. data/ext/gyro/selector.c +0 -205
  115. data/ext/gyro/signal.c +0 -118
  116. data/ext/gyro/thread.c +0 -298
  117. data/ext/gyro/timer.c +0 -134
  118. data/test/test_timer.rb +0 -56
@@ -202,9 +202,9 @@ class SpinLoopTest < MiniTest::Test
202
202
  buffer = []
203
203
  counter = 0
204
204
  f = spin_loop(rate: 50) { buffer << (counter += 1) }
205
- sleep 0.1
205
+ sleep 0.2
206
206
  f.stop
207
- assert counter >= 5 && counter <= 6
207
+ assert counter >= 9 && counter <= 11
208
208
  end
209
209
  end
210
210
 
@@ -215,9 +215,9 @@ class ThrottledLoopTest < MiniTest::Test
215
215
  f = spin do
216
216
  throttled_loop(50) { buffer << (counter += 1) }
217
217
  end
218
- sleep 0.1
218
+ sleep 0.2
219
219
  f.stop
220
- assert counter >= 5 && counter <= 6
220
+ assert counter >= 9 && counter <= 11
221
221
  end
222
222
 
223
223
  def test_throttled_loop_with_count
@@ -2,30 +2,6 @@
2
2
 
3
3
  require_relative 'helper'
4
4
 
5
- class GyroIOTest < MiniTest::Test
6
- def test_that_reading_works
7
- i, o = IO.pipe
8
- data = +''
9
- sequence = []
10
- watcher = Gyro::IO.new(i, :r)
11
- spin {
12
- sequence << 1
13
- watcher.await
14
- sequence << 2
15
- i.read_nonblock(8192, data)
16
- }
17
- snooze
18
- sequence << 3
19
- spin do
20
- o << 'hello'
21
- sequence << 4
22
- end
23
- suspend
24
- assert_equal 'hello', data
25
- assert_equal [1, 3, 4, 2], sequence
26
- end
27
- end
28
-
29
5
  class IOTest < MiniTest::Test
30
6
  def setup
31
7
  super
@@ -61,6 +37,52 @@ class IOTest < MiniTest::Test
61
37
  @o.close
62
38
  assert_equal 'foobarbaz', @i.read
63
39
  end
40
+
41
+ def test_wait_io
42
+ results = []
43
+ i, o = IO.pipe
44
+ f = spin do
45
+ loop do
46
+ result = i.orig_read_nonblock(8192, exception: false)
47
+ results << result
48
+ case result
49
+ when :wait_readable
50
+ Thread.current.agent.wait_io(i, false)
51
+ else
52
+ break result
53
+ end
54
+ end
55
+ end
56
+
57
+ snooze
58
+ o.write('foo')
59
+ o.close
60
+
61
+ result = f.await
62
+
63
+ assert_equal 'foo', f.await
64
+ assert_equal [:wait_readable, 'foo'], results
65
+ end
66
+
67
+ def test_readpartial
68
+ i, o = IO.pipe
69
+
70
+ o << 'hi'
71
+ assert_equal 'hi', i.readpartial(3)
72
+
73
+ o << 'hi'
74
+ assert_equal 'h', i.readpartial(1)
75
+ assert_equal 'i', i.readpartial(1)
76
+
77
+ spin {
78
+ sleep 0.01
79
+ o << 'hi'
80
+ }
81
+ assert_equal 'hi', i.readpartial(2)
82
+ o.close
83
+
84
+ assert_raises(EOFError) { i.readpartial(1) }
85
+ end
64
86
  end
65
87
 
66
88
  class IOClassMethodsTest < MiniTest::Test
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helper'
4
+
5
+ class QueueTest < MiniTest::Test
6
+ def setup
7
+ super
8
+ @queue = Polyphony::Queue.new
9
+ end
10
+
11
+ def test_pop
12
+ spin {
13
+ @queue << 42
14
+ }
15
+ v = @queue.shift
16
+ assert_equal 42, v
17
+
18
+ (1..4).each { |i| @queue << i }
19
+ buf = []
20
+ 4.times { buf << @queue.shift }
21
+ assert_equal [1, 2, 3, 4], buf
22
+ end
23
+
24
+ def test_multiple_waiters
25
+ a = spin { @queue.shift }
26
+ b = spin { @queue.shift }
27
+
28
+ @queue << :foo
29
+ @queue << :bar
30
+
31
+ assert_equal [:foo, :bar], Fiber.await(a, b)
32
+ end
33
+
34
+ def test_multi_thread_usage
35
+ t = Thread.new { @queue.push :foo }
36
+ assert_equal :foo, @queue.shift
37
+ end
38
+
39
+ def test_shift_each
40
+ (1..4).each { |i| @queue << i }
41
+ buf = []
42
+ @queue.shift_each { |i| buf << i }
43
+ assert_equal [1, 2, 3, 4], buf
44
+ end
45
+
46
+ def test_empty?
47
+ assert @queue.empty?
48
+
49
+ @queue << :foo
50
+ assert !@queue.empty?
51
+
52
+ assert_equal :foo, @queue.shift
53
+ assert @queue.empty?
54
+ end
55
+
56
+ def test_fiber_removal_from_queue
57
+ f1 = spin { @queue.shift }
58
+ f2 = spin { @queue.shift }
59
+ f3 = spin { @queue.shift }
60
+
61
+ # let fibers run
62
+ snooze
63
+
64
+ f2.stop
65
+ snooze
66
+
67
+ @queue << :foo
68
+ @queue << :bar
69
+
70
+ assert_equal :foo, f1.await
71
+ assert_nil f2.await
72
+ assert_equal :bar, f3.await
73
+ end
74
+ end
@@ -2,41 +2,6 @@
2
2
 
3
3
  require_relative 'helper'
4
4
 
5
- class SignalTest < MiniTest::Test
6
- def test_Gyro_Signal_constructor
7
- sig = Signal.list['USR1']
8
- count = 0
9
- w = Gyro::Signal.new(sig)
10
-
11
- spin {
12
- loop {
13
- w.await
14
- count += 1
15
- break
16
- }
17
- }
18
- Thread.new do
19
- orig_sleep 0.001
20
- Process.kill(:USR1, Process.pid)
21
- end
22
- suspend
23
- assert_equal 1, count
24
- end
25
-
26
- def test_wait_for_signal_api
27
- count = 0
28
- spin do
29
- Polyphony.wait_for_signal 'SIGHUP'
30
- count += 1
31
- end
32
-
33
- snooze
34
- Process.kill(:HUP, Process.pid)
35
- snooze
36
- assert_equal 1, count
37
- end
38
- end
39
-
40
5
  class SignalTrapTest < Minitest::Test
41
6
  def test_signal_exception_handling
42
7
  i, o = IO.pipe
@@ -57,9 +22,8 @@ class SignalTrapTest < Minitest::Test
57
22
  end
58
23
  sleep 0.01
59
24
  o.close
60
- watcher = Gyro::Child.new(pid)
61
25
  Process.kill('INT', pid)
62
- watcher.await
26
+ Thread.current.agent.waitpid(pid)
63
27
  buffer = i.read
64
28
  assert_equal "3-interrupt\n", buffer
65
29
  end
@@ -86,9 +50,8 @@ class SignalTrapTest < Minitest::Test
86
50
  end
87
51
  sleep 0.02
88
52
  o.close
89
- watcher = Gyro::Child.new(pid)
90
53
  Process.kill('INT', pid)
91
- watcher.await
54
+ Thread.current.agent.waitpid(pid)
92
55
  buffer = i.read
93
56
  assert_equal "3 - interrupted\n2 - terminated\n1 - terminated\n", buffer
94
57
  end
@@ -130,7 +93,7 @@ class SignalTrapTest < Minitest::Test
130
93
  o.close
131
94
  sleep 0.1
132
95
  Process.kill('INT', pid)
133
- Gyro::Child.new(pid).await
96
+ Thread.current.agent.waitpid(pid)
134
97
  buffer = i.read
135
98
  assert_equal "3-interrupt\n", buffer
136
99
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helper'
4
+
5
+ class SocketTest < MiniTest::Test
6
+ def setup
7
+ super
8
+ end
9
+
10
+ def test_tcp
11
+ server = TCPServer.new('127.0.0.1', 1234)
12
+
13
+ server_fiber = spin do
14
+ while (socket = server.accept)
15
+ spin do
16
+ while (data = socket.gets(8192))
17
+ socket << data
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ snooze
24
+ client = TCPSocket.new('127.0.0.1', 1234)
25
+ client.write("1234\n")
26
+ assert_equal "1234\n", client.readpartial(8192)
27
+ client.close
28
+ ensure
29
+ server_fiber.stop
30
+ snooze
31
+ server&.close
32
+ end
33
+ end
@@ -1,20 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'helper'
4
+ require 'polyphony/adapters/trace'
4
5
 
5
6
  class ThreadTest < MiniTest::Test
6
7
  def test_thread_spin
7
8
  buffer = []
8
9
  f = spin { (1..3).each { |i| snooze; buffer << i } }
9
10
  t = Thread.new do
11
+ sleep 0.01
10
12
  s1 = spin { (11..13).each { |i| snooze; buffer << i } }
11
13
  s2 = spin { (21..23).each { |i| snooze; buffer << i } }
14
+ sleep 0.02
12
15
  Fiber.current.await_all_children
13
16
  end
14
17
  f.join
15
18
  t.join
19
+ t = nil
16
20
 
17
21
  assert_equal [1, 2, 3, 11, 12, 13, 21, 22, 23], buffer.sort
22
+ ensure
23
+ t&.kill
24
+ t&.join
18
25
  end
19
26
 
20
27
  def test_thread_join
@@ -23,11 +30,13 @@ class ThreadTest < MiniTest::Test
23
30
  t = Thread.new { sleep 0.01; buffer << 4; :foo }
24
31
 
25
32
  r = t.join
33
+ t = nil
26
34
 
27
- assert_equal [1, 2, 3, 4], buffer
28
35
  assert_equal :foo, r
36
+ assert_equal [1, 2, 3, 4], buffer
29
37
  ensure
30
- t.kill
38
+ t&.kill
39
+ t&.join
31
40
  end
32
41
 
33
42
  def test_thread_join_with_timeout
@@ -36,6 +45,7 @@ class ThreadTest < MiniTest::Test
36
45
  t = Thread.new { sleep 1; buffer << 4 }
37
46
  t0 = Time.now
38
47
  r = t.join(0.01)
48
+ t = nil
39
49
 
40
50
  assert Time.now - t0 < 0.2
41
51
  assert_equal [1, 2, 3], buffer
@@ -43,7 +53,8 @@ class ThreadTest < MiniTest::Test
43
53
  ensure
44
54
  # killing the thread will prevent stopping the sleep timer, as well as the
45
55
  # thread's event selector, leading to a memory leak.
46
- t&.kill if t&.alive?
56
+ t&.kill
57
+ t&.join
47
58
  end
48
59
 
49
60
  def test_thread_await_alias_method
@@ -51,11 +62,13 @@ class ThreadTest < MiniTest::Test
51
62
  spin { (1..3).each { |i| snooze; buffer << i } }
52
63
  t = Thread.new { sleep 0.01; buffer << 4; :foo }
53
64
  r = t.await
65
+ t = nil
54
66
 
55
67
  assert_equal [1, 2, 3, 4], buffer
56
68
  assert_equal :foo, r
57
69
  ensure
58
- t.kill
70
+ t&.kill
71
+ t&.join
59
72
  end
60
73
 
61
74
  def test_join_race_condition_on_thread_spawning
@@ -64,27 +77,33 @@ class ThreadTest < MiniTest::Test
64
77
  :foo
65
78
  end
66
79
  r = t.join
80
+ t = nil
67
81
  assert_equal :foo, r
82
+ ensure
83
+ t&.kill
84
+ t&.join
68
85
  end
69
86
 
70
87
  def test_thread_uncaught_exception_propagation
71
- t = Thread.new do
72
- sleep 1
73
- end
74
- snooze
75
- t.kill
76
- t.await
88
+ ready = Polyphony::Event.new
77
89
 
78
90
  t = Thread.new do
91
+ ready.signal
92
+ sleep 0.01
79
93
  raise 'foo'
80
94
  end
81
95
  e = nil
82
96
  begin
83
- t.await
97
+ ready.await
98
+ r = t.await
84
99
  rescue Exception => e
85
100
  end
101
+ t = nil
86
102
  assert_kind_of RuntimeError, e
87
103
  assert_equal 'foo', e.message
104
+ ensure
105
+ t&.kill
106
+ t&.join
88
107
  end
89
108
 
90
109
  def test_thread_inspect
@@ -101,9 +120,8 @@ class ThreadTest < MiniTest::Test
101
120
  p e
102
121
  puts e.backtrace.join("\n")
103
122
  ensure
104
- t.kill
105
- sleep 0.005
106
- t.join
123
+ t&.kill
124
+ t&.join
107
125
  end
108
126
 
109
127
  def test_that_suspend_returns_immediately_if_no_watchers
@@ -112,14 +130,14 @@ class ThreadTest < MiniTest::Test
112
130
  records << r if r[:event] =~ /^fiber_/
113
131
  end
114
132
  t.enable
115
- Gyro.trace(true)
133
+ Polyphony.trace(true)
116
134
 
117
135
  suspend
118
136
  t.disable
119
137
  assert_equal [:fiber_switchpoint], records.map { |r| r[:event] }
120
138
  ensure
121
139
  t&.disable
122
- Gyro.trace(false)
140
+ Polyphony.trace(false)
123
141
  end
124
142
 
125
143
  def test_thread_child_fiber_termination
@@ -142,7 +160,11 @@ class ThreadTest < MiniTest::Test
142
160
  assert_equal 2, t.main_fiber.children.size
143
161
  t.kill
144
162
  t.join
163
+ t = nil
145
164
 
146
165
  assert_equal [:foo, :bar], buffer
166
+ ensure
167
+ t&.kill
168
+ t&.join
147
169
  end
148
170
  end
@@ -25,7 +25,7 @@ class ThreadPoolTest < MiniTest::Test
25
25
  threads = []
26
26
  results = []
27
27
 
28
- 10.times do |i|
28
+ 15.times do |i|
29
29
  spin do
30
30
  results << @pool.process do
31
31
  threads << Thread.current
@@ -38,7 +38,7 @@ class ThreadPoolTest < MiniTest::Test
38
38
  suspend
39
39
 
40
40
  assert_equal @pool.size, threads.uniq.size
41
- assert_equal (0..9).map { |i| i * 10}, results.sort
41
+ assert_equal (0..14).map { |i| i * 10}, results.sort
42
42
  end
43
43
 
44
44
  def test_process_with_exception