polyphony 0.43.4 → 0.43.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +1 -1
  3. data/CHANGELOG.md +45 -0
  4. data/Gemfile.lock +1 -1
  5. data/README.md +21 -4
  6. data/TODO.md +1 -6
  7. data/bin/stress.rb +28 -0
  8. data/docs/_includes/head.html +40 -0
  9. data/docs/_includes/title.html +1 -0
  10. data/docs/_user-guide/web-server.md +11 -11
  11. data/docs/getting-started/overview.md +2 -2
  12. data/docs/index.md +4 -3
  13. data/docs/main-concepts/design-principles.md +23 -34
  14. data/docs/main-concepts/fiber-scheduling.md +1 -1
  15. data/docs/polyphony-logo.png +0 -0
  16. data/examples/core/xx-channels.rb +4 -2
  17. data/examples/core/xx-using-a-mutex.rb +2 -1
  18. data/examples/io/xx-happy-eyeballs.rb +21 -22
  19. data/examples/io/xx-zip.rb +19 -0
  20. data/examples/performance/fiber_transfer.rb +47 -0
  21. data/examples/performance/messaging.rb +29 -0
  22. data/examples/performance/multi_snooze.rb +11 -9
  23. data/examples/xx-spin.rb +32 -0
  24. data/ext/polyphony/agent.h +39 -0
  25. data/ext/polyphony/event.c +86 -0
  26. data/ext/polyphony/fiber.c +0 -5
  27. data/ext/polyphony/libev_agent.c +231 -79
  28. data/ext/polyphony/polyphony.c +2 -2
  29. data/ext/polyphony/polyphony.h +19 -16
  30. data/ext/polyphony/polyphony_ext.c +4 -2
  31. data/ext/polyphony/queue.c +194 -0
  32. data/ext/polyphony/ring_buffer.c +96 -0
  33. data/ext/polyphony/ring_buffer.h +28 -0
  34. data/ext/polyphony/thread.c +48 -31
  35. data/lib/polyphony.rb +5 -6
  36. data/lib/polyphony/core/channel.rb +3 -34
  37. data/lib/polyphony/core/resource_pool.rb +13 -75
  38. data/lib/polyphony/core/sync.rb +12 -9
  39. data/lib/polyphony/core/thread_pool.rb +1 -1
  40. data/lib/polyphony/extensions/core.rb +9 -0
  41. data/lib/polyphony/extensions/fiber.rb +9 -2
  42. data/lib/polyphony/extensions/io.rb +16 -15
  43. data/lib/polyphony/extensions/openssl.rb +8 -0
  44. data/lib/polyphony/extensions/socket.rb +13 -9
  45. data/lib/polyphony/extensions/thread.rb +1 -1
  46. data/lib/polyphony/version.rb +1 -1
  47. data/test/helper.rb +2 -2
  48. data/test/q.rb +24 -0
  49. data/test/test_agent.rb +2 -2
  50. data/test/test_event.rb +12 -0
  51. data/test/test_global_api.rb +2 -2
  52. data/test/test_io.rb +24 -2
  53. data/test/test_queue.rb +59 -1
  54. data/test/test_resource_pool.rb +0 -43
  55. data/test/test_trace.rb +18 -17
  56. metadata +16 -5
  57. data/ext/polyphony/libev_queue.c +0 -217
  58. data/lib/polyphony/event.rb +0 -27
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9481b252526ebc3f8e0f5a0121eebb68492bc0c1502cfed447da7b0648e6095b
4
- data.tar.gz: dfd2d5d0415833f14f9ebe2cc6336e87d0951ce86f07ebcdec0ad892f71fb74c
3
+ metadata.gz: b3000a280d995b187518c1e12f585939b4e67312db5458d105c6e6b40c784d5a
4
+ data.tar.gz: 5a582fc77ae044238521619b1d5364a0dd2aac13ca8a6b82d006ec1f23284d2d
5
5
  SHA512:
6
- metadata.gz: 652633b1fc27623edb2e87b5657322ba4826ec176e8eb5131a43249e08340f793ba426743be343bfe0fce02fc60b73cb790afef816a4153585d68bdfeaa9c55d
7
- data.tar.gz: 946abb7526324cae5f1d6b62e25c8410129b07ca75c82a6b7285d6269ed1911e161a6030a394ef29ba238a80da6f96787dd94fdbd5c05bd2a643379252ce80d0
6
+ metadata.gz: 6d403109b1bbf55e1b0ab799d8c74752677784ca0c12cb5b1e21f62c9bdd244245966db8020b20a8d4ed988263a9535aef5a8aff24b50587c9583a67109d7677
7
+ data.tar.gz: 205d6c9859ec8d12ddf7630cbd3893a13d3efb3537dd734e53f17efe31d52399488761da45684d0da7e5074ce950c47a7e51f4a1446577e36f7c3c7838604a7a
@@ -1,6 +1,6 @@
1
1
  name: Tests
2
2
 
3
- on: [push]
3
+ on: [push, pull_request]
4
4
 
5
5
  jobs:
6
6
  build:
@@ -1,3 +1,48 @@
1
+ ## 0.43.10 2020-07-23
2
+
3
+ * Fix race condition when terminating fibers (#33)
4
+ * Fix lock release in `Mutex` (#32)
5
+ * Virtualize agent interface
6
+ * Implement `LibevAgent_connect`
7
+
8
+ ## 0.43.9 2020-07-22
9
+
10
+ * Rewrite `Channel` using `Queue`
11
+ * Rewrite `Mutex` using `Queue`
12
+ * Reimplement `Event` in C to prevent cross-thread race condition
13
+ * Reimplement `ResourcePool` using `Queue`
14
+ * Implement `Queue#size`
15
+
16
+ ## 0.43.8 2020-07-21
17
+
18
+ * Rename `LibevQueue` to `Queue`
19
+ * Reimplement Event using `Agent#wait_event`
20
+ * Improve Queue shift queue performance
21
+ * Introduce `Agent#wait_event` API for waiting on asynchronous events
22
+ * Minimize `fcntl` syscalls in IO operations
23
+
24
+ ## 0.43.7 2020-07-20
25
+
26
+ * Fix memory leak in ResourcePool (#31)
27
+ * Check and adjust file position before reading (#30)
28
+ * Minor documentation fixes
29
+
30
+ ## 0.43.6 2020-07-18
31
+
32
+ * Allow brute-force interrupting with second Ctrl-C
33
+ * Fix outgoing SSL connections (#28)
34
+ * Improve Fiber#await_all_children with many children
35
+ * Use `writev` for writing multiple strings
36
+ * Add logo (thanks [Gerald](https://webocube.com/)!)
37
+
38
+ ## 0.43.5 2020-07-13
39
+
40
+ * Fix `#read_nonblock`, `#write_nonblock` for `IO` and `Socket` (#27)
41
+ * Patch `Kernel#p`, `IO#puts` to issue single write call
42
+ * Add support for multiple arguments in `IO#write` and `LibevAgent#write`
43
+ * Use LibevQueue for fiber run queue
44
+ * Reimplement LibevQueue as ring buffer
45
+
1
46
  ## 0.43.4 2020-07-09
2
47
 
3
48
  * Reimplement Kernel#trap
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polyphony (0.43.4)
4
+ polyphony (0.43.10)
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,9 +1,4 @@
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
1
+ -- Add `Fiber#schedule_with_priority` method
7
2
 
8
3
  - Debugging
9
4
  - Eat your own dogfood: need a good tool to check what's going on when some
@@ -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
 
@@ -18,11 +20,10 @@ implements a comprehensive
18
20
  using [libev](https://github.com/enki/libev) as a high-performance event reactor
19
21
  for I/O, timers, and other asynchronous events.
20
22
 
23
+ [Overview](getting-started/overview){: .btn .btn-green .text-gamma }
21
24
  [Take the tutorial](getting-started/tutorial){: .btn .btn-blue .text-gamma }
22
- [Main Concepts](main-concepts/concurrency/){: .btn .btn-green .text-gamma }
23
- [FAQ](faq){: .btn .btn-green .text-gamma }
24
25
  [Source code](https://github.com/digital-fabric/polyphony){: .btn .btn-purple .text-gamma target="_blank" }
25
- {: .mt-6 .h-align-center }
26
+ {:.text-center .mt-6 .h-align-center }
26
27
 
27
28
  ## Focused on Developer Happiness
28
29
 
@@ -6,7 +6,7 @@ parent: Main Concepts
6
6
  permalink: /main-concepts/design-principles/
7
7
  prev_title: Extending Polyphony
8
8
  ---
9
- # The Design of Polyphony
9
+ # The Design of Polyphony
10
10
 
11
11
  Polyphony is a new gem that aims to enable developing high-performance
12
12
  concurrent applications in Ruby using a fluent, compact syntax and API.
@@ -47,7 +47,7 @@ Nevertheless, while work is being done to harness fibers for providing a better
47
47
  way to do concurrency in Ruby, fibers remain a mistery for most Ruby
48
48
  programmers, a perplexing unfamiliar corner right at the heart of Ruby.
49
49
 
50
- ## Design Principles
50
+ ## The History of Polyphony
51
51
 
52
52
  Polyphony started as an experiment, but over about two years of slow, jerky
53
53
  evolution turned into something I'm really excited to share with the Ruby
@@ -58,31 +58,24 @@ Polyphony today as nothing like the way it began. A careful examination of the
58
58
  [CHANGELOG](https://github.com/digital-fabric/polyphony/blob/master/CHANGELOG.md)
59
59
  would show how Polyphony explored not only different event reactor designs, but
60
60
  also different API designs incorporating various concurrent paradigms such as
61
- promises, async/await, fibers, and finally structured concurrency.
62
-
63
- While Polyphony, like nio4r or EventMachine, uses an event reactor to turn
64
- blocking operations into non-blocking ones, it completely embraces fibers and in
65
- fact does not provide any callback-based APIs. Furthermore, Polyphony provides
66
- fullblown fiber-aware implementations of blocking operations, such as
67
- `read/write`, `sleep` or `waitpid`, instead of just event watching primitives.
61
+ promises, async/await, fibers, and finally structured concurrency.
68
62
 
69
63
  Throughout the development process, it was my intention to create a programming
70
- interface that would make highly-concurrent
71
-
72
-
73
-
74
-
64
+ interface that would make it easy to author highly-concurrent Ruby programs.
75
65
 
66
+ ## Design Principles
76
67
 
68
+ While Polyphony, like nio4r or EventMachine, uses an event reactor to turn
69
+ blocking operations into non-blocking ones, it completely embraces fibers and in
70
+ fact does not provide any callback-based APIs.
77
71
 
78
- a single Ruby process may spin up millions of
79
- concurrent fibers.
72
+ Furthermore, Polyphony provides fullblown fiber-aware implementations of
73
+ blocking operations, such as `read/write`, `sleep` or `waitpid`, instead of just
74
+ event watching primitives.
80
75
 
81
- , by utilizing Ruby fibers together with the
82
- [libev](http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod) event reactor
83
- library. Polyphony's design is based on the following principles:
76
+ Polyphony's design is based on the following principles:
84
77
 
85
- - Polyphony's concurrency model should feel "baked-in". The API should allow
78
+ - The concurrency model should feel "baked-in". The API should allow
86
79
  concurrency with minimal effort. Polyphony should facilitate writing both
87
80
  large apps and small scripts with as little boilerplate code as possible.
88
81
  There should be no calls to initialize the event reactor, or other ceremonial
@@ -91,8 +84,8 @@ library. Polyphony's design is based on the following principles:
91
84
  ```ruby
92
85
  require 'polyphony'
93
86
 
94
- # start 10 fibers, each sleeping for 1 second
95
- 10.times { spin { sleep 1 } }
87
+ # start 10 fibers, each sleeping for 3 seconds
88
+ 10.times { spin { sleep 3 } }
96
89
 
97
90
  puts 'going to sleep now'
98
91
  # wait for other fibers to terminate
@@ -106,14 +99,14 @@ library. Polyphony's design is based on the following principles:
106
99
  ```ruby
107
100
  # in Polyphony, I/O ops might block the current fiber, but implicitly yield to
108
101
  # other concurrent fibers:
109
- clients.each { |client|
102
+ clients.each do |client|
110
103
  spin { client.puts 'Elvis has left the chatroom' }
111
- }
104
+ end
112
105
  ```
113
106
 
114
107
  - Concurrency primitives should be accessible using idiomatic Ruby techniques
115
108
  (blocks, method chaining...) and should feel as much as possible "part of the
116
- language". The resulting API is based more on methods and less on classes,
109
+ language". The resulting API is fundamentally based on methods rather than classes,
117
110
  for example `spin` or `move_on_after`, leading to a coding style that is both
118
111
  more compact and more legible:
119
112
 
@@ -125,8 +118,6 @@ library. Polyphony's design is based on the following principles:
125
118
  }
126
119
  ```
127
120
 
128
- - Breaking up operations into
129
-
130
121
  - Polyphony should embrace Ruby's standard `raise/rescue/ensure` exception
131
122
  handling mechanism. Exception handling in a highly concurrent environment
132
123
  should be robust and foolproof:
@@ -147,7 +138,8 @@ library. Polyphony's design is based on the following principles:
147
138
  constructs through composition.
148
139
 
149
140
  - The entire design should embrace fibers. There should be no callback-based
150
- asynchronous APIs.
141
+ asynchronous APIs. The library and its ecosystem will foster the development
142
+ of techniques and tools for converting callback-based APIs to fiber-based ones.
151
143
 
152
144
  - Use of extensive monkey patching of Ruby core modules and classes such as
153
145
  `Kernel`, `Fiber`, `IO` and `Timeout`. This allows porting over non-Polyphony
@@ -160,13 +152,10 @@ library. Polyphony's design is based on the following principles:
160
152
  # use TCPServer from Ruby's stdlib
161
153
  server = TCPServer.open('127.0.0.1', 1234)
162
154
  while (client = server.accept)
163
- spin do
155
+ spin {
164
156
  while (data = client.gets)
165
- client.write('you said: ', data.chomp, "!\n")
157
+ client.write("you said: #{ data.chomp }\n")
166
158
  end
167
- end
159
+ }
168
160
  end
169
161
  ```
170
-
171
- - Development of techniques and tools for converting callback-based APIs to
172
- fiber-based ones.
@@ -44,7 +44,7 @@ pong = Fiber.new { loop { puts "pong"; ping.transfer } }
44
44
  ping.transfer
45
45
  ```
46
46
 
47
- `Fiber#transform` also allows using the main fiber as a general purpose
47
+ `Fiber#transfer` also allows using the main fiber as a general purpose
48
48
  resumable execution context. For that reason, Polyphony uses `Fiber#transfer`
49
49
  exclusively for scheduling fibers. Normally, however, applications based on
50
50
  Polyphony will not use this API directly.
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