polyphony 0.43.5 → 0.43.11

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/CHANGELOG.md +45 -0
  3. data/Gemfile.lock +1 -1
  4. data/README.md +21 -4
  5. data/TODO.md +0 -7
  6. data/bin/stress.rb +28 -0
  7. data/docs/_includes/head.html +40 -0
  8. data/docs/_includes/title.html +1 -0
  9. data/docs/_user-guide/web-server.md +11 -11
  10. data/docs/getting-started/overview.md +2 -2
  11. data/docs/index.md +3 -1
  12. data/docs/polyphony-logo.png +0 -0
  13. data/examples/core/xx-channels.rb +4 -2
  14. data/examples/core/xx-using-a-mutex.rb +2 -1
  15. data/examples/io/xx-happy-eyeballs.rb +21 -22
  16. data/examples/io/xx-zip.rb +19 -0
  17. data/examples/performance/fiber_transfer.rb +47 -0
  18. data/examples/xx-spin.rb +32 -0
  19. data/ext/polyphony/agent.h +41 -0
  20. data/ext/polyphony/event.c +86 -0
  21. data/ext/polyphony/fiber.c +0 -5
  22. data/ext/polyphony/libev_agent.c +277 -135
  23. data/ext/polyphony/polyphony.c +2 -2
  24. data/ext/polyphony/polyphony.h +14 -21
  25. data/ext/polyphony/polyphony_ext.c +4 -2
  26. data/ext/polyphony/queue.c +208 -0
  27. data/ext/polyphony/ring_buffer.c +0 -24
  28. data/ext/polyphony/thread.c +42 -31
  29. data/lib/polyphony.rb +6 -7
  30. data/lib/polyphony/core/channel.rb +3 -34
  31. data/lib/polyphony/core/resource_pool.rb +13 -75
  32. data/lib/polyphony/core/sync.rb +12 -9
  33. data/lib/polyphony/extensions/fiber.rb +8 -8
  34. data/lib/polyphony/extensions/openssl.rb +8 -0
  35. data/lib/polyphony/extensions/socket.rb +11 -9
  36. data/lib/polyphony/extensions/thread.rb +1 -1
  37. data/lib/polyphony/net.rb +2 -1
  38. data/lib/polyphony/version.rb +1 -1
  39. data/test/helper.rb +2 -2
  40. data/test/test_agent.rb +2 -2
  41. data/test/test_event.rb +12 -0
  42. data/test/test_fiber.rb +1 -1
  43. data/test/test_io.rb +14 -0
  44. data/test/test_queue.rb +33 -0
  45. data/test/test_resource_pool.rb +24 -58
  46. data/test/test_trace.rb +18 -17
  47. metadata +12 -5
  48. data/ext/polyphony/libev_queue.c +0 -288
  49. data/lib/polyphony/event.rb +0 -27
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 55e44a81e7358528fabd37e1d48b6387957bc6001e4d947bf6c881d2425953d9
4
- data.tar.gz: 45af7e485891927a1e9f88e8a6342692c25f306aa60e40fa8a74c0ca8494d3d4
3
+ metadata.gz: 5d1eb9e2b1eef7f180107b3952fc6974e9c816268ebd8d2368ddfaac276d2086
4
+ data.tar.gz: dafb382dc7606b92b97fb98125b57abc72e1b1d6b07bb877abff608a41ee51a1
5
5
  SHA512:
