polyphony 0.19 → 0.20

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