polyphony 0.40 → 0.41

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) 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 +6 -2
  6. data/Gemfile.lock +9 -6
  7. data/Rakefile +2 -2
  8. data/TODO.md +18 -97
  9. data/docs/_includes/head.html +40 -0
  10. data/docs/_includes/nav.html +5 -5
  11. data/docs/api-reference/fiber.md +2 -2
  12. data/docs/main-concepts/design-principles.md +67 -9
  13. data/docs/main-concepts/extending.md +1 -1
  14. data/examples/core/xx-agent.rb +102 -0
  15. data/examples/core/xx-sleeping.rb +14 -6
  16. data/examples/io/xx-irb.rb +1 -1
  17. data/examples/performance/thread-vs-fiber/polyphony_mt_server.rb +7 -6
  18. data/examples/performance/thread-vs-fiber/polyphony_server.rb +14 -25
  19. data/ext/{gyro → polyphony}/extconf.rb +2 -2
  20. data/ext/{gyro → polyphony}/fiber.c +15 -19
  21. data/ext/{gyro → polyphony}/libev.c +0 -0
  22. data/ext/{gyro → polyphony}/libev.h +0 -0
  23. data/ext/polyphony/libev_agent.c +503 -0
  24. data/ext/polyphony/libev_queue.c +214 -0
  25. data/ext/{gyro/gyro.c → polyphony/polyphony.c} +16 -25
  26. data/ext/polyphony/polyphony.h +90 -0
  27. data/ext/polyphony/polyphony_ext.c +23 -0
  28. data/ext/{gyro → polyphony}/socket.c +14 -14
  29. data/ext/{gyro → polyphony}/thread.c +32 -115
  30. data/ext/{gyro → polyphony}/tracing.c +1 -1
  31. data/lib/polyphony.rb +16 -12
  32. data/lib/polyphony/adapters/irb.rb +1 -1
  33. data/lib/polyphony/adapters/postgres.rb +6 -5
  34. data/lib/polyphony/adapters/process.rb +5 -5
  35. data/lib/polyphony/adapters/trace.rb +28 -28
  36. data/lib/polyphony/core/channel.rb +3 -3
  37. data/lib/polyphony/core/exceptions.rb +1 -1
  38. data/lib/polyphony/core/global_api.rb +11 -9
  39. data/lib/polyphony/core/resource_pool.rb +3 -3
  40. data/lib/polyphony/core/sync.rb +2 -2
  41. data/lib/polyphony/core/thread_pool.rb +6 -6
  42. data/lib/polyphony/core/throttler.rb +13 -6
  43. data/lib/polyphony/event.rb +27 -0
  44. data/lib/polyphony/extensions/core.rb +20 -11
  45. data/lib/polyphony/extensions/fiber.rb +4 -4
  46. data/lib/polyphony/extensions/io.rb +56 -26
  47. data/lib/polyphony/extensions/openssl.rb +4 -8
  48. data/lib/polyphony/extensions/socket.rb +27 -9
  49. data/lib/polyphony/extensions/thread.rb +16 -9
  50. data/lib/polyphony/net.rb +9 -9
  51. data/lib/polyphony/version.rb +1 -1
  52. data/polyphony.gemspec +2 -2
  53. data/test/helper.rb +12 -1
  54. data/test/test_agent.rb +77 -0
  55. data/test/{test_async.rb → test_event.rb} +13 -7
  56. data/test/test_ext.rb +25 -4
  57. data/test/test_fiber.rb +19 -10
  58. data/test/test_global_api.rb +4 -4
  59. data/test/test_io.rb +46 -24
  60. data/test/test_queue.rb +74 -0
  61. data/test/test_signal.rb +3 -40
  62. data/test/test_socket.rb +33 -0
  63. data/test/test_thread.rb +37 -16
  64. data/test/test_trace.rb +6 -5
  65. metadata +24 -24
  66. data/ext/gyro/async.c +0 -132
  67. data/ext/gyro/child.c +0 -108
  68. data/ext/gyro/gyro.h +0 -158
  69. data/ext/gyro/gyro_ext.c +0 -33
  70. data/ext/gyro/io.c +0 -457
  71. data/ext/gyro/queue.c +0 -146
  72. data/ext/gyro/selector.c +0 -205
  73. data/ext/gyro/signal.c +0 -99
  74. data/ext/gyro/timer.c +0 -115
  75. data/test/test_timer.rb +0 -56
@@ -110,7 +110,7 @@ class FiberTest < MiniTest::Test
110
110
  def test_cross_thread_schedule
111
111
  buffer = []
112
112
  worker_fiber = nil
113
- async = Gyro::Async.new
113
+ async = Polyphony::Event.new
114
114
  worker = Thread.new do
