polyphony 0.43.1 → 0.43.6

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +1 -1
  3. data/CHANGELOG.md +37 -0
  4. data/Gemfile.lock +2 -2
  5. data/README.md +2 -1
  6. data/docs/_includes/head.html +40 -0
  7. data/docs/_includes/title.html +1 -0
  8. data/docs/_user-guide/web-server.md +11 -11
  9. data/docs/getting-started/overview.md +2 -2
  10. data/docs/index.md +4 -3
  11. data/docs/main-concepts/design-principles.md +23 -34
  12. data/docs/main-concepts/fiber-scheduling.md +1 -1
  13. data/docs/polyphony-logo.png +0 -0
  14. data/examples/adapters/concurrent-ruby.rb +9 -0
  15. data/examples/adapters/redis_blpop.rb +12 -0
  16. data/examples/core/xx-daemon.rb +14 -0
  17. data/examples/io/xx-happy-eyeballs.rb +21 -22
  18. data/examples/io/xx-zip.rb +19 -0
  19. data/examples/performance/mem-usage.rb +34 -28
  20. data/examples/performance/messaging.rb +29 -0
  21. data/examples/performance/multi_snooze.rb +11 -9
  22. data/examples/xx-spin.rb +32 -0
  23. data/ext/polyphony/libev_agent.c +242 -145
  24. data/ext/polyphony/libev_queue.c +129 -57
  25. data/ext/polyphony/polyphony.h +12 -5
  26. data/ext/polyphony/ring_buffer.c +120 -0
  27. data/ext/polyphony/ring_buffer.h +28 -0
  28. data/ext/polyphony/thread.c +13 -7
  29. data/lib/polyphony.rb +29 -10
  30. data/lib/polyphony/adapters/redis.rb +3 -2
  31. data/lib/polyphony/core/global_api.rb +5 -3
  32. data/lib/polyphony/core/resource_pool.rb +19 -9
  33. data/lib/polyphony/core/thread_pool.rb +1 -1
  34. data/lib/polyphony/extensions/core.rb +40 -0
  35. data/lib/polyphony/extensions/fiber.rb +9 -14
  36. data/lib/polyphony/extensions/io.rb +17 -16
  37. data/lib/polyphony/extensions/openssl.rb +8 -0
  38. data/lib/polyphony/extensions/socket.rb +12 -0
  39. data/lib/polyphony/version.rb +1 -1
  40. data/test/q.rb +24 -0
  41. data/test/test_agent.rb +13 -7
  42. data/test/test_fiber.rb +3 -3
  43. data/test/test_global_api.rb +50 -17
  44. data/test/test_io.rb +10 -2
  45. data/test/test_queue.rb +26 -1
  46. data/test/test_resource_pool.rb +12 -0
  47. data/test/test_socket.rb +43 -0
  48. data/test/test_throttler.rb +6 -5
  49. metadata +13 -2
@@ -5,6 +5,16 @@ require 'socket'
5
5
  require_relative './io'
6
6
  require_relative '../core/thread_pool'
7
7
 
8
+ class ::BasicSocket
9
+ def write_nonblock(string, _options = {})
10
+ write(string)
11
+ end
12
+
13
+ def read_nonblock(maxlen, str = nil, _options = {})
14
+ readpartial(maxlen, str)
15
+ end
16
+ end
17
+
8
18
  # Socket overrides (eventually rewritten in C)
9
19
  class ::Socket
10
20
  def accept
@@ -77,6 +87,8 @@ end
77
87
  class ::TCPSocket
78
88
  NO_EXCEPTION = { exception: false }.freeze
79
89
 
90
+ attr_reader :io
91
+
80
92
  def initialize(remote_host, remote_port, local_host = nil, local_port = nil)
81
93
  @io = Socket.new Socket::AF_INET, Socket::SOCK_STREAM
