polyphony 0.69 → 0.73

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +1 -0
  3. data/.github/workflows/test.yml +2 -2
  4. data/.gitignore +3 -1
  5. data/CHANGELOG.md +33 -4
  6. data/Gemfile.lock +2 -2
  7. data/TODO.md +1 -24
  8. data/bin/pdbg +1 -1
  9. data/bin/polyphony-debug +0 -0
  10. data/bin/stress.rb +0 -0
  11. data/bin/test +0 -0
  12. data/docs/_user-guide/all-about-timers.md +1 -1
  13. data/docs/api-reference/exception.md +5 -1
  14. data/docs/api-reference/fiber.md +2 -2
  15. data/docs/faq.md +1 -1
  16. data/docs/getting-started/overview.md +8 -8
  17. data/docs/getting-started/tutorial.md +3 -3
  18. data/docs/main-concepts/concurrency.md +1 -1
  19. data/docs/main-concepts/extending.md +3 -3
  20. data/docs/main-concepts/fiber-scheduling.md +1 -1
  21. data/examples/core/calc.rb +37 -0
  22. data/examples/core/calc_with_restart.rb +40 -0
  23. data/examples/core/calc_with_supervise.rb +37 -0
  24. data/examples/core/message_based_supervision.rb +1 -1
  25. data/examples/core/ring.rb +29 -0
  26. data/examples/io/rack_server.rb +1 -1
  27. data/examples/io/tunnel.rb +1 -1
  28. data/examples/performance/fiber_transfer.rb +1 -1
  29. data/examples/performance/line_splitting.rb +1 -1
  30. data/examples/performance/thread-vs-fiber/compare.rb +1 -1
  31. data/ext/polyphony/backend_common.c +31 -7
  32. data/ext/polyphony/backend_common.h +2 -1
  33. data/ext/polyphony/backend_io_uring.c +57 -67
  34. data/ext/polyphony/backend_io_uring_context.c +1 -1
  35. data/ext/polyphony/backend_io_uring_context.h +1 -1
  36. data/ext/polyphony/backend_libev.c +38 -30
  37. data/ext/polyphony/extconf.rb +25 -13
  38. data/ext/polyphony/polyphony.h +5 -1
  39. data/ext/polyphony/queue.c +2 -2
  40. data/ext/polyphony/runqueue_ring_buffer.c +3 -2
  41. data/ext/polyphony/thread.c +1 -1
  42. data/lib/polyphony/adapters/irb.rb +11 -1
  43. data/lib/polyphony/{extensions → core}/debug.rb +0 -0
  44. data/lib/polyphony/core/global_api.rb +3 -6
  45. data/lib/polyphony/core/timer.rb +2 -2
  46. data/lib/polyphony/debugger.rb +3 -3
  47. data/lib/polyphony/extensions/exception.rb +45 -0
  48. data/lib/polyphony/extensions/fiber.rb +30 -16
  49. data/lib/polyphony/extensions/io.rb +2 -2
  50. data/lib/polyphony/extensions/{core.rb → kernel.rb} +0 -73
  51. data/lib/polyphony/extensions/openssl.rb +20 -5
  52. data/lib/polyphony/extensions/process.rb +19 -0
  53. data/lib/polyphony/extensions/socket.rb +3 -4
  54. data/lib/polyphony/extensions/timeout.rb +10 -0
  55. data/lib/polyphony/extensions.rb +9 -0
  56. data/lib/polyphony/net.rb +0 -1
  57. data/lib/polyphony/version.rb +1 -1
  58. data/lib/polyphony.rb +2 -5
  59. data/polyphony.gemspec +1 -1
  60. data/test/coverage.rb +2 -2
  61. data/test/stress.rb +1 -1
  62. data/test/test_backend.rb +12 -12
  63. data/test/test_event.rb +1 -1
  64. data/test/test_ext.rb +1 -1
  65. data/test/test_fiber.rb +52 -12
  66. data/test/test_global_api.rb +16 -4
  67. data/test/test_io.rb +3 -3
  68. data/test/test_process_supervision.rb +39 -10
  69. data/test/test_queue.rb +6 -6
  70. data/test/test_signal.rb +20 -1
  71. data/test/test_socket.rb +12 -10
  72. data/test/test_supervise.rb +249 -81
  73. data/test/test_sync.rb +2 -2
  74. data/test/test_thread.rb +22 -2
  75. data/test/test_thread_pool.rb +1 -1
  76. data/test/test_throttler.rb +1 -1
  77. data/test/test_timer.rb +2 -2
  78. data/test/test_trace.rb +1 -1
  79. metadata +18 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1074fc28d6110cf489e8dce4fcc9c9a2430b557b57a23d865d4ac35c2bb14847