115
115
  worker_fiber = Fiber.current
116
116
  async.signal
@@ -125,10 +125,11 @@ class FiberTest < MiniTest::Test
125
125
  assert_equal [:foo], buffer
126
126
  ensure
127
127
  worker&.kill
128
+ worker&.join
128
129
  end
129
130
 
130
131
  def test_ev_loop_anti_starve_mechanism
131
- async = Gyro::Async.new
132
+ async = Polyphony::Event.new
132
133
  t = Thread.new do
133
134
  f = spin_loop { snooze }
134
135
  sleep 0.001
@@ -139,7 +140,8 @@ class FiberTest < MiniTest::Test
139
140
 
140
141
  assert_equal :foo, result
141
142
  ensure
142
- t.kill if t.alive?
143
+ t&.kill
144
+ t&.join
143
145
  end
144
146
 
145
147
  def test_tag
@@ -350,8 +352,7 @@ class FiberTest < MiniTest::Test
350
352
  result = []
351
353
  f = Fiber.current.spin do
352
354
  result << :start
353
- t = Gyro::Timer.new(1, 0)
354
- result << t.await
355
+ result << Thread.current.agent.sleep(1)
355
356
  end
356
357
  snooze
357
358
  f.interrupt
@@ -624,7 +625,7 @@ class FiberTest < MiniTest::Test
624
625
  end
625
626
  end
626
627
  sleep 0.1
627
- f = spin { Gyro::Child.new(pid).await }
628
+ f = spin { Thread.current.agent.waitpid(pid) }
628
629
  o.close
629
630
  Process.kill('INT', pid)
630
631
  f.await
@@ -646,7 +647,7 @@ class FiberTest < MiniTest::Test
646
647
  end
647
648
  end
648
649
  sleep 0.2
649
- f = spin { Gyro::Child.new(pid).await }
650
+ f = spin { Thread.current.agent.waitpid(pid) }
650
651
  o.close
651
652
  Process.kill('TERM', pid)
652
653
  f.await
@@ -673,7 +674,7 @@ class FiberTest < MiniTest::Test
673
674
  sleep 0.2
674
675
  Process.kill('TERM', pid)
675
676
  end
676
- Gyro::Child.new(pid).await
677
+ Thread.current.agent.waitpid(pid)
677
678
  klass = i.read
678
679
  i.close
679
680
  assert_equal 'Polyphony::Terminate', klass
@@ -707,6 +708,7 @@ class MailboxTest < MiniTest::Test
707
708
  f << i
708
709
  sleep 0
709
710
  end
711
+ sleep 0
710
712
 
711
713
  assert_equal [0, 1, 2], msgs
712
714
  ensure
@@ -717,11 +719,11 @@ class MailboxTest < MiniTest::Test
717
719
  msgs = []
718
720
  f = spin { loop { msgs << receive } }
719
721
 
720
- snooze # allow coproc to start
722
+ snooze # allow f to start
721
723
 
722
724
  3.times { |i| f << i }
723
725
 
724
- sleep 0
726
+ sleep 0.01
725
727
 
726
728
  assert_equal [0, 1, 2], msgs
727
729
  ensure
@@ -743,6 +745,7 @@ class MailboxTest < MiniTest::Test
743
745
  def test_cross_thread_send_receive
744
746
  ping_receive_buffer = []
745
747
  pong_receive_buffer = []
748
+
746
749
  pong = Thread.new do
747
750
  sleep 0.05
748
751
  loop do
@@ -763,9 +766,15 @@ class MailboxTest < MiniTest::Test
763
766
 
764
767
  ping.join
765
768
  pong.kill
769
+ ping = pong = nil
766
770
 
767
771
  assert_equal %w{pong pong pong}, ping_receive_buffer
768
772
  assert_equal %w{ping ping ping}, pong_receive_buffer
773
+ ensure
774
+ pong&.kill
775
+ ping&.kill
776
+ pong&.join
777
+ ping&.join
769
778
  end
770
779
 
771
780
  def test_message_queueing
@@ -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
@@ -8,14 +8,20 @@ class ThreadTest < MiniTest::Test
8
8
  buffer = []
9
9
  f = spin { (1..3).each { |i| snooze; buffer << i } }
10
10
  t = Thread.new do
11
+ sleep 0.01
11
12
  s1 = spin { (11..13).each { |i| snooze; buffer << i } }
12
13
  s2 = spin { (21..23).each { |i| snooze; buffer << i } }
14
+ sleep 0.02
13
15
  Fiber.current.await_all_children
14
16
  end
15
17
  f.join
16
18
  t.join
19
+ t = nil
17
20
 