82
94
  if local_host && local_port
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
- VERSION = '0.43.1'
4
+ VERSION = '0.43.6'
5
5
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'fiber'
5
+ require_relative '../lib/polyphony_ext'
6
+
7
+ queue = Polyphony::LibevQueue.new
8
+
9
+ queue.push :a
10
+ queue.push :b
11
+ queue.push :c
12
+ p [queue.shift_no_wait]
13
+ queue.push :d
14
+ p [queue.shift_no_wait]
15
+ p [queue.shift_no_wait]
16
+ p [queue.shift_no_wait]
17
+ p [queue.shift_no_wait]
18
+
19
+ queue.unshift :e
20
+ p [queue.shift_no_wait]
21
+
22
+ queue.push :f
23
+ queue.push :g
24
+ p [queue.shift_no_wait]
@@ -21,10 +21,13 @@ class AgentTest < MiniTest::Test
21
21
  spin {
22
22
  @agent.sleep 0.01
23
23
  count += 1
24
- }
25
- suspend
26
- assert Time.now - t0 >= 0.01
27
- assert_equal 1, count
24
+ @agent.sleep 0.01
25
+ count += 1
26
+ @agent.sleep 0.01
27
+ count += 1
28
+ }.await
29
+ assert Time.now - t0 >= 0.03
30
+ assert_equal 3, count
28
31
  end
29
32
 
30
33
  def test_write_read_partial
@@ -88,10 +91,13 @@ class AgentTest < MiniTest::Test
88
91
  buf << :done
89
92
  end
90
93
 
94
+ # writing always causes snoozing
91
95
  o << 'foo'
92
96
  o << 'bar'
93
97
  o.close
94
- snooze
98
+
99
+ # read_loop will snooze after every read
100
+ 4.times { snooze }
95
101
 
96
102
  assert_equal [:ready, 'foo', 'bar', :done], buf
97
103
  end
@@ -105,12 +111,12 @@ class AgentTest < MiniTest::Test
105
111
  end
106
112
 
107
113
  c1 = TCPSocket.new('127.0.0.1', 1234)
108
- snooze
114
+ 10.times { snooze }
109
115
 
110
116
  assert_equal 1, clients.size
111
117
 
112
118
  c2 = TCPSocket.new('127.0.0.1', 1234)
113
- snooze
119
+ 10.times { snooze }
114
120
 
115
121
  assert_equal 2, clients.size
116
122
 
@@ -482,9 +482,9 @@ class FiberTest < MiniTest::Test
482
482
  def test_select_from_multiple_fibers
483
483
  sleep 0
484
484
  buffer = []
485
- f1 = spin { sleep 0.01; buffer << :foo; :foo }
486
- f2 = spin { sleep 0.03; buffer << :bar; :bar }
487
- f3 = spin { sleep 0.05; buffer << :baz; :baz }
485
+ f1 = spin { sleep 0.1; buffer << :foo; :foo }
486
+ f2 = spin { sleep 0.3; buffer << :bar; :bar }
487
+ f3 = spin { sleep 0.5; buffer << :baz; :baz }
488
488
 
489
489
  selected, result = Fiber.select(f1, f2, f3)
490
490
  assert_equal :foo, result
@@ -106,7 +106,7 @@ class MoveOnAfterTest < MiniTest::Test
106
106
  end
107
107
  t1 = Time.now
108
108
 
109
- assert t1 - t0 < 0.03
109
+ assert t1 - t0 < 0.1
110
110
  assert_nil v
111
111
  end
112
112
 
@@ -118,7 +118,7 @@ class MoveOnAfterTest < MiniTest::Test
118
118
  end
119
119
  t1 = Time.now
120
120
 
121
- assert t1 - t0 < 0.02
121
+ assert t1 - t0 < 0.1
122
122
  assert_equal :bar, v
123
123
  end
124
124
 
@@ -129,7 +129,7 @@ class MoveOnAfterTest < MiniTest::Test
129
129
  assert_equal Fiber.current, f.parent
130
130
  v = sleep 1
131
131
  t1 = Time.now
132
- assert t1 - t0 < 0.02
132
+ assert t1 - t0 < 0.1
133
133
  assert_equal 'foo', v
134
134
  end
135
135
  end
@@ -145,7 +145,7 @@ class CancelAfterTest < MiniTest::Test
145
145
  end
146
146
  end
147
147
  t1 = Time.now
148
- assert t1 - t0 < 0.02
148
+ assert t1 - t0 < 0.1
149
149
  end
150
150
 
151
151
  def test_cancel_after_without_block
@@ -157,7 +157,31 @@ class CancelAfterTest < MiniTest::Test
157
157
  sleep 1
158
158
  end
159
159
  t1 = Time.now