4
- data.tar.gz: 4d0de60084fdc5eec62c25306e098a19a84e7d859547397eff70e673be2fab9b
3
+ metadata.gz: cfcb66ef2171cc3c4216bc674c38468ef4bd23333031ec77d61d74bc17b32c33
4
+ data.tar.gz: 215cf149bd26e9d3d8c64b5c6f63eeccad86c5b6dc9ed5289bb6a1537e51ab56
5
5
  SHA512:
6
- metadata.gz: f2aa641bceb29837bedc2b8120109b05dac20afa32923b6e20bca9a3d9d6b54886ed1410915f24497a839866ff8a660573a847868747ff1c91aa4e0ba87b45c2
7
- data.tar.gz: b49b24d6ea81f8c1a95402c9024102e0f999a079566af7667ad0ae5aab9d5fef4b934ae9711e447e7a0364d533ffe9071f41ce8309be425913606f3dad1e0860
6
+ metadata.gz: 9157512378e6edb0a362ea3db4fc35ecae3b6d2b54c025184a39ddf5e0e85528ac97aac1f3b829b461aa85e58a1655f35c7ad4d2aec0e8dda97c75d130625b34
7
+ data.tar.gz: f90758ee02b5dd28f488372d260b73413e4d01afa14756b8bfc787cd335c42ef43fa1b5907060cb33065727e804296bc6a25fee91d40b94e390e21c6b98d903e
@@ -0,0 +1 @@
1
+ github: ciconia
@@ -16,7 +16,7 @@ jobs:
16
16
  runs-on: ${{matrix.os}}
17
17
  steps:
18
18
  - uses: actions/checkout@v1
19
- - uses: actions/setup-ruby@v1
19
+ - uses: ruby/setup-ruby@v1
20
20
  with:
21
21
  ruby-version: ${{matrix.ruby}}
22
22
  - name: Install dependencies
@@ -24,7 +24,7 @@ jobs:
24
24
  gem install bundler
25
25
  bundle install
26
26
  - name: Show Linux kernel version
27
- run: uname -r
27
+ run: uname -a
28
28
  - name: Compile C-extension
29
29
  run: POLYPHONY_USE_LIBEV=1 bundle exec rake compile
30
30
  - name: Run tests
