polyphony 1.1.1 → 1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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);
|