polyphony 0.43.5 → 0.43.11

Sign up to get free protection for your applications and to get access to all the features.
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)