data/.gitignore CHANGED
@@ -58,4 +58,6 @@ lib/*.so
58
58
  _site
59
59
  .sass-cache
60
60
 
61
- log
61
+ log
62
+
63
+ .ruby-version
data/CHANGELOG.md CHANGED
@@ -1,3 +1,32 @@
1
+ ## 0.73 2021-12-16
2
+
3
+ - Refactor core extensions into separate files for each class
4
+ - Improve compatibility with Ruby 3.1
5
+ - Improve accuracy of `timer_loop`
6
+
7
+ ## 0.72 2021-11-22
8
+
9
+ - Add support for supervising added child fibers
10
+ - Do not use io_uring under LinuxKit (#66)
11
+ - Fix error message in `Fiber#restart`
12
+ - refactor `runqueue_ring_buffer_mark`
13
+ - Fix setting up test server on random port
14
+ - Enhance `Fiber#supervise`
15
+ - Add support for specifying `restart: true` (same as `restart: :always`)
16
+ - Add support for supervising child fibers (when fibers are not specified)
17
+ - Add `Fiber#attach_all_children_to`
18
+ - Improve `SSLServer#accept` when `servername_cb` is set
19
+ - Fix `#supervise` in Ruby 3.0
20
+
21
+ ## 0.71 2021-08-20
22
+
23
+ - Fix compilation on Ruby 3.0
24
+
25
+ ## 0.70 2021-08-19
26
+
27
+ - New implementation for `#supervise`
28
+ - Reset backend state and runqueue on fork
29
+
1
30
  ## 0.69 2021-08-16
2
31
 
3
32
  - Rename `#__polyphony_read_method__` to `#__parser_read_method__`
@@ -18,7 +47,7 @@
18
47
  ## 0.66 2021-08-01
19
48
 
20
49
  - Fix all splicing APIs on non-linux OSes (#63)
21
- - Add GC marking of buffers when cancelling read/write ops in io_uring backend
50
+ - Add GC marking of buffers when cancelling read/write ops in io_uring backend
22
51
 
23
52
  ## 0.65 2021-07-29
24
53
 
@@ -82,7 +111,7 @@
82
111
  ## 0.55.0 2021-06-17
83
112
 
84
113
  - Finish io_uring implementation of Backend#chain
85
- - Reimplement io_uring op_context acquire/release algorithm (using ref count)
114
+ - Reimplement io_uring op_context acquire/release algorithm (using ref count)
86
115
  - Fix #gets on sockets
87
116
  - Redesign event anti-starvation mechanism
88
117
 
@@ -177,7 +206,7 @@
177
206
 
178
207
  ## 0.47.0 2020-11-10
179
208
 
180
- - Implement `#spin_scope` used for creating blocking fiber scopes
209
+ - Implement `#spin_scope` used for creating blocking fiber scopes
181
210
  - Reimplement `move_on_after`, `cancel_after`, `Timeout.timeout` using
182
211
  `Backend#timeout` (avoids creating canceller fiber for most common use case)
183
212
  - Implement `Backend#timeout` API
@@ -272,7 +301,7 @@
272
301
  - Reimplement Event using `Agent#wait_event`
273
302
  - Improve Queue shift queue performance
274
303
  - Introduce `Agent#wait_event` API for waiting on asynchronous events
275
- - Minimize `fcntl` syscalls in IO operations
304
+ - Minimize `fcntl` syscalls in IO operations
276
305
 
277
306
  ## 0.43.7 2020-07-20
278
307
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polyphony (0.69)
4
+ polyphony (0.73)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -75,4 +75,4 @@ DEPENDENCIES
75
75
  simplecov (= 0.17.1)
76
76
 
77
77
  BUNDLED WITH
78
- 2.2.3
78
+ 2.3.0.dev
data/TODO.md CHANGED
@@ -26,31 +26,8 @@
26
26
 
27
27
  - Add support for `close` to io_uring backend
28
28
 
29
- - Graceful shutdown again:
30
- - What happens to children when doing a graceful shutdown?
31
- - What are the implications of passing graceful shutdown flag to children?
32
- - What about errors while doing a graceful shutdown?
33
- - What about graceful restarts?
34
- - Some interesting discussions:
35
- - https://trio.discourse.group/search?q=graceful%20shutdown
36
- - https://github.com/python-trio/trio/issues/147
37
- - https://github.com/python-trio/trio/issues/143
38
- - https://trio.discourse.group/t/graceful-shutdown/93/2
39
- - https://250bpm.com/blog:146/
40
- - https://www.rodrigoaraujo.me/posts/golang-pattern-graceful-shutdown-of-concurrent-events/
41
- - https://github.com/tj/go-gracefully
42
- - `Fiber#finalize_children` should pass graceful shutdown flag to children
43
- - A good use case is an HTTP server that on graceful shutdown:
44
- - stops listening
45
- - waits for all ongoing requests to finish, optionally with a timeout
46
-
47
29
  ## Roadmap for Polyphony 1.0
48
30
 
49
- - check integration with rb-inotify
50
-
51
- - Improve `#supervise`. It does not work as advertised, and seems to exhibit an
52
- inconsistent behaviour (see supervisor example).
53
-
54
31
  - Add test that mimics the original design for Monocrono:
55
32
  - 256 fibers each waiting for a message
56
33
  - When message received do some blocking work using a `ThreadPool`
@@ -130,7 +107,7 @@
130
107
 
131
108
  Hitting the breakpoint will show the current location in the source code
132
109
  (with few lines before and after), and present a prompt for commands.
133
-
110
+
134
111
  - commands:
135
112
  - `step` / `up` / `skip` / `continue` etc. - step into, step out, step over, run
136
113
  - `switch` - switch fiber
data/bin/pdbg CHANGED
@@ -89,7 +89,7 @@ def get_user_cmd
89
89
  STDOUT << "(pdbg) "
90
90
  cmd = parse_command(STDIN.gets)
91
91
  next unless cmd
92
-
92
+
93
93
  if cmd[:cmd] == :help
94
94
  print_help
95
95
  else
data/bin/polyphony-debug CHANGED
File without changes
data/bin/stress.rb CHANGED
File without changes
data/bin/test CHANGED
File without changes
@@ -44,7 +44,7 @@ The `#sleep` forever call can be used for example in the main fiber when we do
44
44
  all our work in other fibers, since once the main fiber terminates the program
45
45
  exits.
46
46
 
47
- ## Doing Work Later
47
+ ## Doing Work Later
48
48
 
49
49
  While `#sleep` allows you to block execution of the current fiber, sometimes you
50
50
  want to perform some work later, while not blocking the current fiber. This is done simply by spinning off another fiber:
@@ -3,15 +3,19 @@ layout: page
3
3
  title: ::Exception
4
4
  parent: API Reference
5
5
  permalink: /api-reference/exception/
6
+ source_url: https://github.com/digital-fabric/polyphony/blob/master/lib/polyphony/extensions/core.rb
7
+ ruby_docs_url: https://rubyapi.org/3.0/o/exception
6
8
  ---
7
9
  # ::Exception
8
10
 
9
- [Ruby core Exception documentation](https://ruby-doc.org/core-2.7.0/Exception.html)
11
+ [Ruby core Exception documentation](https://rubyapi.org/3.0/o/exception)
10
12
 
11
13
  The core `Exception` class is enhanced to provide a better backtrace that takes
12
14
  into account the fiber hierarchy. In addition, a `source_fiber` attribute allows
13
15
  tracking the fiber from which an uncaught exception was propagated.
14
16
 
17
+
18
+
15
19
  ## Class Methods
16
20
 
17
21
  ## Instance methods
@@ -205,7 +205,7 @@ f.raise
205
205
  Pops the first message from the fiber's mailbox. If no message is available,
206
206
  `#receive` will block until a message is pushed to the mailbox. The received
207
207
  message can be any kind of object. This method is complemented by
208
- `Fiber#<<`/`Fiber#send`.
208
+ `Fiber#<<`/`Fiber#send`.
209
209
 
210
210
  ```ruby
211
211
  spin { Fiber.current.parent << 'hello from child' }
@@ -328,7 +328,7 @@ Returns the fiber's current state, which can be any of the following:
328
328
  Supervises all child fibers, optionally restarting any fiber that terminates.
329
329
 
330
330
  The given `opts` argument controls the behaviour of the supervision. The
331
- following options are currently supported:
331
+ following options are currently supported:
332
332
 
333
333
  - `:restart`: restart options
334
334
  - `nil` - Child fibers are not restarted (default behaviour).
data/docs/faq.md CHANGED
@@ -99,7 +99,7 @@ In conclusion:
99
99
 
100
100
  ## If callbacks suck, why not use promises?
101
101
 
102
- Promises have gained a lot of traction during the last few years as an
102
+ Promises have gained a lot of traction during the last few years as an
103
103
  alternative to callbacks, above all in the Javascript community. While promises
104
104
  have been at a certain point considered for use in Polyphony, they were not
105
105
  found to offer enough of a benefit. Promises still cause split logic, are quite
@@ -114,7 +114,7 @@ single CPU core.
114
114
 
115
115
  Nevertheless, Polyphony fully supports multithreading, with each thread having
116
116
  its own fiber run queue and its own libev event loop. Polyphony even enables
117
- cross-thread communication using [fiber messaging](#message-passing).
117
+ cross-thread communication using [fiber messaging](#message-passing).
118
118
 
119
119
  ## Fibers vs Callbacks
120
120
 
@@ -138,7 +138,7 @@ Fibers, in contrast, let the developer express the business logic in a
138
138
  sequential, easy to read manner: do this, then that. State can be stored right
139
139
  in the business logic, as local variables. And finally, the sequential
140
140
  programming style makes it much easier to debug your code, since stack traces
141
- contain the entire history of execution from the app's inception.
141
+ contain the entire history of execution from the app's inception.
142
142
 
143
143
  ## Structured Concurrency
144
144
 
@@ -229,7 +229,7 @@ normally or with an exception:
229
229
  fiber1 = spin { sleep 1; raise 'foo' }
230
230
  fiber2 = spin { sleep 1 }
231
231
 
232
- supervise # blocks and then propagates the error raised in fiber1
232
+ supervise # blocks and then propagates the error raised in fiber1
233
233
  ```
234
234
 
235
235
  ## Message Passing
@@ -266,7 +266,7 @@ end
266
266
  Notice how the state (the `subscribers` variable) stays local, and how the logic
267
267
  of the chat room is expressed in a way that is both compact and easy to extend.
268
268
  Also notice how the chat room is written as an infinite loop. This is a common
269
- pattern in Polyphony, since fibers can always be stopped at any moment.
269
+ pattern in Polyphony, since fibers can always be stopped at any moment.
270
270
 
271
271
  The code for handling a chat room user might be expressed as follows:
272
272
 
@@ -350,14 +350,14 @@ operations.
350
350
 
351
351
  While a standard event loop-based solution would implement a blocking call
352
352
  separately from the fiber scheduling, the system backend integrates the two to
353
- create a blocking call that is already knows how to switch and schedule fibers.
353
+ create a blocking call that already knows how to switch and schedule fibers.
354
354
  For example, in Polyphony all APIs having to do with reading from files or
355
355
  sockets end up calling `Thread.current.backend.read`, which does all the work.
356
356
 
357
357
  This design offers some major advantages over other designs. It minimizes memory
358
- allocations, of both Ruby objects and C structures. For example, instead of
358
+ allocations of both Ruby objects and C structures. For example, instead of
359
359
  having to allocate libev watchers on the heap and then pass them around, they
360
- are allocated on the stack instead, which saves up on both memory and CPU cycles.
360
+ are allocated on the stack instead, which saves both memory and CPU cycles.
361
361
 
362
362
  In addition, the backend interface includes two methods that allow maximizing
363
363
  server performance by accepting connections and reading from sockets in a tight
@@ -482,5 +482,5 @@ reach version 1.0. Here are some of the exciting directions we're working on.
482
482
 
483
483
  - Support for more core and stdlib APIs
484
484
  - More adapters for gems with C-extensions, such as `mysql`, `sqlite3` etc
485
- - Use `io_uring` backend as alternative to the libev backend
485
+ - Use `io_uring` backend as alternative to the libev backend
486
486
  - More concurrency constructs for building highly concurrent applications
@@ -51,7 +51,7 @@ multiple threads or processes, but this approach has numerous disavantages:
51
51
  - Both threads and processes are limited to a few thousand at best on a single
52
52
  machine. Trying to spawn a thread per client essentially limits the scaling
53
53
  capacity of your system.
54
-
54
+
55
55
  Polyphony eschews both threads and processes in favor of fibers as the basic
56
56
  unit of concurrency. The idea is that any time a blocking I/O operation occurs,
57
57
  the current fiber is paused, and another fiber which has been marked as
@@ -117,7 +117,7 @@ Here's the actual sequence of execution (in pseudo-code)
117
117
  sleeper = spin { ... } # The main fiber spins up a new fiber marked as runnable
118
118
  suspend # The main fiber suspends, waiting for all other work to finish
119
119
  Thread.current.switch_fiber # Polyphony looks for other runnable fibers
120
-
120
+
121
121
  # (sleeper fiber)
122
122
  puts "Going to sleep..." # The sleeper fiber starts running
123
123
  sleep 1 # The sleeper fiber goes to sleep
@@ -280,7 +280,7 @@ def handle_client(client)
280
280
  rescue Polyphony::Cancel
281
281
  client.puts 'Closing connection due to inactivity.'
282
282
  rescue Polyphony::Terminate
283
- # We add a handler for the Terminate exception, and give
283
+ # We add a handler for the Terminate exception, and give
284
284
  client.puts 'Server is shutting down. You have 5 more seconds...'
285
285
  move_on_after(5) do
286
286
  client_loop(client)
@@ -133,7 +133,7 @@ fiber that's blocking on any arbitrary operation.
133
133
  Some other constructs offered by Polyphony:
134
134
 
135
135
  * `Mutex` - a mutex used to synchronize access to a single shared resource.
136
- * `ResourcePool` - used for synchronizing access to a limited amount of shared
136
+ * `ResourcePool` - used for synchronizing access to a limited amount of shared
137
137
  resources, for example a pool of database connections.
138
138
  * `Throttler` - used for throttling repeating operations, for example throttling
139
139
  access to a shared resource, or throttling incoming requests.
@@ -9,9 +9,9 @@ next_title: The Design of Polyphony
9
9
  ---
10
10
  # Extending Polyphony
11
11
 
12
- Polyphony was designed to ease the transition from blocking APIs and
12
+ Polyphony was designed to ease the transition from blocking APIs and
13
13
  callback-based API to non-blocking, fiber-based ones. It is important to
14
- understand that not all blocking calls can be easily converted into
14
+ understand that not all blocking calls can be easily converted into
15
15
  non-blocking calls. That might be the case with Ruby gems based on C-extensions,
16
16
  such as database libraries. In that case, Polyphony's built-in
17
17
  [thread pool](#threadpool) might be used for offloading such blocking calls.
@@ -22,7 +22,7 @@ Some of the most common patterns in Ruby APIs is the callback pattern, in which
22
22
  the API takes a block as a callback to be called upon completion of a task. One
23
23
  such example can be found in the excellent
24
24
  [http_parser.rb](https://github.com/tmm1/http_parser.rb/) gem, which is used by
25
- Polyphony itself to provide HTTP 1 functionality. The `HTTP:Parser` provides
25
+ Polyphony itself to provide HTTP 1 functionality. The `HTTP:Parser` provides
26
26
  multiple hooks, or callbacks, for being notified when an HTTP request is
27
27
  complete. The typical callback-based setup is as follows:
28
28
 
@@ -145,7 +145,7 @@ def with_timeout(duration)
145
145
  sleep duration
146
146
  interruptible_fiber.raise 'timeout'
147
147
  end
148
-
148
+
149
149
  # do work
150
150
  yield
151
151
  ensure
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ # Kernel#spin starts a new fiber
7
+ @controller = spin do
8
+ @worker = spin do
9
+ loop do
10
+ # Each fiber has a mailbox for receiving messages
11
+ peer, op, x, y = receive
12
+ result = x.send(op, y)
13
+ # The result is sent back to the "client"
14
+ peer << result
15
+ end
16
+ end
17
+ # The controller fiber will block until the worker is done (but notice that
18
+ # the worker runs an infinite loop.)
19
+ @worker.await
20
+ end
21
+
22
+ def calc(op, x, y)
23
+ # Send the job to the worker fiber...
24
+ @worker << [Fiber.current, op, x, y]
25
+ # ... and wait for the result
26
+ receive
27
+ end
28
+
29
+ # wait for worker to start
30
+ snooze until @worker
31
+
32
+ p calc(:+, 2, 3)
33
+ p calc(:**, 2, 3)
34
+ p calc(:+, 2, nil)
35
+
36
+ # wait for the controller to terminate
37
+ @controller.await
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ # Kernel#spin starts a new fiber
7
+ @controller = spin do
8
+ @worker = spin do
9
+ loop do
10
+ # Each fiber has a mailbox for receiving messages
11
+ peer, op, x, y = receive
12
+ result = x.send(op, y)
13
+ # The result is sent back to the "client"
14
+ peer << result
15
+ end
16
+ end
17
+ # The controller fiber will block until the worker is done (but notice that
18
+ # the worker runs an infinite loop.)
19
+ @worker.await
20
+ rescue => e
21
+ puts "Uncaught exception in worker: #{e}. Restarting..."
22
+ @worker.restart
23
+ end
24
+
25
+ def calc(op, x, y)
26
+ # Send the job to the worker fiber...
27
+ @worker << [Fiber.current, op, x, y]
28
+ # ... and wait for the result
29
+ receive
30
+ end
31
+
32
+ # wait for worker to start
33
+ snooze until @worker
34
+
35
+ p calc(:+, 2, 3)
36
+ p calc(:**, 2, 3)
37
+ p calc(:+, 2, nil)
38
+
39
+ # wait for the controller to terminate
40
+ @controller.await
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ # Kernel#spin starts a new fiber
7
+ @controller = spin do
8
+ @worker = spin do
9
+ loop do
10
+ # Each fiber has a mailbox for receiving messages
11
+ peer, op, x, y = receive
12
+ result = x.send(op, y)
13
+ # The result is sent back to the "client"
14
+ peer << result
15
+ end
16
+ end
17
+ # The controller fiber will block until the worker is done (but notice that
18
+ # the worker runs an infinite loop.)
19
+ @worker.supervise(@worker, restart: :always)
20
+ end
21
+
22
+ def calc(op, x, y)
23
+ # Send the job to the worker fiber...
24
+ @worker << [Fiber.current, op, x, y]
25
+ # ... and wait for the result
26
+ receive
27
+ end
28
+
29
+ # wait for worker to start
30
+ snooze until @worker
31
+
32
+ p calc(:+, 2, 3)
33
+ p calc(:**, 2, 3)
34
+ p calc(:+, 2, nil)
35
+
36
+ # wait for the controller to terminate
37
+ @controller.await
@@ -45,7 +45,7 @@ def start_worker(id)
45
45
  sleep duration
46
46
  raise 'foo' if rand > 0.7
47
47
  break if rand > 0.6
48
- end
48
+ end
49
49
  end
50
50
 
51
51
  supervise(start_worker(1), start_worker(2))
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ M = 100
7
+ N = 10000
8
+
9
+ GC.disable
10
+
11
+ def monotonic_clock
12
+ ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
13
+ end
14
+
15
+ def spin_proc(next_fiber)
16
+ spin_loop { next_fiber << receive }
17
+ end
18
+
19
+ last = Fiber.current
20
+ N.times { last = spin_proc(last) }
21
+
22
+ snooze
23
+ t0 = monotonic_clock
24
+ M.times do
25
+ last << 'hello'
26
+ receive
27
+ end
28
+ elapsed = monotonic_clock - t0
29
+ puts "M=#{M} N=#{N} elapsed: #{elapsed}"
@@ -14,7 +14,7 @@ module RackAdapter
14
14
  def env(req)
15
15
  {}
16
16
  end
17
-
17
+
18
18
  def respond(socket, request, (status_code, headers, body))
19
19
  body = body.join
20
20
  headers = "Content-Type: text/plain\r\nContent-Length: #{body.bytesize}\r\n"
@@ -21,7 +21,7 @@ def endpoint_loop(idx, peer_idx)
21
21
  '0.0.0.0',
22
22
  port,
23
23
  reuse_addr: true
24
- )
24
+ )
25
25
  # server = TCPServer.open('0.0.0.0', port)
26
26
  log "Listening on port #{port}"
27
27
  loop do
@@ -35,7 +35,7 @@ def run(num_fibers)
35
35
  end
36
36
 
37
37
  last.next = first
38
-
38
+
39
39
  t0 = Time.now
40
40
  puts "start transfer..."
41
41
  first.transfer
@@ -8,7 +8,7 @@ def slice
8
8
  while true
9
9
  idx = str.index("\n")
10
10
  break unless idx
11
-
11
+
12
12
  lines << str.slice!(0, idx + 1)
13
13
  end
14
14
  raise unless lines.size == 4
@@ -27,7 +27,7 @@ def run_test(name, port, cmd, setting)
27
27
  puts "*" * 80
28
28
  puts "Run #{name} (#{port}): #{setting}"
29
29
  puts "*" * 80
30
-
30
+
31
31
  pid = spawn("#{cmd} > /dev/null 2>&1")
32
32
  sleep 1
33
33