polyphony 0.19 → 0.20

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 (186) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.rubocop.yml +87 -1
  4. data/CHANGELOG.md +35 -0
  5. data/Gemfile.lock +17 -6
  6. data/README.md +200 -139
  7. data/Rakefile +4 -4
  8. data/TODO.md +35 -7
  9. data/bin/poly +11 -0
  10. data/docs/getting-started/getting-started.md +1 -1
  11. data/docs/summary.md +3 -0
  12. data/docs/technical-overview/exception-handling.md +94 -0
  13. data/docs/technical-overview/fiber-scheduling.md +99 -0
  14. data/examples/core/cancel.rb +8 -4
  15. data/examples/core/channel_echo.rb +18 -17
  16. data/examples/core/defer.rb +12 -0
  17. data/examples/core/enumerator.rb +4 -4
  18. data/examples/core/fiber_error.rb +9 -0
  19. data/examples/core/fiber_error_with_backtrace.rb +73 -0
  20. data/examples/core/fork.rb +6 -6
  21. data/examples/core/genserver.rb +16 -8
  22. data/examples/core/lock.rb +3 -3
  23. data/examples/core/move_on.rb +4 -3
  24. data/examples/core/move_on_twice.rb +5 -5
  25. data/examples/core/move_on_with_ensure.rb +8 -11
  26. data/examples/core/move_on_with_value.rb +14 -0
  27. data/examples/core/{multiple_spawn.rb → multiple_spin.rb} +5 -5
  28. data/examples/core/nested_cancel.rb +5 -5
  29. data/examples/core/{nested_multiple_spawn.rb → nested_multiple_spin.rb} +6 -6
  30. data/examples/core/nested_spin.rb +17 -0
  31. data/examples/core/pingpong.rb +21 -0
  32. data/examples/core/pulse.rb +4 -5
  33. data/examples/core/resource.rb +6 -4
  34. data/examples/core/resource_cancel.rb +6 -9
  35. data/examples/core/resource_delegate.rb +3 -3
  36. data/examples/core/sleep.rb +3 -3
  37. data/examples/core/sleep_spin.rb +19 -0
  38. data/examples/core/snooze.rb +32 -0
  39. data/examples/core/spin.rb +14 -0
  40. data/examples/core/{spawn_cancel.rb → spin_cancel.rb} +6 -7
  41. data/examples/core/spin_error.rb +17 -0
  42. data/examples/core/spin_error_backtrace.rb +30 -0
  43. data/examples/core/spin_uncaught_error.rb +15 -0
  44. data/examples/core/supervisor.rb +8 -8
  45. data/examples/core/supervisor_with_cancel_scope.rb +7 -7
  46. data/examples/core/supervisor_with_error.rb +8 -8
  47. data/examples/core/supervisor_with_manual_move_on.rb +6 -7
  48. data/examples/core/suspend.rb +13 -0
  49. data/examples/core/thread.rb +1 -1
  50. data/examples/core/thread_cancel.rb +9 -11
  51. data/examples/core/thread_pool.rb +18 -14
  52. data/examples/core/throttle.rb +7 -7
  53. data/examples/core/timeout.rb +3 -3
  54. data/examples/fs/read.rb +7 -9
  55. data/examples/http/config.ru +7 -3
  56. data/examples/http/cuba.ru +22 -0
  57. data/examples/http/happy_eyeballs.rb +6 -4
  58. data/examples/http/http_client.rb +1 -1
  59. data/examples/http/http_get.rb +1 -1
  60. data/examples/http/http_parse_experiment.rb +21 -16
  61. data/examples/http/http_proxy.rb +28 -26
  62. data/examples/http/http_server.rb +10 -10
  63. data/examples/http/http_server_forked.rb +6 -5
  64. data/examples/http/http_server_throttled.rb +3 -3
  65. data/examples/http/http_ws_server.rb +11 -11
  66. data/examples/http/https_raw_client.rb +1 -1
  67. data/examples/http/https_server.rb +8 -8
  68. data/examples/http/https_wss_server.rb +13 -11
  69. data/examples/http/rack_server.rb +2 -2
  70. data/examples/http/rack_server_https.rb +4 -4
  71. data/examples/http/rack_server_https_forked.rb +5 -5
  72. data/examples/http/websocket_secure_server.rb +6 -6
  73. data/examples/http/websocket_server.rb +5 -5
  74. data/examples/interfaces/pg_client.rb +4 -4
  75. data/examples/interfaces/pg_pool.rb +13 -6
  76. data/examples/interfaces/pg_transaction.rb +5 -4
  77. data/examples/interfaces/redis_channels.rb +15 -11
  78. data/examples/interfaces/redis_client.rb +2 -2
  79. data/examples/interfaces/redis_pubsub.rb +2 -1
  80. data/examples/interfaces/redis_pubsub_perf.rb +13 -9
  81. data/examples/io/backticks.rb +11 -0
  82. data/examples/io/cat.rb +4 -5
  83. data/examples/io/echo_client.rb +9 -4
  84. data/examples/io/echo_client_from_stdin.rb +20 -0
  85. data/examples/io/echo_pipe.rb +7 -8
  86. data/examples/io/echo_server.rb +8 -6
  87. data/examples/io/echo_server_with_timeout.rb +13 -10
  88. data/examples/io/echo_stdin.rb +3 -3
  89. data/examples/io/httparty.rb +2 -2
  90. data/examples/io/httparty_multi.rb +8 -4
  91. data/examples/io/httparty_threaded.rb +6 -2
  92. data/examples/io/io_read.rb +2 -2
  93. data/examples/io/irb.rb +16 -4
  94. data/examples/io/net-http.rb +3 -3
  95. data/examples/io/open.rb +17 -0
  96. data/examples/io/system.rb +3 -3
  97. data/examples/io/tcpserver.rb +15 -0
  98. data/examples/io/tcpsocket.rb +6 -5
  99. data/examples/performance/multi_snooze.rb +29 -0
  100. data/examples/performance/{perf_snooze.rb → snooze.rb} +7 -5
  101. data/examples/performance/snooze_raw.rb +39 -0
  102. data/ext/gyro/async.c +165 -0
  103. data/ext/gyro/child.c +167 -0
  104. data/ext/{ev → gyro}/extconf.rb +4 -3
  105. data/ext/gyro/gyro.c +316 -0
  106. data/ext/{ev/ev.h → gyro/gyro.h} +12 -7
  107. data/ext/gyro/gyro_ext.c +23 -0
  108. data/ext/{ev → gyro}/io.c +65 -57
  109. data/ext/{ev → gyro}/libev.h +0 -0
  110. data/ext/gyro/signal.c +117 -0
  111. data/ext/{ev → gyro}/socket.c +61 -6
  112. data/ext/gyro/timer.c +199 -0
  113. data/ext/libev/Changes +35 -0
  114. data/ext/libev/README +2 -1
  115. data/ext/libev/ev.c +213 -151
  116. data/ext/libev/ev.h +95 -88
  117. data/ext/libev/ev_epoll.c +26 -15
  118. data/ext/libev/ev_kqueue.c +11 -5
  119. data/ext/libev/ev_linuxaio.c +642 -0
  120. data/ext/libev/ev_poll.c +13 -8
  121. data/ext/libev/ev_port.c +5 -2
  122. data/ext/libev/ev_vars.h +14 -3
  123. data/ext/libev/ev_wrap.h +16 -0
  124. data/lib/ev_ext.bundle +0 -0
  125. data/lib/polyphony.rb +46 -50
  126. data/lib/polyphony/auto_run.rb +12 -0
  127. data/lib/polyphony/core/cancel_scope.rb +11 -7
  128. data/lib/polyphony/core/channel.rb +16 -9
  129. data/lib/polyphony/core/coprocess.rb +101 -51
  130. data/lib/polyphony/core/exceptions.rb +14 -12
  131. data/lib/polyphony/core/resource_pool.rb +21 -8
  132. data/lib/polyphony/core/supervisor.rb +10 -5
  133. data/lib/polyphony/core/sync.rb +7 -6
  134. data/lib/polyphony/core/thread.rb +4 -4
  135. data/lib/polyphony/core/thread_pool.rb +4 -4
  136. data/lib/polyphony/core/throttler.rb +6 -4
  137. data/lib/polyphony/extensions/core.rb +253 -0
  138. data/lib/polyphony/extensions/io.rb +28 -16
  139. data/lib/polyphony/extensions/openssl.rb +2 -1
  140. data/lib/polyphony/extensions/socket.rb +47 -52
  141. data/lib/polyphony/http.rb +4 -3
  142. data/lib/polyphony/http/agent.rb +68 -57
  143. data/lib/polyphony/http/server.rb +5 -5
  144. data/lib/polyphony/http/server/http1.rb +268 -0
  145. data/lib/polyphony/http/server/http2.rb +62 -0
  146. data/lib/polyphony/http/server/http2_stream.rb +104 -0
  147. data/lib/polyphony/http/server/rack.rb +64 -0
  148. data/lib/polyphony/http/server/request.rb +119 -0
  149. data/lib/polyphony/net.rb +26 -15
  150. data/lib/polyphony/postgres.rb +17 -13
  151. data/lib/polyphony/redis.rb +16 -15
  152. data/lib/polyphony/version.rb +1 -1
  153. data/lib/polyphony/websocket.rb +11 -4
  154. data/polyphony.gemspec +13 -9
  155. data/test/eg.rb +27 -0
  156. data/test/helper.rb +25 -0
  157. data/test/run.rb +5 -0
  158. data/test/test_async.rb +33 -0
  159. data/test/test_coprocess.rb +239 -77
  160. data/test/test_core.rb +95 -61
  161. data/test/test_gyro.rb +148 -0
  162. data/test/test_http_server.rb +313 -0
  163. data/test/test_io.rb +79 -27
  164. data/test/test_kernel.rb +22 -12
  165. data/test/test_signal.rb +36 -0
  166. data/test/test_timer.rb +24 -0
  167. metadata +89 -33
  168. data/examples/core/nested_async.rb +0 -17
  169. data/examples/core/next_tick.rb +0 -12
  170. data/examples/core/sleep_spawn.rb +0 -19
  171. data/examples/core/spawn.rb +0 -14
  172. data/examples/core/spawn_error.rb +0 -28
  173. data/examples/performance/perf_multi_snooze.rb +0 -21
  174. data/ext/ev/async.c +0 -168
  175. data/ext/ev/child.c +0 -169
  176. data/ext/ev/ev_ext.c +0 -23
  177. data/ext/ev/ev_module.c +0 -242
  178. data/ext/ev/signal.c +0 -119
  179. data/ext/ev/timer.c +0 -197
  180. data/lib/polyphony/core/fiber_pool.rb +0 -98
  181. data/lib/polyphony/extensions/kernel.rb +0 -169
  182. data/lib/polyphony/http/http1_adapter.rb +0 -254
  183. data/lib/polyphony/http/http2_adapter.rb +0 -157
  184. data/lib/polyphony/http/rack.rb +0 -25
  185. data/lib/polyphony/http/request.rb +0 -66
  186. data/test/test_ev.rb +0 -110