160
- assert t1 - t0 < 0.02
160
+ assert t1 - t0 < 0.1
161
+ end
162
+
163
+ class CustomException < Exception
164
+ end
165
+
166
+ def test_cancel_after_with_custom_exception
167
+ assert_raises CustomException do
168
+ cancel_after(0.01, with_exception: CustomException) do
169
+ sleep 1
170
+ :foo
171
+ end
172
+ end
173
+
174
+ begin
175
+ e = nil
176
+ cancel_after(0.01, with_exception: 'foo') do
177
+ sleep 1
178
+ :foo
179
+ end
180
+ rescue => e
181
+ ensure
182
+ assert_kind_of RuntimeError, e
183
+ assert_equal 'foo', e.message
184
+ end
161
185
  end
162
186
  end
163
187
 
@@ -187,13 +211,13 @@ class SpinLoopTest < MiniTest::Test
187
211
 
188
212
  def test_spin_loop_location
189
213
  location = /^#{__FILE__}:#{__LINE__ + 1}/
190
- f = spin_loop {}
214
+ f = spin_loop { snooze }
191
215
 
192
216
  assert_match location, f.location
193
217
  end
194
218
 
195
219
  def test_spin_loop_tag
196
- f = spin_loop(:my_loop) {}
220
+ f = spin_loop(:my_loop) { snooze }
197
221
 
198
222
  assert_equal :my_loop, f.tag
199
223
  end
@@ -201,10 +225,13 @@ class SpinLoopTest < MiniTest::Test
201
225
  def test_spin_loop_with_rate
202
226
  buffer = []
203
227
  counter = 0
204
- f = spin_loop(rate: 50) { buffer << (counter += 1) }
228
+ t0 = Time.now
229
+ f = spin_loop(rate: 10) { buffer << (counter += 1) }
205
230
  sleep 0.2
206
231
  f.stop
207
- assert counter >= 9 && counter <= 11
232
+ elapsed = Time.now - t0
233
+ expected = (elapsed * 10).to_i
234
+ assert counter >= expected - 1 && counter <= expected + 1
208
235
  end
209
236
  end
210
237
 
@@ -212,12 +239,15 @@ class ThrottledLoopTest < MiniTest::Test
212
239
  def test_throttled_loop
213
240
  buffer = []
214
241
  counter = 0
242
+ t0 = Time.now
215
243
  f = spin do
216
- throttled_loop(50) { buffer << (counter += 1) }
244
+ throttled_loop(10) { buffer << (counter += 1) }
217
245
  end
218
- sleep 0.2
246
+ sleep 0.3
219
247
  f.stop
220
- assert counter >= 9 && counter <= 11
248
+ elapsed = Time.now - t0
249
+ expected = (elapsed * 10).to_i
250
+ assert counter >= expected - 1 && counter <= expected + 1
221
251
  end
222
252
 
223
253
  def test_throttled_loop_with_count
@@ -243,19 +273,22 @@ class GlobalAPIEtcTest < MiniTest::Test
243
273
 
244
274
  def test_every
245
275
  buffer = []
276
+ t0 = Time.now
246
277
  f = spin do
247
- every(0.01) { buffer << 1 }
278
+ every(0.1) { buffer << 1 }
248
279
  end
249
- sleep 0.05
280
+ sleep 0.5
250
281
  f.stop
251
- assert (4..5).include?(buffer.size)
282
+ elapsed = Time.now - t0
283
+ expected = (elapsed / 0.1).to_i
284
+ assert buffer.size >= expected - 2 && buffer.size <= expected + 2
252
285
  end
253
286
 
254
287
  def test_sleep
255
288
  t0 = Time.now
256
- sleep 0.05
289
+ sleep 0.1
257
290
  elapsed = Time.now - t0
258
- assert (0.045..0.08).include? elapsed
291
+ assert (0.05..0.15).include? elapsed
259
292
 
260
293
  f = spin { sleep }
261
294
  snooze
@@ -30,6 +30,14 @@ class IOTest < MiniTest::Test
30
30
  assert_equal 'hello', msg
31
31
  end
32
32
 
33
+ def test_write_multiple_arguments
34
+ i, o = IO.pipe
35
+ count = o.write('a', 'b', "\n", 'c')
36
+ assert_equal 4, count
37
+ o.close
38
+ assert_equal "ab\nc", i.read
39
+ end
40
+
33
41
  def test_that_double_chevron_method_returns_io
34
42
  assert_equal @o, @o << 'foo'
35
43
 
@@ -121,7 +129,7 @@ class IOClassMethodsTest < MiniTest::Test
121
129
  assert_equal "end\n", lines[-1]
