polyphony 1.1.1 → 1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +1 -1
- data/.github/workflows/test_io_uring.yml +1 -1
- data/.rubocop.yml +16 -8
- data/CHANGELOG.md +9 -0
- data/README.md +2 -1
- data/docs/advanced-io.md +9 -1
- data/docs/cancellation.md +213 -0
- data/docs/readme.md +2 -1
- data/examples/core/enumerator.rb +92 -0
- data/examples/io/https_server_sni_2.rb +1 -1
- data/ext/polyphony/backend_common.c +11 -0
- data/ext/polyphony/backend_common.h +2 -0
- data/ext/polyphony/backend_io_uring.c +1 -1
- data/ext/polyphony/backend_libev.c +1 -1
- data/ext/polyphony/polyphony.h +3 -1
- data/lib/polyphony/core/debug.rb +24 -29
- data/lib/polyphony/core/exceptions.rb +0 -3
- data/lib/polyphony/core/sync.rb +0 -3
- data/lib/polyphony/core/thread_pool.rb +1 -5
- data/lib/polyphony/core/throttler.rb +0 -1
- data/lib/polyphony/core/timer.rb +7 -9
- data/lib/polyphony/extensions/exception.rb +0 -1
- data/lib/polyphony/extensions/fiber.rb +41 -28
- data/lib/polyphony/extensions/io.rb +86 -93
- data/lib/polyphony/extensions/kernel.rb +52 -16
- data/lib/polyphony/extensions/object.rb +7 -6
- data/lib/polyphony/extensions/openssl.rb +6 -8
- data/lib/polyphony/extensions/pipe.rb +5 -7
- data/lib/polyphony/extensions/socket.rb +28 -37
- data/lib/polyphony/extensions/thread.rb +2 -4
- data/lib/polyphony/extensions/timeout.rb +0 -1
- data/lib/polyphony/version.rb +1 -1
- data/lib/polyphony.rb +4 -7
- data/polyphony.gemspec +1 -1
- data/test/test_fiber.rb +6 -6
- data/test/test_global_api.rb +3 -3
- data/test/test_io.rb +2 -2
- data/test/test_socket.rb +2 -2
- data/test/test_supervise.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d8069af9a528319585e1112ca1c1fc97981ae7106da100701691d7caf1478fdc
|
4
|
+
data.tar.gz: c5ec274b188332a18f8de7b595fad457f25cfc79bf903c05db0aaf7bf20507dc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f4e8fbd79cce5062a1fa054838186c925fa626c29faf86f589889f418838b681923863d07603036c2276bd3644ce99eac1f84cdc57b7c57e97787bceec6f4cd0
|
7
|
+
data.tar.gz: 22dbe0e38bc0d46ac1dcd543b0ae2b5e4a282c286f6dd40d823347542cbb37e2f21cf3c7c5503389f80f4b057b644c19c683d346a3500ada6a39e83bdbd1516f
|
data/.github/workflows/test.yml
CHANGED
data/.rubocop.yml
CHANGED
@@ -64,8 +64,7 @@ Layout/HashAlignment:
|
|
64
64
|
|
65
65
|
Naming/AccessorMethodName:
|
66
66
|
Exclude:
|
67
|
-
- lib/polyphony/
|
68
|
-
- lib/polyphony/http/server/http2_stream.rb
|
67
|
+
- lib/polyphony/extensions/fiber.rb
|
69
68
|
- examples/**/*.rb
|
70
69
|
|
71
70
|
Naming/MethodName:
|
@@ -74,16 +73,11 @@ Naming/MethodName:
|
|
74
73
|
|
75
74
|
Lint/SuppressedException:
|
76
75
|
Exclude:
|
77
|
-
- lib/polyphony/http/server/http1.rb
|
78
|
-
- lib/polyphony/http/server/http2.rb
|
79
|
-
- lib/polyphony/http/server/http2_stream.rb
|
80
|
-
- lib/polyphony/http/server.rb
|
81
76
|
- examples/**/*.rb
|
82
77
|
|
83
78
|
Metrics/MethodLength:
|
84
79
|
Max: 12
|
85
80
|
Exclude:
|
86
|
-
- lib/polyphony/http/server/rack.rb
|
87
81
|
- lib/polyphony/extensions/io.rb
|
88
82
|
- lib/polyphony/extensions/fiber.rb
|
89
83
|
- test/**/*.rb
|
@@ -96,7 +90,6 @@ Metrics/ModuleLength:
|
|
96
90
|
|
97
91
|
Metrics/ClassLength:
|
98
92
|
Exclude:
|
99
|
-
- lib/polyphony/http/server/http1.rb
|
100
93
|
- lib/polyphony/extensions/io.rb
|
101
94
|
- lib/polyphony/extensions/fiber.rb
|
102
95
|
- lib/polyphony/extensions/object.rb
|
@@ -176,6 +169,21 @@ Lint/RaiseException:
|
|
176
169
|
Lint/StructNewOverride:
|
177
170
|
Enabled: true
|
178
171
|
|
172
|
+
Style/NegatedIf:
|
173
|
+
Enabled: false
|
174
|
+
|
175
|
+
Style/NegatedWhile:
|
176
|
+
Enabled: false
|
177
|
+
|
178
|
+
Style/CombinableLoops:
|
179
|
+
Enabled: false
|
180
|
+
|
181
|
+
Style/InfiniteLoop:
|
182
|
+
Enabled: false
|
183
|
+
|
184
|
+
Style/RedundantReturn:
|
185
|
+
Enabled: false
|
186
|
+
|
179
187
|
Style/ExponentialNotation:
|
180
188
|
Enabled: true
|
181
189
|
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
## 1.2 2023-06-17
|
2
|
+
|
3
|
+
- Require Ruby 3.1 or newer
|
4
|
+
- Add cancellation doc page
|
5
|
+
- Cleanup code
|
6
|
+
- Accept array of fiber in `Fiber.await` (in addition to accepting multiple fibers)
|
7
|
+
- Automatically create backend for thread if not already created (#100)
|
8
|
+
- Fix trap API when used with debug gem (#100)
|
9
|
+
|
1
10
|
## 1.1.1 2023-06-08
|
2
11
|
|
3
12
|
- Minor improvements to documentation
|
data/README.md
CHANGED
@@ -51,7 +51,7 @@ the hood, Polyphony uses
|
|
51
51
|
In order to use Polyphony you need to have:
|
52
52
|
|
53
53
|
- Linux or MacOS (support for Windows will come at a later stage)
|
54
|
-
- Ruby (MRI) 3.
|
54
|
+
- Ruby (MRI) 3.1 or newer
|
55
55
|
|
56
56
|
### Installing the Polyphony Gem
|
57
57
|
|
@@ -77,6 +77,7 @@ $ gem install polyphony
|
|
77
77
|
|
78
78
|
- [Overview](docs/overview.md)
|
79
79
|
- [Tutorial](docs/tutorial.md)
|
80
|
+
- [All About Cancellation: How to Stop Concurrent Operations](docs/cancellation.md)
|
80
81
|
- [Advanced I/O with Polyphony](docs/advanced-io.md)
|
81
82
|
- [Cheat-Sheet](docs/cheat-sheet.md)
|
82
83
|
- [FAQ](docs/faq.md)
|
data/docs/advanced-io.md
CHANGED
@@ -72,6 +72,10 @@ def send_file_using_splice(src, dest)
|
|
72
72
|
end
|
73
73
|
IO.splice(pipe, dest, -2**14)
|
74
74
|
end
|
75
|
+
|
76
|
+
# +----+ IO.splice() +------+ IO.splice() +--------+
|
77
|
+
# | io |-------------->| pipe |-------------->| socket |
|
78
|
+
# +----+ +------+ +--------+
|
75
79
|
```
|
76
80
|
|
77
81
|
There are a few things to notice here: While we have two concurrent operations
|
@@ -293,11 +297,15 @@ def send_compressed_chunked_response_from_io(socket, io)
|
|
293
297
|
spin { IO.gzip(io, pipe) }
|
294
298
|
IO.http1_splice_chunked(pipe, socket, MAX_CHUNK_SIZE)
|
295
299
|
end
|
300
|
+
|
301
|
+
# +----+ IO.gzip() +------+ IO.http1_splice_chunked() +--------+
|
302
|
+
# | io |------------>| pipe |---------------------------->| socket |
|
303
|
+
# +----+ +------+ +--------+
|
296
304
|
```
|
297
305
|
|
298
306
|
The code above looks simple enough, but it actually packs a lot of power in just
|
299
307
|
3 lines of code: we create a pipe, then spin up a fiber that compresses data
|
300
|
-
|
308
|
+
from `io` into the pipe. We then splice data from the pipe to the socket using
|
301
309
|
chunked transfer encoding. As discussed above, we do this without actually
|
302
310
|
allocating any Ruby strings for holding the data, we take maximum advantage of
|
303
311
|
kernel buffers (a.k.a. pipes) and we perform the two operations - compressing
|
@@ -0,0 +1,213 @@
|
|
1
|
+
# @title All About Cancellation: How to Stop Concurrent Operations
|
2
|
+
|
3
|
+
# All About Cancellation: How to Stop Concurrent Operations
|
4
|
+
|
5
|
+
## The Problem of Cancellation
|
6
|
+
|
7
|
+
Being able to cancel an operation is an important aspect of concurrent
|
8
|
+
programming. When you have multiple operations going on at the same time, you
|
9
|
+
want to be able to stop an operation in certain circumstances. Imagine sending a
|
10
|
+
an HTTP request to some server, and waiting for it to respond. We can wait
|
11
|
+
forever, or we can use some kind of mechanism for stopping the operation and
|
12
|
+
declaring it a failure. This mechanism, which is generally called cancellation,
|
13
|
+
plays a crucial part in how Polyphony works. Let's examine how operations are
|
14
|
+
cancelled in Polyphony.
|
15
|
+
|
16
|
+
## Cancellation in Polyphony
|
17
|
+
|
18
|
+
In Polyphony, every operation can be cancelled in the same way, using the same
|
19
|
+
APIs. Polyphony provides multiple APIs that can be used to stop an ongoing
|
20
|
+
operation, but the underlying mechanism is always the same: the fiber running
|
21
|
+
the ongoing operation is scheduled with an exception.
|
22
|
+
|
23
|
+
Let's revisit how fibers are run in Polyphony (this is covered in more detail in
|
24
|
+
the overview document). When a waiting fiber is ready to continue, it is
|
25
|
+
scheduled with the result of the operation which it was waiting for. If the
|
26
|
+
waiting fiber is scheduled with an exception *before* the operation it is
|
27
|
+
waiting for is completed, the operation is stopped, and the exception is raised
|
28
|
+
in the context of the fiber once it is switched to. What this means is that any
|
29
|
+
fiber waiting for a long-running operation to complete can be stopped at any
|
30
|
+
moment, with Polyphony taking care of actually stopping the operation, whether
|
31
|
+
it is reading from a file, or from a socket, or waiting for a timer to elapse.
|
32
|
+
|
33
|
+
On top of this general mechanism of cancellation, Polyphony provides
|
34
|
+
cancellation APIs with differing semantics that can be employed by the
|
35
|
+
developer. For example, `move_on_after` can be used to stop an operation after a
|
36
|
+
timeout without raising an exception, while `cancel_after` can be used to raise
|
37
|
+
an exception that must be handled. There's also the `Fiber#restart` API which,
|
38
|
+
as its name suggests, allows one to restart any fiber, which might be very
|
39
|
+
useful for retrying complex operations.
|
40
|
+
|
41
|
+
Let's examine how a concurrent operation is stopped in Polyphony:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
sleeper = spin { sleep 1 }
|
45
|
+
sleep 0.5
|
46
|
+
sleeper.raise 'Foo'
|
47
|
+
```
|
48
|
+
|
49
|
+
In the example above, we spin up a fiber that sleeps for 1 second, we then sleep
|
50
|
+
for half a second, and cancel `sleeper` by raising an exception in its context.
|
51
|
+
This causes the sleep operation to be cancelled and the fiber to be stopped. The
|
52
|
+
exception is further propagated to the context of the main fiber, and the
|
53
|
+
program finally exits with an exception message.
|
54
|
+
|
55
|
+
Another way to stop a concurrent operation is to use the `Fiber#move_on` method,
|
56
|
+
which causes the fiber to stop, but without raising an exception:
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
sleeper = spin { sleep 1; :foo }
|
60
|
+
sleep 0.5
|
61
|
+
sleeper.move_on :bar
|
62
|
+
result = sleeper.await #=> :bar
|
63
|
+
```
|
64
|
+
|
65
|
+
Using `Fiber#move_on`, we avoid raising an exception which then needs to be
|
66
|
+
rescued, and instead cause the fiber to stop, with its return value being the
|
67
|
+
value given to `Fiber#move_on`. In the code above, the fiber's result will be
|
68
|
+
set to `:bar` instead of `:foo`.
|
69
|
+
|
70
|
+
## Using Timeouts
|
71
|
+
|
72
|
+
Timeouts are probably the most common reason for cancelling an operation. While
|
73
|
+
different Ruby gems provide their own APIs and mechanisms for setting timeouts
|
74
|
+
(core Ruby has also recently introduced timeout settings for IO operations),
|
75
|
+
Polyphony provides a uniform interface for stopping *any* long-running operation
|
76
|
+
based on a timeout, using either the core ruby `Timeout` class, or the
|
77
|
+
`move_on_after` and `cancel_after` that Polyphony provides.
|
78
|
+
|
79
|
+
Before we discuss the different timeout APIs, we can first explore how to create
|
80
|
+
a timeout mechanism from scratch in Polyphony:
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
class MyTimeoutError < RuntimeError
|
84
|
+
end
|
85
|
+
|
86
|
+
def with_timeout(duration)
|
87
|
+
timeout_fiber = spin do
|
88
|
+
sleep duration
|
89
|
+
raise MyTimeoutError
|
90
|
+
end
|
91
|
+
yield
|
92
|
+
ensure
|
93
|
+
timeout_fiber.stop # this is the same as timeout_fiber.move_on
|
94
|
+
end
|
95
|
+
|
96
|
+
# Usage example:
|
97
|
+
with_timeout(5) { sleep 1; :foo } #=> :foo
|
98
|
+
with_timeout(5) { sleep 10; :bar } #=> MyTimeoutError raised!
|
99
|
+
```
|
100
|
+
|
101
|
+
In the code above, we create a `with_timeout` method that takes a duration
|
102
|
+
argument. It starts by spinning up a fiber that will sleep for the given
|
103
|
+
duration, then raise a custom exception. It then runs the given block by calling
|
104
|
+
`yield`. If the given block stops running before the timeout, it exists
|
105
|
+
normally, not before making sure to stop the timeout fiber. If the given block
|
106
|
+
runs longer than the timeout, the exception raised by the timeout fiber will be
|
107
|
+
propagated to the fiber running the block, causing it to be stopped.
|
108
|
+
|
109
|
+
Now that we have an idea of how we can construct timeouts, let's look at the
|
110
|
+
different timeout APIs included in Polyphony:
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
# Timeout without raising an exception
|
114
|
+
move_on_after(5) { ... }
|
115
|
+
|
116
|
+
# Timeout without raising an exception, returning an arbitrary value
|
117
|
+
move_on_after(5, with_value: :foo) { ... } #=> :foo (in case of a timeout)
|
118
|
+
|
119
|
+
# Timeout raising an exception
|
120
|
+
cancel_after(5) { ... } #=> raises a Polyphony::Cancel exception
|
121
|
+
|
122
|
+
# Timeout raising a custom exception
|
123
|
+
cancel_after(5, with_exception: MyExceptionClass) { ... } #=> raises the given exception
|
124
|
+
|
125
|
+
# Timeout using the Timeout API
|
126
|
+
Timeout.timeout(5) { ... } #=> raises Timeout::Error
|
127
|
+
```
|
128
|
+
|
129
|
+
## Resetting Ongoing Operations
|
130
|
+
|
131
|
+
In addition to offering a uniform API for cancelling operations and setting
|
132
|
+
timeouts, Polyphony also allows you to reset, or restart, ongoing operations.
|
133
|
+
Let's imagine an active search feature that shows the user search results while
|
134
|
+
they're typing their search term. How we go about implementing this? We would
|
135
|
+
like to show the user search results, but if the user hits another key before
|
136
|
+
the results are received from the database, we'd like to cancel the operation
|
137
|
+
and relaunch the search. Let's see how Polyphony let's us do this:
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
searcher = spin do
|
141
|
+
peer, term = receive
|
142
|
+
results = get_search_results_from_db(term)
|
143
|
+
peer << results
|
144
|
+
end
|
145
|
+
|
146
|
+
def search_term_updated(term)
|
147
|
+
spin do
|
148
|
+
searcher.restart
|
149
|
+
searcher << [Fiber.current, term]
|
150
|
+
results = receive
|
151
|
+
update_search_results(results)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
```
|
155
|
+
|
156
|
+
In the example above we use fiber message passing in order to communicate
|
157
|
+
between two concurrent operations. Each time `search_term_updated` is called, we
|
158
|
+
*restart* the `searcher` fiber, send the term to it, wait for the results and
|
159
|
+
them update them in the UI.
|
160
|
+
|
161
|
+
## Resettable Timeouts
|
162
|
+
|
163
|
+
Here's another example of restarting: we have a TCP server that accepts
|
164
|
+
connection but would like to close connections after one minute of inactivity.
|
165
|
+
We can use a timeout for that, but each time we receive data from the client, we
|
166
|
+
need to reset the timeout. Here's how we can do this:
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
def handle_connection(conn)
|
170
|
+
timeout = spin do
|
171
|
+
sleep 60
|
172
|
+
raise Polyphony::Cancel
|
173
|
+
end
|
174
|
+
conn.recv_loop do |msg|
|
175
|
+
timeout.reset # same as timeout.restart
|
176
|
+
handle_message(msg)
|
177
|
+
end
|
178
|
+
rescue Polyphony::Cancel
|
179
|
+
puts 'Closing connection due to inactivity!'
|
180
|
+
ensure
|
181
|
+
timeout.stop
|
182
|
+
end
|
183
|
+
|
184
|
+
server.accept_loop { |conn| handle_connection(conn) }
|
185
|
+
```
|
186
|
+
|
187
|
+
In the code above, we create a timeout fiber that sleeps for one minute, then
|
188
|
+
raises an exception. We then run a loop waiting for messages from the client,
|
189
|
+
and each time a message arrives we reset the timeout. In fact, the standard
|
190
|
+
`#move_on_after` and `#cancel_after` APIs also propose a way to reset timeouts.
|
191
|
+
Let's examine how to do just that:
|
192
|
+
|
193
|
+
```ruby
|
194
|
+
def handle_connection(conn)
|
195
|
+
cancel_after(60) do |timeout|
|
196
|
+
conn.recv_loop do |msg|
|
197
|
+
timeout.reset
|
198
|
+
handle_message(msg)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
rescue Polyphony::Cancel
|
202
|
+
puts 'Closing connection due to inactivity!'
|
203
|
+
end
|
204
|
+
|
205
|
+
server.accept_loop { |conn| handle_connection(conn) }
|
206
|
+
```
|
207
|
+
|
208
|
+
Here, instead of hand-rolling our own timeout mechanism, we use `#cancel_after`
|
209
|
+
but give it a block that takes an argument. When the block is called, this
|
210
|
+
argument is actually the timeout fiber that `#cancel_after` spins up, which lets
|
211
|
+
us reset it just like in the example before. Also notice how we don't need to
|
212
|
+
cleanup the timeout in the ensure block, as `#cancel_after` takes care of it by
|
213
|
+
itself.
|
data/docs/readme.md
CHANGED
@@ -53,7 +53,7 @@ the hood, Polyphony uses
|
|
53
53
|
In order to use Polyphony you need to have:
|
54
54
|
|
55
55
|
- Linux or MacOS (support for Windows will come at a later stage)
|
56
|
-
- Ruby (MRI) 3.
|
56
|
+
- Ruby (MRI) 3.1 or newer
|
57
57
|
|
58
58
|
### Installing the Polyphony Gem
|
59
59
|
|
@@ -79,6 +79,7 @@ $ gem install polyphony
|
|
79
79
|
|
80
80
|
- {file:/docs/overview.md Overview}
|
81
81
|
- {file:/docs/tutorial.md Tutorial}
|
82
|
+
- {file:/docs/cancellation.md All About Cancellation: How to Stop Concurrent Operations}
|
82
83
|
- {file:/docs/advanced-io.md Advanced I/O with Polyphony}
|
83
84
|
- {file:/docs/cheat-sheet.md Cheat-Sheet}
|
84
85
|
- {file:/docs/faq.md FAQ}
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony'
|
5
|
+
|
6
|
+
class ::Enumerator
|
7
|
+
def spin
|
8
|
+
map { |i| Object.spin { yield i } }
|
9
|
+
end
|
10
|
+
|
11
|
+
def concurrently(max_fibers: nil, &block)
|
12
|
+
return each_concurrently_with_fiber_pool(max_fibers, &block) if max_fibers
|
13
|
+
|
14
|
+
results = []
|
15
|
+
fibers = []
|
16
|
+
each_with_index do |i, idx|
|
17
|
+
fibers << Object.spin { results[idx] = block.(i) }
|
18
|
+
end
|
19
|
+
Fiber.await(fibers)
|
20
|
+
results
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def each_concurrently_with_fiber_pool(max_fibers, &block)
|
26
|
+
fiber_count = 0
|
27
|
+
results = []
|
28
|
+
workers = []
|
29
|
+
|
30
|
+
each_with_index do |i, idx|
|
31
|
+
if fiber_count < max_fibers
|
32
|
+
workers << Object.spin do
|
33
|
+
loop do
|
34
|
+
item, idx = receive
|
35
|
+
break if item == :__stop__
|
36
|
+
results[idx] = block.(item)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
fiber = workers.shift
|
42
|
+
fiber << [i, idx]
|
43
|
+
workers << fiber
|
44
|
+
end
|
45
|
+
workers.each { |f| f << :__stop__ }
|
46
|
+
Fiber.current.await_all_children
|
47
|
+
results
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
a = [1, 2, 3]
|
52
|
+
|
53
|
+
# ff = a.map do |i|
|
54
|
+
# spin do
|
55
|
+
# puts "#{Fiber.current.inspect} #{i} >>"
|
56
|
+
# sleep rand(0.1..0.2)
|
57
|
+
# puts "#{Fiber.current.inspect} #{i} <<"
|
58
|
+
# end
|
59
|
+
# end
|
60
|
+
|
61
|
+
# Fiber.await(*ff)
|
62
|
+
|
63
|
+
# puts; puts '*' * 40; puts
|
64
|
+
|
65
|
+
# ff = a.each.spin do |i|
|
66
|
+
# puts "#{Fiber.current.inspect} #{i} >>"
|
67
|
+
# sleep 0.1
|
68
|
+
# puts "#{Fiber.current.inspect} #{i} <<"
|
69
|
+
# end
|
70
|
+
|
71
|
+
# Fiber.await(*ff)
|
72
|
+
|
73
|
+
# puts; puts '*' * 40; puts
|
74
|
+
|
75
|
+
# ff = a.each.concurrently do |i|
|
76
|
+
# puts "#{Fiber.current.inspect} #{i} >>"
|
77
|
+
# sleep 0.1
|
78
|
+
# puts "#{Fiber.current.inspect} #{i} <<"
|
79
|
+
# i * 10
|
80
|
+
# end
|
81
|
+
# p ff: ff
|
82
|
+
|
83
|
+
puts; puts '*' * 40; puts
|
84
|
+
|
85
|
+
ff = a.each.concurrently(max_fibers: 2) do |i|
|
86
|
+
puts "#{Fiber.current.inspect} #{i} >>"
|
87
|
+
sleep i
|
88
|
+
puts "#{Fiber.current.inspect} #{i} <<"
|
89
|
+
i * 10
|
90
|
+
end
|
91
|
+
|
92
|
+
p ff: ff
|
@@ -28,7 +28,7 @@ server = Polyphony::Net.tcp_listen('localhost', 1234, opts)
|
|
28
28
|
puts 'Serving HTTPS on port 1234'
|
29
29
|
|
30
30
|
begin
|
31
|
-
server.accept_loop(false) do |socket|
|
31
|
+
server.accept_loop(ignore_errors: false) do |socket|
|
32
32
|
spin do
|
33
33
|
while (data = socket.gets("\n", 8192))
|
34
34
|
if data.chomp.empty?
|
@@ -8,6 +8,8 @@
|
|
8
8
|
#include "ruby/io/buffer.h"
|
9
9
|
#endif
|
10
10
|
|
11
|
+
VALUE cBackend;
|
12
|
+
|
11
13
|
inline void backend_base_initialize(struct Backend_base *base) {
|
12
14
|
runqueue_initialize(&base->runqueue);
|
13
15
|
runqueue_initialize(&base->parked_runqueue);
|
@@ -604,3 +606,12 @@ VALUE coerce_io_string_or_buffer(VALUE buf) {
|
|
604
606
|
return StringValue(buf);
|
605
607
|
}
|
606
608
|
}
|
609
|
+
|
610
|
+
inline VALUE Backend_for_current_thread(void) {
|
611
|
+
VALUE backend = rb_ivar_get(rb_thread_current(), ID_ivar_backend);
|
612
|
+
if (backend == Qnil) {
|
613
|
+
backend = rb_funcall(cBackend, ID_new, 0);
|
614
|
+
rb_ivar_set(rb_thread_current(), ID_ivar_backend, backend);
|
615
|
+
}
|
616
|
+
return backend;
|
617
|
+
}
|
@@ -1965,7 +1965,7 @@ void Backend_unpark_fiber(VALUE self, VALUE fiber) {
|
|
1965
1965
|
}
|
1966
1966
|
|
1967
1967
|
void Init_Backend(void) {
|
1968
|
-
|
1968
|
+
cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cObject);
|
1969
1969
|
rb_define_alloc_func(cBackend, Backend_allocate);
|
1970
1970
|
|
1971
1971
|
rb_define_method(cBackend, "initialize", Backend_initialize, 0);
|
@@ -1610,7 +1610,7 @@ void Backend_unpark_fiber(VALUE self, VALUE fiber) {
|
|
1610
1610
|
void Init_Backend(void) {
|
1611
1611
|
ev_set_allocator(xrealloc);
|
1612
1612
|
|
1613
|
-
|
1613
|
+
cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cObject);
|
1614
1614
|
rb_define_alloc_func(cBackend, Backend_allocate);
|
1615
1615
|
|
1616
1616
|
rb_define_method(cBackend, "initialize", Backend_initialize, 0);
|
data/ext/polyphony/polyphony.h
CHANGED
@@ -25,7 +25,7 @@
|
|
25
25
|
#define FIBER_TRANSFER(fiber, value) rb_funcall(fiber, ID_transfer, 1, value)
|
26
26
|
#endif
|
27
27
|
|
28
|
-
#define BACKEND() (
|
28
|
+
#define BACKEND() Backend_for_current_thread()
|
29
29
|
|
30
30
|
// SAFE is used to cast functions used in rb_ensure
|
31
31
|
#define SAFE(f) (VALUE (*)(VALUE))(f)
|
@@ -95,6 +95,8 @@ VALUE Pipe_close(VALUE self);
|
|
95
95
|
|
96
96
|
// Backend public interface
|
97
97
|
|
98
|
+
VALUE Backend_for_current_thread();
|
99
|
+
|
98
100
|
VALUE Backend_accept(VALUE self, VALUE server_socket, VALUE socket_class);
|
99
101
|
VALUE Backend_accept_loop(VALUE self, VALUE server_socket, VALUE socket_class);
|
100
102
|
VALUE Backend_connect(VALUE self, VALUE io, VALUE addr, VALUE port);
|