data/Rakefile CHANGED
@@ -6,15 +6,15 @@ require "rake/clean"
6
6
  # frozen_string_literal: true
7
7
 
8
8
  require "rake/extensiontask"
9
- Rake::ExtensionTask.new("ev_ext") do |ext|
10
- ext.ext_dir = "ext/ev"
9
+ Rake::ExtensionTask.new("gyro_ext") do |ext|
10
+ ext.ext_dir = "ext/gyro"
11
11
  end
12
12
 
13
13
  task :default => [:compile, :test]
14
14
  task :test do
15
- Dir.glob('./test/test_*.rb').each { |file| require(file) }
15
+ exec 'ruby test/run.rb'
16
16
  end
17
17
 
18
- # task default: %w[compile]# spec rubocop]
18
+ task default: %w[compile]
19
19
 
20
20
  CLEAN.include "**/*.o", "**/*.so", "**/*.bundle", "**/*.jar", "pkg", "tmp"
data/TODO.md CHANGED
@@ -1,26 +1,53 @@
1
1
  # Roadmap:
2
2
 
3
- ## 0.20 Full Rack adapter implementation
3
+ ## 0.20.1
4
+
5
+ - Cull, edit and annotate examples
6
+ - Work better mechanism supervising multiple coprocesses (`when_done` feels a
7
+ bit hacky)
8
+
9
+ ## 0.21 REPL usage, coprocess introspection, monitoring
10
+
11
+ - Implement `move_on_after(1, with: nil) { ... }`
12
+ - Implement `Coprocess.await` for waiting on multiple coprocesses without
13
+ starting them in a supervisor, will also necessitate adding `Supervisor#add`
14
+ - Implement `Coprocess#location`
15
+ - Implement `Coprocess#alive?`
16
+ - Implement `Coprocess#caller` - points to coprocess that called the coprocess
17
+ - Implement `Coprocess.list` - a list of running coprocesses
18
+
19
+ ## 0.22 Full Rack adapter implementation
20
+
21
+ - Homogenize HTTP 1 and HTTP 2 headers - upcase ? downcase ?
22
+ - Rewrite agent code to use sequential API (like I did for server)
23
+ - Streaming bodies for HTTP client
24
+
25
+ ```ruby
26
+ def download_doc
27
+ response = Polyphony::HTTP::Agent.get('https://acme.com/doc.pdf')
28
+ File.open('doc.pdf', 'wb+') do |f|
29
+ response.each { |chunk| f << chunk } # streaming body
30
+ end
31
+ end
32
+ ```
4
33
 