122
130
  end
123
131
 
124
- def test_read
132
+ def test_read_class_method
125
133
  s = IO.read(__FILE__)
126
134
  assert_kind_of String, s
127
135
  assert(!s.empty?)
@@ -144,7 +152,7 @@ class IOClassMethodsTest < MiniTest::Test
144
152
 
145
153
  WRITE_DATA = "foo\nbar קוקו"
146
154
 
147
- def test_write
155
+ def test_write_class_method
148
156
  fn = '/tmp/test_write'
149
157
  FileUtils.rm(fn) rescue nil
150
158
 
@@ -8,7 +8,7 @@ class QueueTest < MiniTest::Test
8
8
  @queue = Polyphony::Queue.new
9
9
  end
10
10
 
11
- def test_pop
11
+ def test_push_shift
12
12
  spin {
13
13
  @queue << 42
14
14
  }
@@ -21,6 +21,18 @@ class QueueTest < MiniTest::Test
21
21
  assert_equal [1, 2, 3, 4], buf
22
22
  end
23
23
 
24
+ def test_unshift
25
+ @queue.push 1
26
+ @queue.push 2
27
+ @queue.push 3
28
+ @queue.unshift 4
29
+
30
+ buf = []
31
+ buf << @queue.shift while !@queue.empty?
32
+
33
+ assert_equal [4, 1, 2, 3], buf
34
+ end
35
+
24
36
  def test_multiple_waiters
25
37
  a = spin { @queue.shift }
26
38
  b = spin { @queue.shift }
@@ -41,6 +53,19 @@ class QueueTest < MiniTest::Test
41
53
  buf = []
42
54
  @queue.shift_each { |i| buf << i }
43
55
  assert_equal [1, 2, 3, 4], buf
56
+
57
+ buf = []
58
+ @queue.shift_each { |i| buf << i }
59
+ assert_equal [], buf
60
+ end
61
+
62
+ def test_shift_all
63
+ (1..4).each { |i| @queue << i }
64
+ buf = @queue.shift_all
65
+ assert_equal [1, 2, 3, 4], buf
66
+
67
+ buf = @queue.shift_all
68
+ assert_equal [], buf
44
69
  end
45
70
 
46
71
  def test_empty?
@@ -123,4 +123,16 @@ class ResourcePoolTest < MiniTest::Test
123
123
  pool.preheat!
124
124
  assert_equal 2, pool.size
125
125
  end
126
+
127
+ def test_reentrant_resource_pool
128
+ resources = [+'a', +'b']
129
+ pool = Polyphony::ResourcePool.new(limit: 1) { resources.shift }
130
+
131
+ pool.acquire do |r|
132
+ assert_equal 'a', r
133
+ pool.acquire do |r|
134
+ assert_equal 'a', r
135
+ end
136
+ end
137
+ end
126
138
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'helper'
4
+ require 'localhost/authority'
4
5
 
5
6
  class SocketTest < MiniTest::Test
6
7
  def setup
@@ -31,4 +32,46 @@ class SocketTest < MiniTest::Test
31
32
  server_fiber&.await
32
33
  server&.close
33
34
  end
35
+
36
+ def test_openssl
37
+ authority = Localhost::Authority.fetch
38
+ context = authority.server_context
39
+ port = rand(1234..5678)
40
+ sock = TCPServer.new('127.0.0.1', port)
41
+ server = OpenSSL::SSL::SSLServer.new(sock, context)
42
+
43
+ server_fiber = spin do
44
+ while (socket = server.accept)
45
+ puts "accepted: #{socket.inspect}"
46
+ spin do
47
+ while (data = socket.gets(8192))
48
+ socket << data
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ snooze
55
+ p 1
56
+ sock = TCPSocket.new('127.0.0.1', port)
57
+ p 2
58
+ sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
59
+ context = OpenSSL::SSL::SSLContext.new
60
+ context.verify_mode = OpenSSL::SSL::VERIFY_NONE
61
+ client = OpenSSL::SSL::SSLSocket.new(sock, context)
62
+ client.sync_close = true
63
+ client.hostname = 'localhost'
64
+ p 3
65
+ client.connect
66
+ p 4
67
+ client.write("GET / HTTP/1.0\r\nHost: realiteq.net\r\n\r\n")#("1234\n")
68
+ p 5
69
+ assert_equal "1234\n", client.readpartial(8192)
70
+ p 6
71
+ client.close
72
+ ensure
73
+ server_fiber&.stop
74
+ server_fiber&.await
75
+ server&.close
76
+ end
34
77
  end