18
21
  assert_equal [1, 2, 3, 11, 12, 13, 21, 22, 23], buffer.sort
22
+ ensure
23
+ t&.kill
24
+ t&.join
19
25
  end
20
26
 
21
27
  def test_thread_join
@@ -24,11 +30,13 @@ class ThreadTest < MiniTest::Test
24
30
  t = Thread.new { sleep 0.01; buffer << 4; :foo }
25
31
 
26
32
  r = t.join
33
+ t = nil
27
34
 
28
- assert_equal [1, 2, 3, 4], buffer
29
35
  assert_equal :foo, r
36
+ assert_equal [1, 2, 3, 4], buffer
30
37
  ensure
31
- t.kill
38
+ t&.kill
39
+ t&.join
32
40
  end
33
41
 
34
42
  def test_thread_join_with_timeout
@@ -37,6 +45,7 @@ class ThreadTest < MiniTest::Test
37
45
  t = Thread.new { sleep 1; buffer << 4 }
38
46
  t0 = Time.now
39
47
  r = t.join(0.01)
48
+ t = nil
40
49
 
41
50
  assert Time.now - t0 < 0.2
42
51
  assert_equal [1, 2, 3], buffer
@@ -44,7 +53,8 @@ class ThreadTest < MiniTest::Test
44
53
  ensure
45
54
  # killing the thread will prevent stopping the sleep timer, as well as the
46
55
  # thread's event selector, leading to a memory leak.
47
- t&.kill if t&.alive?
56
+ t&.kill
57
+ t&.join
48
58
  end
49
59
 
50
60
  def test_thread_await_alias_method
@@ -52,11 +62,13 @@ class ThreadTest < MiniTest::Test
52
62
  spin { (1..3).each { |i| snooze; buffer << i } }
53
63
  t = Thread.new { sleep 0.01; buffer << 4; :foo }
54
64
  r = t.await
65
+ t = nil
55
66
 
56
67
  assert_equal [1, 2, 3, 4], buffer
57
68
  assert_equal :foo, r
58
69
  ensure
59
- t.kill
70
+ t&.kill
71
+ t&.join
60
72
  end
61
73
 
62
74
  def test_join_race_condition_on_thread_spawning
@@ -65,27 +77,33 @@ class ThreadTest < MiniTest::Test
65
77
  :foo
66
78
  end
67
79
  r = t.join
80
+ t = nil
68
81
  assert_equal :foo, r
82
+ ensure
83
+ t&.kill
84
+ t&.join
69
85
  end
70
86
 
71
87
  def test_thread_uncaught_exception_propagation
72
- t = Thread.new do
73
- sleep 1
74
- end
75
- snooze
76
- t.kill
77
- t.await
88
+ ready = Polyphony::Event.new
78
89
 
79
90
  t = Thread.new do
91
+ ready.signal
92
+ sleep 0.01
80
93
  raise 'foo'
81
94
  end
82
95
  e = nil
83
96
  begin
84
- t.await
97
+ ready.await
98
+ r = t.await
85
99
  rescue Exception => e
86
100
  end
101
+ t = nil
87
102
  assert_kind_of RuntimeError, e
88
103
  assert_equal 'foo', e.message
104
+ ensure
105
+ t&.kill
106
+ t&.join
89
107
  end
90
108
 
91
109
  def test_thread_inspect
@@ -102,9 +120,8 @@ class ThreadTest < MiniTest::Test
102
120
  p e
103
121
  puts e.backtrace.join("\n")
104
122
  ensure
105
- t.kill
106
- sleep 0.005
107
- t.join
123
+ t&.kill
124
+ t&.join
108
125
  end
109
126
 
110
127
  def test_that_suspend_returns_immediately_if_no_watchers
@@ -113,14 +130,14 @@ class ThreadTest < MiniTest::Test
113
130
  records << r if r[:event] =~ /^fiber_/
114
131
  end
115
132
  t.enable
116
- Gyro.trace(true)
133
+ Polyphony.trace(true)
117
134
 
118
135
  suspend
119
136
  t.disable
120
137
  assert_equal [:fiber_switchpoint], records.map { |r| r[:event] }
121
138
  ensure
122
139
  t&.disable
123
- Gyro.trace(false)
140
+ Polyphony.trace(false)
124
141
  end
125
142
 
126
143
  def test_thread_child_fiber_termination
@@ -143,7 +160,11 @@ class ThreadTest < MiniTest::Test
143
160
  assert_equal 2, t.main_fiber.children.size
144
161
  t.kill
145
162
  t.join
163
+ t = nil
146
164
 
147
165
  assert_equal [:foo, :bar], buffer
166
+ ensure
167
+ t&.kill
168
+ t&.join
148
169
  end
149
170
  end