5
- - follow Rack specification (doesn't have to include stuff like streaming or
6
- websockets)
7
34
  - find some demo Rack apps and test with Polyphony
8
35
 
9
- ## 0.21 Working Rails application
36
+ ## 0.23 Working Sinatra application
10
37
 
11
38
  - app with database access (postgresql)
12
39
  - benchmarks!
13
40
 
14
- ## 0.22 Support for multi-threading
41
+ ## 0.24 Support for multi-threading
15
42
 
16
43
  - Separate event loop for each thread
17
44
 
18
- ## 0.23 Testing
45
+ ## 0.25 Testing
19
46
 
20
47
  - test thread / thread_pool modules
21
48
  - report test coverage
22
49
 
23
- ## 0.24 Documentation
50
+ ## 0.26 Documentation
24
51
 
25
52
  # DNS
26
53
 
@@ -56,3 +83,4 @@ puts "listening on port 5300"
56
83
  Prior art:
57
84
 
58
85
  - https://github.com/socketry/async-dns
86
+
data/bin/poly ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative('../lib/polyphony/http')
4
+
5
+ app_path = ARGV.first || './config.ru'
6
+ app = Polyphony::HTTP::Rack.load(app_path)
7
+ opts = { reuse_addr: true, dont_linger: true }
8
+
9
+ puts "listening on port 1234"
10
+ puts "pid: #{Process.pid}"
11
+ Polyphony::HTTP::Server.serve('0.0.0.0', 1234, opts, &app)
@@ -3,7 +3,7 @@
3
3
  ## Installing