6
- metadata.gz: 444675baf3131ffc843b61649b92ce5ca7d77773277db750d8c8ac80a88ccd133e6f60d61282fa38ffb91de59eaf2304ea9b0fca196a7eb61c7a53becaa1fe46
7
- data.tar.gz: f21e26a41c8dfeea0d803b5b9bc82b1e648bf0ca588225f721560c432f363020b37561c59daeefbe2d434b012f1f5f767460557939a22d04e78e40471813a8ca
6
+ metadata.gz: 9f91b787b3eec2c19f6a02fd8a77469c8ae5d7a9302799c3e2a5957376b9f4b0135d2cd958ec7dcf326701e8ba81d85a28201a140ce5806ccc057cedafe907d5
7
+ data.tar.gz: df865277e2f37c0d7fde0ffd38f52fc466bf28033fb968bd012af7455df9e7805989152467e1e8e84719194f173c99c6290496987ad8a2b48fe26076e62c85bd
@@ -1,3 +1,48 @@
1
+ ## 0.43.11 2020-07-24
2
+
3
+ * Dump uncaught exception info for forked process (#36)
4
+ * Add additional socket config options (#37)
5
+ - :reuse_port (`SO_REUSEPORT`)
6
+ - :backlog (listen backlog, default `SOMAXCONN`)
7
+ * Fix possible race condition in Queue#shift (#34)
8
+
9
+ ## 0.43.10 2020-07-23
10
+
11
+ * Fix race condition when terminating fibers (#33)
12
+ * Fix lock release in `Mutex` (#32)
13
+ * Virtualize agent interface
14
+ * Implement `LibevAgent_connect`
15
+
16
+ ## 0.43.9 2020-07-22
17
+
18
+ * Rewrite `Channel` using `Queue`
19
+ * Rewrite `Mutex` using `Queue`
20
+ * Reimplement `Event` in C to prevent cross-thread race condition
21
+ * Reimplement `ResourcePool` using `Queue`
22
+ * Implement `Queue#size`
23
+
24
+ ## 0.43.8 2020-07-21
25
+
26
+ * Rename `LibevQueue` to `Queue`
27
+ * Reimplement Event using `Agent#wait_event`
28
+ * Improve Queue shift queue performance
29
+ * Introduce `Agent#wait_event` API for waiting on asynchronous events
30
+ * Minimize `fcntl` syscalls in IO operations
31
+
32
+ ## 0.43.7 2020-07-20
33
+
34
+ * Fix memory leak in ResourcePool (#31)
35
+ * Check and adjust file position before reading (#30)
36
+ * Minor documentation fixes
37
+
38
+ ## 0.43.6 2020-07-18
39
+
40
+ * Allow brute-force interrupting with second Ctrl-C
41
+ * Fix outgoing SSL connections (#28)
42
+ * Improve Fiber#await_all_children with many children
43
+ * Use `writev` for writing multiple strings
44
+ * Add logo (thanks [Gerald](https://webocube.com/)!)
45
+
1
46
  ## 0.43.5 2020-07-13
2
47
 
3
48
  * Fix `#read_nonblock`, `#write_nonblock` for `IO` and `Socket` (#27)
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polyphony (0.43.5)
4
+ polyphony (0.43.11)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -1,8 +1,25 @@
1
- # Polyphony - Fine-Grained Concurrency for Ruby
1
+ <h1 align="center">
2
+ <a href="https://digital-fabric.github.io/polyphony/">
3
+ <img src="docs/polyphony-logo.png" alt="Polyphony">
4
+ </a>
5
+ <br>
6
+ Polyphony
7
+ <br>
8
+ </h1>
2
9
 
3
- [![Gem Version](https://badge.fury.io/rb/polyphony.svg)](http://rubygems.org/gems/polyphony)
4
- [![Modulation Test](https://github.com/digital-fabric/polyphony/workflows/Tests/badge.svg)](https://github.com/digital-fabric/polyphony/actions?query=workflow%3ATests)
5
- [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/digital-fabric/polyphony/blob/master/LICENSE)
10
+ <h4 align="center">Fine-Grained Concurrency for Ruby</h4>
11
+
12
+ <p align="center">
13
+ <a href="http://rubygems.org/gems/polyphony">
14
+ <img src="https://badge.fury.io/rb/polyphony.svg" alt="Ruby gem">
15
+ </a>
16
+ <a href="https://github.com/digital-fabric/polyphony/actions?query=workflow%3ATests">
17
+ <img src="https://github.com/digital-fabric/polyphony/workflows/Tests/badge.svg" alt="Tests">
18
+ </a>
19
+ <a href="https://github.com/digital-fabric/polyphony/blob/master/LICENSE">
20
+ <img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT License">
21
+ </a>
22
+ </p>
6
23
 
7
24
  [DOCS](https://digital-fabric.github.io/polyphony/) |
8
25
  [EXAMPLES](examples)
data/TODO.md CHANGED
@@ -1,10 +1,3 @@
1
- ## 0.43
2
-
3
- - Reimplement ResourcePool, Channel, Mutex using LibevQueue
4
- -- Add `Fiber#schedule_with_priority` method, aliased by `Fiber#wakeup`
5
- - Implement agent interface is virtual function table
6
- - Implement proxy agent for plugging in a user-provided agent class
7
-
8
1
  - Debugging
9
2
  - Eat your own dogfood: need a good tool to check what's going on when some
10
3
  test fails
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ `rake recompile`
5
+
6
+ count = ARGV[0] ? ARGV[0].to_i : 100
7
+
8
+ TEST_CMD = 'ruby test/run.rb'
9
+
10
+ def run_test(count)
11
+ puts "#{count}: running tests..."
12
+ system(TEST_CMD)
13
+ return if $?.exitstatus == 0
14
+
15
+ puts "Failure after #{count} tests"
16
+ exit!
17
+ end
18
+
19
+ trap('INT') { exit! }
20
+ t0 = Time.now
21
+ count.times { |i| run_test(i + 1) }
22
+ elapsed = Time.now - t0
23
+ puts format(
24
+ "Successfully ran %d tests in %f seconds (%f per test)",
25
+ count,
26
+ elapsed,
27
+ elapsed / count
28
+ )
@@ -0,0 +1,40 @@
1
+ <head>
2
+ <meta charset="UTF-8">
3
+ <meta http-equiv="X-UA-Compatible" content="IE=Edge">
4
+
5
+ {% unless site.plugins contains "jekyll-seo-tag" %}
6
+ <title>{{ page.title }} - {{ site.title }}</title>
7
+
8
+ {% if page.description %}
9
+ <meta name="Description" content="{{ page.description }}">
10
+ {% endif %}
11
+ {% endunless %}
12
+
13
+ <link rel="shortcut icon" href="{{ 'polyphony-logo.png' | absolute_url }}" type="image/png">
14
+
15
+ <link rel="stylesheet" href="{{ '/assets/css/just-the-docs-default.css' | absolute_url }}">
16
+
17
+ {% if site.ga_tracking != nil %}
18
+ <script async src="https://www.googletagmanager.com/gtag/js?id={{ site.ga_tracking }}"></script>
19
+ <script>
20
+ window.dataLayer = window.dataLayer || [];
21
+ function gtag(){dataLayer.push(arguments);}
22
+ gtag('js', new Date());
23
+
24
+ gtag('config', '{{ site.ga_tracking }}'{% unless site.ga_tracking_anonymize_ip == nil %}, { 'anonymize_ip': true }{% endunless %});
25
+ </script>
26
+
27
+ {% endif %}
28
+
29
+ {% if site.search_enabled != false %}
30
+ <script type="text/javascript" src="{{ '/assets/js/vendor/lunr.min.js' | absolute_url }}"></script>
31
+ {% endif %}
32
+ <script type="text/javascript" src="{{ '/assets/js/just-the-docs.js' | absolute_url }}"></script>
33
+
34
+ <meta name="viewport" content="width=device-width, initial-scale=1">
35
+
36
+ {% seo %}
37
+
38
+ {% include head_custom.html %}
39
+
40
+ </head>
@@ -0,0 +1 @@
1
+ <img src="{{ 'polyphony-logo.png' | absolute_url }}" style="height: 1.5em; margin-right: 0.5em">Polyphony
@@ -26,9 +26,9 @@ the entire request body.
26
26
  ## A basic web server
27
27
 
28
28
  ```ruby
29
- require 'polyphony/http'
29
+ require 'tipi'
30
30
 
31
- Polyphony::HTTP::Server.serve('0.0.0.0', 1234) do |request|
31
+ Tipi.serve('0.0.0.0', 1234) do |request|
32
32
  request.respond("Hello world!\n")
33
33
  end
34
34
  ```
@@ -55,13 +55,13 @@ TLS termination can be handled by passing a `secure_context` option to the
55
55
  server:
56
56
 
57
57
  ```ruby
58
- require 'polyphony/http'
58
+ require 'tipi'
59
59
  require 'localhost/authority'
60
60
 
61
61
  authority = Localhost::Authority.fetch
62
62
  opts = { secure_context: authority.server_context }
63
63
 
64
- Polyphony::HTTP::Server.serve('0.0.0.0', 1234, opts) do |request|
64
+ Tipi.serve('0.0.0.0', 1234, opts) do |request|
65
65
  request.respond("Hello world!\n")
66
66
  end
67
67
  ```
@@ -72,8 +72,8 @@ Polyphony's web server makes it really easy to integrate websocket communication
72
72
  with normal HTTP processing:
73
73
 
74
74
  ```ruby
75
- require 'polyphony/http'
76
- require 'polyphony/websocket'
75
+ require 'tipi'
76
+ require 'tipi/websocket'
77
77
 
78
78
  ws_handler = Polyphony::Websocket.handler do |ws|
79
79
  while (msg = ws.recv)
@@ -85,7 +85,7 @@ opts = {
85
85
  upgrade: { websocket: ws_handler }
86
86
  }
87
87
 
88
- Polyphony::HTTP::Server.serve('0.0.0.0', 1234, opts) do |request|
88
+ Tipi.serve('0.0.0.0', 1234, opts) do |request|
89
89
  request.respond("Hello world!\n")
90
90
  end
91
91
  ```
@@ -93,7 +93,7 @@ end
93
93
  Polyphony also supports general-purpose HTTP upgrades using the same mechanism:
94
94
 
95
95
  ```ruby
96
- require 'polyphony/http'
96
+ require 'tipi'
97
97
 
98
98
  opts = {
99
99
  upgrade: {
@@ -105,7 +105,7 @@ opts = {
105
105
  }
106
106
  }
107
107
 
108
- Polyphony::HTTP::Server.serve('0.0.0.0', 1234, opts) do |request|
108
+ Tipi.serve('0.0.0.0', 1234, opts) do |request|
109
109
  request.respond("Hello world!\n")
110
110
  end
111
111
  ```
@@ -117,7 +117,7 @@ and enables streaming (using chunked encoding for HTTP/1.1 connections). Here's
117
117
  an example of an SSE response:
118
118
 
119
119
  ```ruby
120
- require 'polyphony/http'
120
+ require 'tipi'
121
121
 
122
122
  def sse_response(request)
123
123
  request.send_headers('Content-Type': 'text/event-stream')
@@ -131,6 +131,6 @@ ensure
131
131
  request.send_chunk("retry: 0\n\n", done: true)
132
132
  end
133
133
 
134
- Polyphony::HTTP::Server.serve('0.0.0.0', 1234, &method(:sse_response))
134
+ Tipi.serve('0.0.0.0', 1234, &method(:sse_response))
135
135
  ```
136
136
 
@@ -113,8 +113,8 @@ active concurrent connections, each advancing at its own pace, consuming only a
113
113
  single CPU core.
114
114
 
115
115
  Nevertheless, Polyphony fully supports multithreading, with each thread having
116
- its own fiber run queue and its own libev event loop. In addition, Polyphony
117
- enables cross-thread communication using
116
+ its own fiber run queue and its own libev event loop. Polyphony even enables
117
+ cross-thread communication using [fiber messaging](#message-passing).
118
118
 
119
119
  ## Fibers vs Callbacks
120
120
 
@@ -6,6 +6,8 @@ permalink: /
6
6
  next_title: Installing Polyphony
7
7
  ---
8
8
 
9
+ <p align="center"><img src="{{ 'polyphony-logo.png' | absolute_url }}" /></p>
10
+
9
11
  # Polyphony
10
12
  {:.text-center .logo-title}
11
13
 
@@ -21,7 +23,7 @@ for I/O, timers, and other asynchronous events.
21
23
  [Overview](getting-started/overview){: .btn .btn-green .text-gamma }
22
24
  [Take the tutorial](getting-started/tutorial){: .btn .btn-blue .text-gamma }
23
25
  [Source code](https://github.com/digital-fabric/polyphony){: .btn .btn-purple .text-gamma target="_blank" }
24
- {: .mt-6 .h-align-center }
26
+ {:.text-center .mt-6 .h-align-center }
25
27
 
26
28
  ## Focused on Developer Happiness
27
29
 
Binary file
@@ -2,10 +2,12 @@
2
2
 
3
3
  require 'bundler/setup'
4
4
  require 'polyphony'
5
+ require 'polyphony/core/channel'
5
6
 
6
7
  def echo(cin, cout)
7
8
  puts 'start echoer'
8
9
  while (msg = cin.receive)
10
+ puts "echoer received #{msg}"
9
11
  cout << "you said: #{msg}"
10
12
  end
11
13
  ensure
@@ -20,7 +22,7 @@ spin do
20
22
  puts 'start receiver'
21
23
  while (msg = chan2.receive)
22
24
  puts msg
23
- $main.resume if msg =~ /world/
25
+ $main.schedule if msg =~ /world/
24
26
  end
25
27
  ensure
26
28
  puts 'receiver stopped'
@@ -42,4 +44,4 @@ $main = spin do
42
44
  puts "done #{Time.now - t0}"
43
45
  end
44
46
 
45
- suspend
47
+ $main.await
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'bundler/setup'
4
4
  require 'polyphony'
5
+ require 'polyphony/core/sync'
5
6
 
6
7
  def loop_it(number, lock)
7
8
  loop do
@@ -13,7 +14,7 @@ def loop_it(number, lock)
13
14
  end
14
15
  end
15
16
 
16
- lock = Polyphony::Sync::Mutex.new
17
+ lock = Polyphony::Mutex.new
17
18
  spin { loop_it(1, lock) }
18
19
  spin { loop_it(2, lock) }
19
20
  spin { loop_it(3, lock) }
@@ -1,37 +1,36 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # idea taken from the example given in trio:
4
- # https://www.youtube.com/watch?v=oLkfnc_UMcE
5
-
6
- require 'bundler/setup'
7
3
  require 'polyphony'
8
4
 
9
- def try_connect(target, supervisor)
10
- puts "trying #{target[2]}"
11
- socket = Polyphony::Net.tcp_connect(target[2], 80)
12
- # connection successful
13
- supervisor.stop([target[2], socket])
5
+ def try_connect(ip_address, port, supervisor)
6
+ puts "trying #{ip_address}"
7
+ sleep rand * 0.2
8
+ socket = TCPSocket.new(ip_address, port)
9
+ puts "connected to #{ip_address}"
10
+ supervisor.schedule [ip_address, socket]
14
11
  rescue IOError, SystemCallError
15
12
  # ignore error
16
13
  end
17
14
 
18
- def happy_eyeballs(hostname, port, max_wait_time: 0.025)
15
+ def happy_eyeballs(hostname, port, max_wait_time: 0.010)
19
16
  targets = Socket.getaddrinfo(hostname, port, :INET, :STREAM)
20
17
  t0 = Time.now
21
- cancel_after(5) do
22
- success = supervise do |supervisor|
23
- targets.each_with_index do |t, idx|
24
- sleep(max_wait_time) if idx > 0
25
- supervisor.spin { try_connect(t, supervisor) }
26
- end
27
- end
28
- if success
29
- puts format('success: %s (%.3fs)', success[0], Time.now - t0)
30
- else
31
- puts "timed out (#{Time.now - t0}s)"
18
+ fibers = []
19
+ supervisor = Fiber.current
20
+ spin do
21
+ targets.each do |t|
22
+ spin { try_connect(t[2], t[1], supervisor) }
23
+ sleep(max_wait_time)
32
24
  end
25
+ suspend
26
+ end
27
+ target, socket = move_on_after(5) { suspend }
28
+ supervisor.shutdown_all_children
29
+ if target
30
+ puts format('success: %s (%.3fs)', target, Time.now - t0)
31
+ else
32
+ puts 'timed out'
33
33
  end
34
34
  end
35
35
 
36
- # Let's try it out:
37
36
  happy_eyeballs('debian.org', 'https')
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+ require 'zlib'
6
+
7
+ i, o = IO.pipe
8
+
9
+ w = Zlib::GzipWriter.new(o)
10
+
11
+ s = (1..1000).map { (65 + rand(26)).chr }.join
12
+ puts "full length: #{s.bytesize}"
13
+ w << s
14
+ w.close
15
+ o.close
16
+
17
+
18
+ z = i.read
19
+ puts "zipped length: #{z.bytesize}"
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fiber'
4
+
5
+ class Fiber
6
+ attr_accessor :next
7
+ end
8
+
9
+ # This program shows how the performance
10
+
11
+ def run(num_fibers)
12
+ count = 0
13
+
14
+ GC.disable
15
+
16
+ first = nil
17
+ last = nil
18
+ supervisor = Fiber.current
19
+ num_fibers.times do
20
+ fiber = Fiber.new do
21
+ loop do
22
+ count += 1
23
+ if count == 1_000_000
24
+ supervisor.transfer
25
+ else
26
+ Fiber.current.next.transfer
27
+ end
28
+ end
29
+ end
30
+ first ||= fiber
31
+ last.next = fiber if last
32
+ last = fiber
33
+ end
34
+
35
+ last.next = first
36
+
37
+ t0 = Time.now
38
+ first.transfer
39
+ elapsed = Time.now - t0
40
+
41
+ puts "fibers: #{num_fibers} count: #{count} rate: #{count / elapsed}"
42
+ GC.start
43
+ end
44
+
45
+ run(100)
46
+ run(1000)
47
+ run(10000)