@@ -4,14 +4,15 @@ require_relative 'helper'
4
4
 
5
5
  class ThrottlerTest < MiniTest::Test
6
6
  def test_throttler_with_rate
7
- t = Polyphony::Throttler.new(100)
7
+ t = Polyphony::Throttler.new(10)
8
8
  buffer = []
9
+ t0 = Time.now
9
10
  f = spin { loop { t.process { buffer << 1 } } }
10
- sleep 0.02
11
+ sleep 0.2
11
12
  f.stop
12
- snooze
13
- assert buffer.size >= 2
14
- assert buffer.size <= 3
13
+ elapsed = Time.now - t0
14
+ expected = (elapsed * 10).to_i
15
+ assert buffer.size >= expected - 1 && buffer.size <= expected + 1
15
16
  ensure
16
17
  t.stop
17
18
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: polyphony
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.43.1
4
+ version: 0.43.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-06 00:00:00.000000000 Z
11
+ date: 2020-07-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httparty
@@ -242,6 +242,8 @@ files:
242
242
  - TODO.md
243
243
  - bin/polyphony-debug
244
244
  - docs/_config.yml
245
+ - docs/_includes/head.html
246
+ - docs/_includes/title.html
245
247
  - docs/_sass/custom/custom.scss
246
248
  - docs/_sass/overrides.scss
247
249
  - docs/_user-guide/all-about-timers.md
@@ -280,10 +282,12 @@ files:
280
282
  - docs/main-concepts/fiber-scheduling.md
281
283
  - docs/main-concepts/index.md
282
284
  - docs/polyphony-logo.png
285
+ - examples/adapters/concurrent-ruby.rb
283
286
  - examples/adapters/pg_client.rb
284
287
  - examples/adapters/pg_notify.rb
285
288
  - examples/adapters/pg_pool.rb
286
289
  - examples/adapters/pg_transaction.rb
290
+ - examples/adapters/redis_blpop.rb
287
291
  - examples/adapters/redis_channels.rb
288
292
  - examples/adapters/redis_client.rb
289
293
  - examples/adapters/redis_pubsub.rb
@@ -296,6 +300,7 @@ files:
296
300
  - examples/core/xx-at_exit.rb
297
301
  - examples/core/xx-caller.rb
298
302
  - examples/core/xx-channels.rb
303
+ - examples/core/xx-daemon.rb
299
304
  - examples/core/xx-deadlock.rb
300
305
  - examples/core/xx-deferring-an-operation.rb
301
306
  - examples/core/xx-erlang-style-genserver.rb
@@ -348,8 +353,10 @@ files:
348
353
  - examples/io/xx-system.rb
349
354
  - examples/io/xx-tcpserver.rb
350
355
  - examples/io/xx-tcpsocket.rb
356
+ - examples/io/xx-zip.rb
351
357
  - examples/performance/fs_read.rb
352
358
  - examples/performance/mem-usage.rb
359
+ - examples/performance/messaging.rb
353
360
  - examples/performance/multi_snooze.rb
354
361
  - examples/performance/snooze.rb
355
362
  - examples/performance/snooze_raw.rb
@@ -363,6 +370,7 @@ files:
363
370
  - examples/performance/xx-array.rb
364
371
  - examples/performance/xx-fiber-switch.rb
365
372
  - examples/performance/xx-snooze.rb
373
+ - examples/xx-spin.rb
366
374
  - ext/libev/Changes
367
375
  - ext/libev/LICENSE
368
376
  - ext/libev/README
@@ -388,6 +396,8 @@ files:
388
396
  - ext/polyphony/polyphony.c
389
397
  - ext/polyphony/polyphony.h
390
398
  - ext/polyphony/polyphony_ext.c
399
+ - ext/polyphony/ring_buffer.c
400
+ - ext/polyphony/ring_buffer.h
391
401
  - ext/polyphony/thread.c
392
402
  - ext/polyphony/tracing.c
393
403
  - lib/polyphony.rb
@@ -417,6 +427,7 @@ files:
417
427
  - test/coverage.rb
418
428
  - test/eg.rb
419
429
  - test/helper.rb
430
+ - test/q.rb
420
431
  - test/run.rb
421
432
  - test/stress.rb
422
433
  - test/test_agent.rb