4
4
 
5
5
  ```bash
6
- $ gem install nuclear
6
+ $ gem install polyphony
7
7
  ```
8
8
 
9
9
  ## Tutorial
data/docs/summary.md CHANGED
@@ -7,3 +7,6 @@
7
7
  * [Installing](getting-started/getting-started.md)
8
8
  * [Tutorial](getting-started/tutorial.md)
9
9
 
10
+ ## Technical overview
11
+
12
+ * [Error handling](technical-overview/error-handling.md)
@@ -0,0 +1,94 @@
1
+ # Exception Handling in a Multi-Fiber Environment
2
+
3
+ Ruby employs a pretty robust exception handling mechanism. An raised exception
4
+ will bubble up the call stack until a suitable exception handler is found, based
5
+ on the exception's class. In addition, the exception will include a stack trace
6
+ showing the execution path from the exception's locus back to the program's
7
+ entry point. Unfortunately, when exceptions are raised while switching between
8
+ fibers, stack traces will only include partial information. Here's a simple
9
+ demonstration:
10
+
11
+ *fiber_exception.rb*
12
+ ```ruby
13
+ require 'fiber'
14
+
15
+ def fail!
16
+ raise 'foobar'
17
+ end
18
+
19
+ f = Fiber.new do
20
+ Fiber.new do
21
+ fail!
22
+ end.transfer
23
+ end
24
+
25
+ f.transfer
26
+ ```
27
+
28
+ Running the above program will give us:
29
+
30
+ ```
31
+ Traceback (most recent call last):
32
+ 1: from fiber_exception.rb:9:in `block (2 levels) in <main>'
33
+ fiber_exception.rb:4:in `fail!': foobar (RuntimeError)
34
+ ```
35
+
36
+ So, the stack trace includes two frames: the exception's locus on line 4 and the
37
+ call site at line 9. But we have no information on how we got to line 9. Let's
38
+ imagine if we had more complete information about the sequence of execution. In
39
+ fact, what is missing is information about how the different fibers were
40
+ created. If we had that, our stack trace would have looked something like this:
41
+
42
+ ```
43
+ Traceback (most recent call last):
44
+ 4: from fiber_exception.rb:13:in `<main>'
45
+ 3: from fiber_exception.rb:7:in `Fiber.new'
46
+ 2: from fiber_exception.rb:8:in `Fiber.new'
47
+ 1: from fiber_exception.rb:9:in `block (2 levels) in <main>'
48
+ fiber_exception.rb:4:in `fail!': foobar (RuntimeError)
49
+ ```
50
+
51
+ In order to achieve this, Polyphony patches `Fiber.new` to keep track of the
52
+ call stack at the moment the fiber was created, as well as the fiber from which
53
+ the call happened. In addition, Polyphony patches `Exception#backtrace` in order
54
+ to synthesize a complete stack trace based on the call stack information stored
55
+ for the current fiber. This is done recursively through the chain of fibers
56
+ leading up to the current location. What we end up with is a record of the
57
+ entire sequence of (possibly intermittent) execution leading up to the point
58
+ where the exception was raised.
59
+
60
+ In addition, the backtrace is sanitized to remove stack frames originating from
61
+ the Polyphony code itself, which hides away the Polyphony plumbing and lets
62
+ developers concentrate on their own code. The sanitizing of exception backtraces
63
+ can be disabled by setting the `Exception.__disable_sanitized_backtrace__` flag:
64
+
65
+ ```ruby
66
+ Exception.__disable_sanitized_backtrace__ = true
67
+ ...
68
+ ```
69
+
70
+ ## Cleaning up after exceptions
71
+
72
+ A major issue when handling exceptions is cleaning up - freeing up resources
73
+ that have been allocated, cancelling ongoing operations, etc. Polyphony allows
74
+ using the normal `ensure` statement for cleaning up. Have a look at Polyphony's
75
+ implementation of `Kernel#sleep`:
76
+
77
+ ```ruby
78
+ def sleep(duration)
79
+ timer = Gyro::Timer.new(duration, 0)
80
+ timer.await
81
+ ensure
82
+ timer.stop
83
+ end
84
+ ```
85
+
86
+ This method creates a one-shot timer with the given duration and then suspends
87
+ the current fiber, waiting for the timer to fire and then resume the fiber.
88
+ While the awaiting fiber is suspended, other operations might be going on, which
89
+ might interrupt the `sleep` operation by scheduling the awaiting fiber with an
90
+ exception, for example a `MoveOn` or a `Cancel` exception. For this reason, we
91
+ need to *ensure* that the timer will be stopped, regardless of whether it has
92
+ fired or not. We call `timer.stop` inside an ensure block, thus ensuring that
93
+ the timer will have stopped once the awaiting fiber has resumed, even if it has
94
+ not fired.
@@ -0,0 +1,99 @@
1
+ # Fiber Scheduling
2
+
3
+ Ruby provides two mechanisms for transferring control between fibers:
4
+ `Fiber#resume` / `Fiber.yield` and `Fiber#transfer`. The first is inherently asymmetric and is famously used
5
+ for implementing generators and [resumable enumerators](https://blog.appsignal.com/2018/11/27/ruby-magic-fibers-and-enumerators-in-ruby.html).
6
+ Another limiting factor of using resume / yield is that the root fiber can't
7
+ yield away, limiting its usability as a resumable fiber.
8
+
9
+ The second mechanism, using `Fiber#transfer`, is completely symmetric and allows
10
+ use of the root fiber as a general purpose resumable execution context.
11
+ Polyphony uses `Fiber#transfer` exclusively for scheduling fibers.
12
+
13
+ ## The Reactor Fiber
14
+
15
+ Polyphony relies on [libev](http://software.schmorp.de/pkg/libev.html) for
16
+ handling events such as I/O readiness, timers and signals. The libev event loop
17
+ runs in a separate fiber (called the reactor fiber) and handles each event by
18
+ resuming the appropriate fiber using `Fiber#transfer`. The event loop will
19
+ continue running and scheduling fibers as long as there's active event watchers.
20
+ When all event watchers have been deactivated, the event loop terminates and
21
+ control is transferred to the root fiber, which will either terminate the
22
+ program, or go on with other work, and possibly another run of the event loop.
23
+
24
+ ## Fiber scheduling
25
+
26
+ When a new fiber is created, it is in a suspended state. To start it, it needs
27
+ to be resumed using `Fiber#transfer`. Upon performing a blocking operation, such
28
+ as `sleep` or `gets`, an event watcher will be created, and control will be
29
+ transferred to the reactor fiber, which will resume running the event loop. A
30
+ fiber waiting for an event will be resumed using `Fiber#transfer` once the event
31
+ has been received, and will continue execution until encountering another
32
+ blocking operation, at which point it will again create an event watcher and
33
+ transfer control back to the reactor fiber.
34
+
35
+ ## Interrupting blocking operations
36
+
37
+ Sometimes it is desirable to be able to interrupt a blocking operation, such as
38
+ waiting for a socket to be readable, or sleeping for an extended period of time.
39
+ This is especially useful when higher-level constructs are needed for
40
+ controlling multiple concurrent operations.
41
+
42
+ Polyphony provides the ability to interrupt a blocking operation by harnessing
43
+ the ability to transfer values back and forth when using `Fiber#transfer`.
44
+ Whenever a waiting fiber transfers control to the reactor fiber, the value
45
+ received upon being resumed is checked. If the value is an exception, it will
46
+ be raised in the context of the waiting fiber, effectively signalling that the
47
+ blocking operation has been unsuccessful and allowing exception handling using
48
+ the usual mechanisms offered by Ruby, namely `rescue` and `ensure` (see also
49
+ [exception handling](exception-handling.md)).
50
+
51
+ Here's an siplified example of how this mechanism works when reading from an I/O
52
+ object (the actual code for I/O reading in Polyphony is written in C and a bit
53
+ more involved):
54
+
55
+ ```ruby
56
+ def read_from(io)
57
+ loop do
58
+ result = IO.readnonblock(8192, exception: false)
59
+ if result == :wait_readable
60
+ wait_readable(io)
61
+ else
62
+ return result
63
+ end
64
+ end
65
+ end
66
+
67
+ def wait_readable(io)
68
+ watcher = Gyro::IO.new(io, :read)
69
+ # transfer control to event loop
70
+ result = $__reactor_fiber__.transfer
71
+ # got control back, check if result is an exception
72
+ raise result if result.is_a?(Exception)
73
+ result
74
+ ensure
75
+ watcher.active = false
76
+ end
77
+ ```
78
+
79
+ In the above example, the `wait_readable` method will normally wait indefinitely
80
+ until the IO object has become readable. But we could interrupt it at any time
81
+ by scheduling the corresponding fiber with an exception.
82
+
83
+ ## Deferred Operations
84
+
85
+ In addition to waiting for blocking operations, Polyphony provides numerous APIs
86
+ for suspending and scheduling fibers:
87
+
88
+ - `Fiber#safe_transfer(value = nil)` - transfers control to another fiber with
89
+ exception handling.
90
+ - `Fiber#schedule(value = nil)` - schedules a fiber to be resumed once the
91
+ event loop becomes idle.
92
+ - `Kernel#snooze` - transfers control to the reactor fiber while scheduling the
93
+ current fiber to be resumed immediately once the event loop is idle.
94
+ - `Kernel#suspend` - suspends the current fiber indefinitely by transferring
95
+ control to the reactor fiber.
96
+
97
+ In addition, a lower level API allows running arbitrary code in the context of
98
+ the reactor loop using `Kernel#defer`. Using this API will run the given block
99
+ the next time the event loop is idle.
@@ -1,9 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'bundler/setup'
4
- require 'polyphony'
4
+ require 'polyphony/auto_run'
5
5
 
6
- puts "going to sleep..."
7
- cancel_after(1) do
8
- sleep(60)
6
+ begin
7
+ puts 'going to sleep...'
8
+ cancel_after(1) do
9
+ sleep(60)
10
+ end
11
+ rescue Polyphony::Cancel
12
+ puts 'cancelled'
9
13
  end
@@ -1,41 +1,42 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'bundler/setup'
4
- require 'polyphony'
4
+ require 'polyphony/auto_run'
5
5
 
6
- def echo(rchan, wchan)
7
- puts "start echoer"
8
- while msg = rchan.receive
9
- wchan << "you said: #{msg}"
6
+ def echo(cin, cout)
7
+ puts 'start echoer'
8
+ while (msg = cin.receive)
9
+ cout << "you said: #{msg}"
10
10
  end
11
11
  ensure
12
- puts "echoer stopped"
12
+ puts 'echoer stopped'
13
13
  end
14
14
 
15
- chan1, chan2 = Polyphony::Channel.new, Polyphony::Channel.new
15
+ chan1, chan2 = 2.times.map { Polyphony::Channel.new }
16
16
 
17
- echoer = spin { echo(chan1, chan2) }
17
+ spin { echo(chan1, chan2) }
18
18
 
19
19
  spin do
20
- puts "start receiver"
21
- while msg = chan2.receive
20
+ puts 'start receiver'
21
+ while (msg = chan2.receive)
22
22
  puts msg
23
23
  $main.resume if msg =~ /world/
24
24
  end
25
25
  ensure
26
- puts "receiver stopped"
26
+ puts 'receiver stopped'
27
27
  end
28
28
 
29
29
  $main = spin do
30
+ puts 'start main'
30
31
  t0 = Time.now
31
- puts "send hello"
32
- chan1 << "hello"
33
- puts "send world"
34
- chan1 << "world"
32
+ puts 'send hello'
33
+ chan1 << 'hello'
34
+ puts 'send world'
35
+ chan1 << 'world'
35
36
 
36
37
  suspend
37
-
38
- puts "closing channels"
38
+
39
+ puts 'closing channels'
39
40
  chan1.close
40
41
  chan2.close
41
42
  puts "done #{Time.now - t0}"
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony/auto_run'
5
+
6
+ defer do
7
+ puts 'two'
8
+ defer { puts 'four' }
9
+ puts 'three'
10
+ end
11
+
12
+ puts 'one'
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'bundler/setup'
4
- require 'polyphony'
4
+ require 'polyphony/auto_run'
5
5
 
6
- enum = [1,2,3].each
6
+ enum = [1, 2, 3].each
7
7
 
8
8
  spin do
9
- while e = enum.next rescue nil
9
+ while (e = enum.next rescue nil)
10
10
  puts e
11
- sleep 1
11
+ sleep 0.1
12
12
  end
13
13
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ f = Fiber.new do
4
+ raise 'hi'
5
+ end
6
+
7
+ f.resume
8
+
9
+ puts 'done'
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fiber'
4
+
5
+ # This is an experiment to see if we could provide better backtraces for
6
+ # exceptions raised in fibers. Our approach is to monkey-patch Fiber.new so as
7
+ # to keep track of the caller stack trace and calling fiber. We also
8
+ # monkey-patch Exception#backtrace to calculate the full backtrace based on the
9
+ # fiber in which the exception was raised. The benefit of this approach is that
10
+ # there's no need to sanitize the backtrace (remove stack frames related to the
11
+ # backtrace calculation).
12
+ class Fiber
13
+ attr_writer :__calling_fiber__, :__caller__
14
+
15
+ class << self
16
+ alias_method :orig_new, :new
17
+ def new(&block)
18
+ calling_fiber = Fiber.current
19
+ fiber_caller = caller
20
+ orig_new(&block).tap do |f|
21
+ f.__calling_fiber__ = calling_fiber
22
+ f.__caller__ = fiber_caller
23
+ end
24
+ end
25
+ end
26
+
27
+ def caller
28
+ @__caller__ ||= []
29
+ if @__calling_fiber__
30
+ @__caller__ + @__calling_fiber__.caller
31
+ else
32
+ @__caller__
33
+ end
34
+ end
35
+ end
36
+
37
+ class Exception
38
+ alias_method :orig_initialize, :initialize
39
+
40
+ def initialize(*args)
41
+ @__raising_fiber__ = Fiber.current
42
+ orig_initialize(*args)
43
+ end
44
+
45
+ alias_method :orig_backtrace, :backtrace
46
+ def backtrace
47
+ unless @backtrace_called
48
+ @backtrace_called = true
49
+ return orig_backtrace
50
+ end
51
+
52
+ if @__raising_fiber__
53
+ backtrace = orig_backtrace || []
54
+ backtrace + @__raising_fiber__.caller
55
+ else
56
+ orig_backtrace
57
+ end
58
+ end
59
+ end
60
+
61
+ def foo
62
+ Fiber.new do
63
+ bar
64
+ end.resume
65
+ end
66
+
67
+ def bar
68
+ Fiber.new do
69
+ raise 'baz'
70
+ end.resume
71
+ end
72
+
73
+ foo