polyphony 0.21 → 0.22
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +11 -0
- data/Gemfile.lock +5 -5
- data/TODO.md +83 -20
- data/docs/technical-overview/design-principles.md +3 -3
- data/docs/technical-overview/faq.md +11 -0
- data/docs/technical-overview/fiber-scheduling.md +46 -33
- data/examples/core/sleep_spin.rb +2 -2
- data/examples/http/http2_raw.rb +135 -0
- data/examples/http/http_client.rb +14 -3
- data/examples/http/http_get.rb +28 -2
- data/examples/http/http_server.rb +3 -1
- data/examples/http/http_server_forked.rb +3 -1
- data/examples/interfaces/pg_pool.rb +1 -0
- data/examples/io/echo_server.rb +1 -0
- data/ext/gyro/async.c +7 -9
- data/ext/gyro/child.c +5 -8
- data/ext/gyro/extconf.rb +2 -0
- data/ext/gyro/gyro.c +159 -204
- data/ext/gyro/gyro.h +16 -6
- data/ext/gyro/io.c +7 -10
- data/ext/gyro/signal.c +3 -0
- data/ext/gyro/timer.c +9 -18
- data/lib/polyphony/auto_run.rb +12 -5
- data/lib/polyphony/core/coprocess.rb +1 -1
- data/lib/polyphony/core/resource_pool.rb +49 -15
- data/lib/polyphony/core/supervisor.rb +3 -2
- data/lib/polyphony/extensions/core.rb +16 -3
- data/lib/polyphony/extensions/io.rb +2 -0
- data/lib/polyphony/extensions/openssl.rb +60 -0
- data/lib/polyphony/extensions/socket.rb +0 -4
- data/lib/polyphony/http/client/agent.rb +127 -0
- data/lib/polyphony/http/client/http1.rb +129 -0
- data/lib/polyphony/http/client/http2.rb +180 -0
- data/lib/polyphony/http/client/response.rb +32 -0
- data/lib/polyphony/http/client/site_connection_manager.rb +109 -0
- data/lib/polyphony/http/server/request.rb +0 -1
- data/lib/polyphony/http.rb +1 -1
- data/lib/polyphony/net.rb +2 -1
- data/lib/polyphony/version.rb +1 -1
- data/lib/polyphony.rb +4 -4
- data/polyphony.gemspec +1 -0
- data/test/test_gyro.rb +42 -10
- data/test/test_resource_pool.rb +107 -0
- metadata +10 -4
- data/lib/polyphony/http/agent.rb +0 -250
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 27942af70470ad8751268c35230cf822e5b671305450edc19dc4b998409ca1b2
|
4
|
+
data.tar.gz: 94393e31d741b484e188730496980fd5fc44fd4ff632abf8abe7e9ba3117b71f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2d97e95fd9b8d5c9bf8f3224652c88f29a8e5e930918819647a4e66f2d09be6c190c43c88cf202c3f3211b41faedf5afc0c1c67922fe6e31e2453750b0566777
|
7
|
+
data.tar.gz: 4f86964e496dcc418ed23f45cffe88db4d93489dc6ab7616157456f309f117e1fe3f4e12a2edc06347ee3b7ca3af76f2e2ec461499c5bf05caf30eea281bb6bd
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
0.22 2020-01-02
|
2
|
+
---------------
|
3
|
+
|
4
|
+
* Redesign Gyro scheduling subsystem, go scheduler-less
|
5
|
+
* More docs
|
6
|
+
* Rewrite HTTP client agent c1b63787
|
7
|
+
* Increment Gyro refcount in ResourcePool#acquire
|
8
|
+
* Rewrite ResourcePool
|
9
|
+
* Fix socket extensions
|
10
|
+
* Fix ALPN setup in Net.secure_socket
|
11
|
+
|
1
12
|
0.21 2019-12-12
|
2
13
|
---------------
|
3
14
|
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
polyphony (0.
|
4
|
+
polyphony (0.22)
|
5
5
|
http-2 (= 0.10.0)
|
6
6
|
http_parser.rb (= 0.6.0)
|
7
7
|
modulation (~> 0.25)
|
@@ -11,7 +11,7 @@ GEM
|
|
11
11
|
remote: https://rubygems.org/
|
12
12
|
specs:
|
13
13
|
ansi (1.5.0)
|
14
|
-
builder (3.2.
|
14
|
+
builder (3.2.4)
|
15
15
|
hiredis (0.6.3)
|
16
16
|
http-2 (0.10.0)
|
17
17
|
http_parser.rb (0.6.0)
|
@@ -19,7 +19,7 @@ GEM
|
|
19
19
|
mime-types (~> 3.0)
|
20
20
|
multi_xml (>= 0.5.2)
|
21
21
|
localhost (1.1.4)
|
22
|
-
mime-types (3.3)
|
22
|
+
mime-types (3.3.1)
|
23
23
|
mime-types-data (~> 3.2015)
|
24
24
|
mime-types-data (3.2019.1009)
|
25
25
|
minitest (5.11.3)
|
@@ -31,7 +31,7 @@ GEM
|
|
31
31
|
modulation (0.34)
|
32
32
|
multi_xml (0.6.0)
|
33
33
|
pg (1.1.3)
|
34
|
-
rack (2.0.
|
34
|
+
rack (2.0.8)
|
35
35
|
rake (13.0.1)
|
36
36
|
rake-compiler (1.0.5)
|
37
37
|
rake
|
@@ -55,4 +55,4 @@ DEPENDENCIES
|
|
55
55
|
websocket (= 1.2.8)
|
56
56
|
|
57
57
|
BUNDLED WITH
|
58
|
-
2.1.
|
58
|
+
2.1.3
|
data/TODO.md
CHANGED
@@ -1,43 +1,106 @@
|
|
1
|
-
#
|
1
|
+
# Add ability to cancel multiple coprocesses
|
2
2
|
|
3
|
-
|
3
|
+
```ruby
|
4
|
+
scope = CancelScope.new
|
5
|
+
|
6
|
+
3.times { |i|
|
7
|
+
spin {
|
8
|
+
puts "sleep for #{i + 1}s"
|
9
|
+
scope.call { sleep i + 1 }
|
10
|
+
puts "woke up"
|
11
|
+
}
|
12
|
+
}
|
13
|
+
|
14
|
+
sleep 0.5
|
15
|
+
scope.cancel!
|
16
|
+
```
|
4
17
|
|
5
|
-
|
6
|
-
- Work better mechanism supervising multiple coprocesses (`when_done` feels a
|
7
|
-
bit hacky)
|
18
|
+
# Add ability to wait for signal
|
8
19
|
|
9
|
-
|
20
|
+
```ruby
|
21
|
+
sig = Gyro::Signal('SIGUP')
|
10
22
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
23
|
+
loop do
|
24
|
+
sig.await
|
25
|
+
restart
|
26
|
+
end
|
27
|
+
```
|
28
|
+
|
29
|
+
# HTTP Client Agent
|
30
|
+
|
31
|
+
The concurrency model and the fact that we want to serve the response object on
|
32
|
+
receiving headers and let the user lazily read the response body, means we'll
|
33
|
+
need to change the API to accept a block:
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
# current API
|
37
|
+
resp = Agent.get('http://acme.org')
|
38
|
+
puts resp.body
|
39
|
+
|
40
|
+
# proposed API
|
41
|
+
Agent.get('http://acme.org') do |resp|
|
42
|
+
puts resp.body
|
43
|
+
end
|
44
|
+
```
|
45
|
+
|
46
|
+
While the block is running, the connection adapter is acquired. Once the block
|
47
|
+
is done running, the request (and response) can be discarded. The problem with
|
48
|
+
that if we spin up a coprocess from that block we risk all kinds of race
|
49
|
+
conditions and weird behaviours.
|
50
|
+
|
51
|
+
A compromise might be to allow the two: doing a `get` without providing a block
|
52
|
+
will return a response object that already has the body (i.e. the entire
|
53
|
+
response has already been received). Doing a `get` with a block will invoke the
|
54
|
+
block once headers are received, letting the user's code stream the body:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
def request(ctx, &block)
|
58
|
+
...
|
59
|
+
connection_manager.acquire do |adapter|
|
60
|
+
response = adapter.request(ctx)
|
61
|
+
if block
|
62
|
+
block.(response)
|
63
|
+
else
|
64
|
+
# wait for body
|
65
|
+
response.body
|
20
66
|
end
|
67
|
+
response
|
21
68
|
end
|
22
|
-
|
69
|
+
end
|
70
|
+
```
|
23
71
|
|
72
|
+
# Roadmap:
|
73
|
+
|
74
|
+
## 0.22 Redesign of Gyro scheduling system
|
75
|
+
|
76
|
+
- Schedulerless design - no separate fiber for running ev loop
|
77
|
+
- Blocking operations directly transfer to first scheduled fiber
|
78
|
+
- Scheduled fibers managed using linked list, switching directly from one to the
|
79
|
+
other
|
80
|
+
|
81
|
+
## 0.23 Full Rack adapter implementation
|
82
|
+
|
83
|
+
- Work better mechanism supervising multiple coprocesses (`when_done` feels a
|
84
|
+
bit hacky)
|
85
|
+
- Add supervisor test
|
86
|
+
- Homogenize HTTP 1 and HTTP 2 headers - upcase ? downcase ?
|
24
87
|
- find some demo Rack apps and test with Polyphony
|
25
88
|
|
26
|
-
## 0.
|
89
|
+
## 0.24 Working Sinatra application
|
27
90
|
|
28
91
|
- app with database access (postgresql)
|
29
92
|
- benchmarks!
|
30
93
|
|
31
|
-
## 0.
|
94
|
+
## 0.25 Support for multi-threading
|
32
95
|
|
33
96
|
- Separate event loop for each thread
|
34
97
|
|
35
|
-
## 0.
|
98
|
+
## 0.26 Testing
|
36
99
|
|
37
100
|
- test thread / thread_pool modules
|
38
101
|
- report test coverage
|
39
102
|
|
40
|
-
## 0.
|
103
|
+
## 0.27 Documentation
|
41
104
|
|
42
105
|
# DNS
|
43
106
|
|
@@ -21,9 +21,9 @@ library. Polyphony's design is based on the following principles:
|
|
21
21
|
puts 'going to sleep now'
|
22
22
|
```
|
23
23
|
|
24
|
-
- Blocking operations should yield to
|
25
|
-
wrapper APIs. This means no `async/await` notation, and no
|
26
|
-
deferred computation.
|
24
|
+
- Blocking operations should yield to other concurrent tasks without any
|
25
|
+
decoration or wrapper APIs. This means no `async/await` notation, and no
|
26
|
+
built-in concept of deferred computation.
|
27
27
|
|
28
28
|
```ruby
|
29
29
|
# in Polyphony, I/O ops block the current fiber, but implicitly yield to other
|
@@ -72,3 +72,14 @@ Actually, async/await was contemplated while developing Polyphony, but at a cert
|
|
72
72
|
|
73
73
|
Instead, we have decided to make blocking operations implicit and thus allow the use of common APIs such as `Kernel#sleep` or `IO.popen` in a transparent manner. After all, these APIs in their stock form block execution just as well.
|
74
74
|
|
75
|
+
## Why use `Fiber#transfer` and not `Fiber#resume`?
|
76
|
+
|
77
|
+
The API for `Fiber.yield`/`Fiber#resume` is stateful and is intended for the asymmetric execution of coroutines. This is useful when using generators, or other cases where one coroutine acts as a "server" and another as a "client". In Polyphony's case, all fibers are equal, and control can be transferred freely between them, which is much easier to achieve using `Fiber#transfer`. In addition, using `Fiber#transfer` allows us to perform blocking operations from the main fiber, which is not possible when using `Fiber#resume`.
|
78
|
+
|
79
|
+
## Why is Polyphony not split into multiple gems?
|
80
|
+
|
81
|
+
Polyphony is currently at an experimental stage, and its different APIs are still in flux. For that reason, all the different parts of Polyphony are currently kept in a single gem. Once things stabilize, and as Polyphony approaches version 1.0, it will be split into separate gems, each with its own functionality.
|
82
|
+
|
83
|
+
## Who is behind this project?
|
84
|
+
|
85
|
+
I'm Sharon Rosner, an independent software developer living in France. Here's my [github profile](https://github.com/ciconia). You can contact me by writing to [noteflakes@gmail.com](mailto:ciconia@gmail.com).
|
@@ -4,21 +4,43 @@ Ruby provides two mechanisms for transferring control between fibers: `Fiber#res
|
|
4
4
|
|
5
5
|
The second mechanism, using `Fiber#transfer`, is completely symmetric and allows use of the root fiber as a general purpose resumable execution context. Polyphony uses `Fiber#transfer` exclusively for scheduling fibers.
|
6
6
|
|
7
|
-
##
|
7
|
+
## Scheduler-less scheduling
|
8
8
|
|
9
|
-
Polyphony relies on [libev](http://software.schmorp.de/pkg/libev.html) for handling events such as I/O readiness, timers and signals.
|
9
|
+
Polyphony relies on [libev](http://software.schmorp.de/pkg/libev.html) for handling events such as I/O readiness, timers and signals. In most event reactor-based libraries and frameworks, such as `nio4r`, `EventMachine` or `node.js`, the reactor loop is run, and event callbacks are used to schedule user-supplied code *from inside the loop*. In Polyphony, however, we have chosen a programming model that does not use a loop to schedule fibers. In fact, in Polyphony there's no such thing as a reactor loop, and there's no *scheduler* running on a separate execution context.
|
10
10
|
|
11
|
-
|
11
|
+
Instead, Polyphony maintains a list of scheduled fibers, fibers that will be resumed once the current fiber yields control, which will occur on every blocking operation. If no fibers are scheduled, the libev event reactor will be ran until one or more events have occurred. Events are handled by adding the corresponding fibers onto the *scheduled fibers* list. Finally, control is transferred to the first scheduled fiber, which will run until it blocks or terminates, at which point control is transferred to the next scheduled fiber.
|
12
12
|
|
13
|
-
|
13
|
+
This approach has numerous benefits:
|
14
|
+
|
15
|
+
- No separate reactor fiber that needs to be resumed on each blocking operation, leading to less context switches, and less bookkeeping.
|
16
|
+
- Clear separation between the reactor code (the `libev` code) and the fiber scheduling code.
|
17
|
+
- Much less time is spent in reactor loop callbacks, letting the reactor loop run more efficiently.
|
18
|
+
- Fibers are resumed outside of the event reactor code, making it easier to avoid race conditions and unexpected behaviours.
|
19
|
+
|
20
|
+
## Fiber states
|
21
|
+
|
22
|
+
In Polyphony, each fiber has one of the following states at any given moment:
|
23
|
+
|
24
|
+
- `:running`: this is the state of the currently running fiber.
|
25
|
+
- `:dead`: the fiber has terminated.
|
26
|
+
- `:paused`: the fiber is paused;
|
27
|
+
- `:scheduled`: the fiber is scheduled to run soon.
|
28
|
+
|
29
|
+
## Fiber scheduling and fiber switching
|
30
|
+
|
31
|
+
The Polyphony scheduling model makes a clear separation between the scheduling of fibers and the switching of fibers. The scheduling of fibers is the act of marking the fiber to be run at the earliest opportunity, but not immediately. The switching of fibers is the act of transferring control to another fiber, in this case the first fiber in the list of *currently* scheduled fibers.
|
32
|
+
|
33
|
+
The scheduling of fibers can occur at any time, either as a result of an event occuring, an exception being raised, or using `Fiber#schedule`. The switching of fibers will occur only when a blocking operation is started, or upon calling `Fiber#suspend` or `Fiber#snooze`. In order to switch to a scheduled fiber, Polyphony uses `Fiber#transfer`.
|
34
|
+
|
35
|
+
When a fiber terminates, any other scheduled fibers will be run. If no fibers are waiting and the main fiber is done running, the Ruby process will terminate.
|
14
36
|
|
15
37
|
## Interrupting blocking operations
|
16
38
|
|
17
39
|
Sometimes it is desirable to be able to interrupt a blocking operation, such as waiting for a socket to be readable, or sleeping for an extended period of time. This is especially useful when higher-level constructs are needed for controlling multiple concurrent operations.
|
18
40
|
|
19
|
-
Polyphony provides the ability to interrupt a blocking operation by harnessing the ability to transfer values back and forth
|
41
|
+
Polyphony provides the ability to interrupt a blocking operation by harnessing the ability to transfer values back and forth between fibers using `Fiber#transfer`. Whenever a waiting fiber yields control to the next scheduled fiber, the value received upon being resumed is checked. If the value is an exception, it will be raised in the context of the waiting fiber, effectively signalling that the blocking operation has been unsuccessful and allowing exception handling using the builtin mechanisms offered by Ruby, namely `rescue` and `ensure` (see also [exception handling](exception-handling.md)).
|
20
42
|
|
21
|
-
Here's
|
43
|
+
Here's a naive implementation of a yielding I/O read operation in Polyphony (the actual code for I/O reading in Polyphony is written in C and is a bit more involved):
|
22
44
|
|
23
45
|
```ruby
|
24
46
|
def read_from(io)
|
@@ -33,38 +55,29 @@ def read_from(io)
|
|
33
55
|
end
|
34
56
|
|
35
57
|
def wait_readable(io)
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
#
|
58
|
+
fiber = Fiber.current
|
59
|
+
watcher = Gyro::IO.new(io, :read) { fiber.transfer }
|
60
|
+
|
61
|
+
# run any scheduled fibers or run libev reactor waiting for events
|
62
|
+
result = GV.run
|
63
|
+
|
64
|
+
# waiting fiber is resumed - check transferred value
|
40
65
|
raise result if result.is_a?(Exception)
|
41
66
|
result
|
42
67
|
ensure
|
68
|
+
# ensure the I/O watcher is deactivated, even if exception is raised
|
43
69
|
watcher.active = false
|
44
70
|
end
|
45
71
|
```
|
46
72
|
|
47
|
-
In the above example, the `wait_readable` method will normally wait indefinitely until the IO object has become readable. But we could interrupt it at any time by scheduling the corresponding fiber with an exception
|
48
|
-
|
49
|
-
## Deferred Operations
|
50
|
-
|
51
|
-
In addition to waiting for blocking operations, Polyphony provides numerous APIs for suspending and scheduling fibers:
|
52
|
-
|
53
|
-
* `Fiber#safe_transfer(value = nil)` - transfers control to another fiber with
|
54
|
-
|
55
|
-
exception handling.
|
56
|
-
|
57
|
-
* `Fiber#schedule(value = nil)` - schedules a fiber to be resumed once the
|
58
|
-
|
59
|
-
event loop becomes idle.
|
60
|
-
|
61
|
-
* `Kernel#snooze` - transfers control to the reactor fiber while scheduling the
|
62
|
-
|
63
|
-
current fiber to be resumed immediately once the event loop is idle.
|
64
|
-
|
65
|
-
* `Kernel#suspend` - suspends the current fiber indefinitely by transferring
|
66
|
-
|
67
|
-
control to the reactor fiber.
|
68
|
-
|
69
|
-
In addition, a lower level API allows running arbitrary code in the context of the reactor loop using `Kernel#defer`. Using this API will run the given block the next time the event loop is idle.
|
73
|
+
In the above example, the `wait_readable` method will normally wait indefinitely until the IO object has become readable. But we could interrupt it at any time by scheduling the corresponding fiber with an exception:
|
70
74
|
|
75
|
+
```ruby
|
76
|
+
def timeout(duration)
|
77
|
+
fiber = Fiber.current
|
78
|
+
watcher = Gyro::Timer.new(duration) { fiber.transfer(TimerException.new) }
|
79
|
+
yield
|
80
|
+
ensure
|
81
|
+
watcher.active = false
|
82
|
+
end
|
83
|
+
```
|
data/examples/core/sleep_spin.rb
CHANGED
@@ -0,0 +1,135 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony/auto_run'
|
5
|
+
require 'http/2'
|
6
|
+
|
7
|
+
# Response = import '../../lib/polyphony/http/client/response'
|
8
|
+
|
9
|
+
url = 'https://realiteq.net/?q=time'
|
10
|
+
uri = URI(url)
|
11
|
+
uri_key = { scheme: uri.scheme, host: uri.host, port: uri.port }
|
12
|
+
|
13
|
+
ctx = {
|
14
|
+
method: :GET,
|
15
|
+
uri: uri,
|
16
|
+
opts: {},
|
17
|
+
retry: 0
|
18
|
+
}
|
19
|
+
|
20
|
+
SECURE_OPTS = { secure: true, alpn_protocols: ['h2', 'http/1.1'] }.freeze
|
21
|
+
socket = Polyphony::Net.tcp_connect(uri_key[:host], uri_key[:port], SECURE_OPTS)
|
22
|
+
|
23
|
+
puts 'connected'
|
24
|
+
|
25
|
+
$client = HTTP2::Client.new
|
26
|
+
$client.on(:frame) { |bytes| socket << bytes }
|
27
|
+
$client.on(:frame_received) do |frame|
|
28
|
+
puts "Received frame: #{frame.inspect}"
|
29
|
+
end
|
30
|
+
# $client.on(:frame_sent) do |frame|
|
31
|
+
# puts "Sent frame: #{frame.inspect}"
|
32
|
+
# end
|
33
|
+
|
34
|
+
reader = spin do
|
35
|
+
while (data = socket.readpartial(8192))
|
36
|
+
$client << data
|
37
|
+
snooze
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
stream = $client.new_stream
|
42
|
+
|
43
|
+
$headers = nil
|
44
|
+
$done = nil
|
45
|
+
@buffered_chunks = []
|
46
|
+
|
47
|
+
@waiting_headers_fiber = nil
|
48
|
+
@waiting_chunk_fiber = nil
|
49
|
+
@waiting_done_fiber = nil
|
50
|
+
|
51
|
+
# send request
|
52
|
+
headers = {
|
53
|
+
':method' => ctx[:method].to_s,
|
54
|
+
':scheme' => ctx[:uri].scheme,
|
55
|
+
':authority' => [ctx[:uri].host, ctx[:uri].port].join(':'),
|
56
|
+
':path' => ctx[:uri].request_uri,
|
57
|
+
'User-Agent' => 'curl/7.54.0'
|
58
|
+
}
|
59
|
+
headers.merge!(ctx[:opts][:headers]) if ctx[:opts][:headers]
|
60
|
+
|
61
|
+
if ctx[:opts][:payload]
|
62
|
+
stream.headers(headers, end_stream: false)
|
63
|
+
stream.data(ctx[:opts][:payload], end_stream: true)
|
64
|
+
else
|
65
|
+
stream.headers(headers, end_stream: true)
|
66
|
+
end
|
67
|
+
|
68
|
+
stream.on(:headers) { |headers|
|
69
|
+
puts "got headers"
|
70
|
+
# if @waiting_headers_fiber
|
71
|
+
# @waiting_headers_fiber.transfer headers.to_h
|
72
|
+
# else
|
73
|
+
$headers = headers.to_h
|
74
|
+
# end
|
75
|
+
}
|
76
|
+
stream.on(:data) { |chunk|
|
77
|
+
puts "got data"
|
78
|
+
# if @waiting_chunk_fiber
|
79
|
+
# @waiting_chunk_fiber&.transfer c
|
80
|
+
# else
|
81
|
+
@buffered_chunks << chunk
|
82
|
+
# end
|
83
|
+
}
|
84
|
+
|
85
|
+
def close
|
86
|
+
puts "got close"
|
87
|
+
$done = true
|
88
|
+
end
|
89
|
+
|
90
|
+
stream.on(:close) { close }
|
91
|
+
# @waiting_done_fiber&.transfer
|
92
|
+
# }
|
93
|
+
|
94
|
+
stream.on(:active) { puts "* active" }
|
95
|
+
stream.on(:half_close) { puts "* half_close" }
|
96
|
+
|
97
|
+
|
98
|
+
|
99
|
+
# wait for response
|
100
|
+
# unless $headers
|
101
|
+
# @waiting_headers_fiber = Fiber.current
|
102
|
+
# $headers = suspend
|
103
|
+
# end
|
104
|
+
# p $headers
|
105
|
+
# response = Response.new(self, $headers[':status'].to_i, $headers)
|
106
|
+
# p response
|
107
|
+
|
108
|
+
puts "waiting for response"
|
109
|
+
while !$done
|
110
|
+
puts "waiting..."
|
111
|
+
sleep 1
|
112
|
+
end
|
113
|
+
puts "done"
|
114
|
+
|
115
|
+
# def body
|
116
|
+
# @waiting_chunk_fiber = Fiber.current
|
117
|
+
# body = +''
|
118
|
+
# while !$done
|
119
|
+
# chunk = suspend
|
120
|
+
# body << chunk
|
121
|
+
# end
|
122
|
+
# body
|
123
|
+
# rescue => e
|
124
|
+
# p e
|
125
|
+
# puts e.backtrace.join("\n")
|
126
|
+
# end
|
127
|
+
# end
|
128
|
+
|
129
|
+
# adapter = StreamAdapter.new
|
130
|
+
# resp = adapter.request(ctx)
|
131
|
+
# puts "*" * 40
|
132
|
+
# p resp
|
133
|
+
|
134
|
+
# body = resp.body
|
135
|
+
# p body
|
@@ -3,15 +3,26 @@
|
|
3
3
|
require 'bundler/setup'
|
4
4
|
require 'polyphony/http'
|
5
5
|
|
6
|
+
Exception.__disable_sanitized_backtrace__ = true
|
7
|
+
|
8
|
+
TIME_URI = 'https://ui.realiteq.net/'
|
9
|
+
|
6
10
|
def get_server_time
|
7
|
-
Polyphony::HTTP::Agent.get(
|
11
|
+
json = Polyphony::HTTP::Agent.get(TIME_URI, query: { q: :time }).json
|
12
|
+
puts "*" * 40
|
13
|
+
p json
|
8
14
|
end
|
9
15
|
|
10
|
-
X =
|
16
|
+
X = 1
|
11
17
|
puts "Making #{X} requests..."
|
12
18
|
t0 = Time.now
|
13
19
|
supervise do |s|
|
14
|
-
X.times {
|
20
|
+
X.times {
|
21
|
+
s.spin {
|
22
|
+
get_server_time
|
23
|
+
}
|
24
|
+
}
|
15
25
|
end
|
26
|
+
# get_server_time
|
16
27
|
elapsed = Time.now - t0
|
17
28
|
puts "count: #{X} elapsed: #{elapsed} rate: #{X / elapsed} reqs/s"
|
data/examples/http/http_get.rb
CHANGED
@@ -2,6 +2,32 @@
|
|
2
2
|
|
3
3
|
require 'bundler/setup'
|
4
4
|
require 'polyphony/http'
|
5
|
+
require 'polyphony/auto_run'
|
5
6
|
|
6
|
-
|
7
|
-
|
7
|
+
Exception.__disable_sanitized_backtrace__ = true
|
8
|
+
|
9
|
+
resp = Polyphony::HTTP::Agent.get('https://realiteq.net/?q=time')
|
10
|
+
puts "*" * 40
|
11
|
+
puts resp.body
|
12
|
+
|
13
|
+
__END__
|
14
|
+
|
15
|
+
X = 1
|
16
|
+
Y = 1
|
17
|
+
t0 = Time.now
|
18
|
+
supervise { |s|
|
19
|
+
X.times {
|
20
|
+
s.spin {
|
21
|
+
Y.times {
|
22
|
+
resp = Polyphony::HTTP::Agent.get('http://about.gitlab.com/')
|
23
|
+
puts "*" * 40
|
24
|
+
p resp.headers
|
25
|
+
puts "*" * 40
|
26
|
+
puts resp.body
|
27
|
+
# puts "body size: #{resp.body.bytesize}"
|
28
|
+
}
|
29
|
+
}
|
30
|
+
}
|
31
|
+
}
|
32
|
+
elapsed = Time.now - t0
|
33
|
+
puts "\nelapsed: #{elapsed} rate: #{(X * Y) / elapsed}"
|
@@ -12,12 +12,14 @@ opts = {
|
|
12
12
|
spin do
|
13
13
|
Polyphony::HTTP::Server.serve('0.0.0.0', 1234, opts) do |req|
|
14
14
|
req.respond("Hello world!\n")
|
15
|
+
rescue Exception => e
|
16
|
+
p e
|
15
17
|
end
|
16
18
|
end
|
17
19
|
|
18
20
|
spin do
|
19
21
|
throttled_loop(1) do
|
20
|
-
puts "
|
22
|
+
puts "#{Time.now} coprocess count: #{Polyphony::Coprocess.list.size}"
|
21
23
|
end
|
22
24
|
end
|
23
25
|
|
@@ -3,6 +3,8 @@
|
|
3
3
|
require 'bundler/setup'
|
4
4
|
require 'polyphony/http'
|
5
5
|
|
6
|
+
::Exception.__disable_sanitized_backtrace__ = true
|
7
|
+
|
6
8
|
opts = {
|
7
9
|
reuse_addr: true,
|
8
10
|
dont_linger: true
|
@@ -24,4 +26,4 @@ child_pids = []
|
|
24
26
|
child_pids << pid
|
25
27
|
end
|
26
28
|
|
27
|
-
child_pids.each { |pid|
|
29
|
+
child_pids.each { |pid| Gyro::Child.new(pid).await }
|
data/examples/io/echo_server.rb
CHANGED
data/ext/gyro/async.c
CHANGED
@@ -77,18 +77,15 @@ static VALUE Gyro_Async_initialize(VALUE self) {
|
|
77
77
|
}
|
78
78
|
|
79
79
|
void Gyro_Async_callback(struct ev_loop *ev_loop, struct ev_async *ev_async, int revents) {
|
80
|
-
VALUE fiber;
|
81
80
|
struct Gyro_Async *async = (struct Gyro_Async*)ev_async;
|
82
81
|
|
82
|
+
ev_async_stop(EV_DEFAULT, ev_async);
|
83
|
+
async->active = 0;
|
84
|
+
|
83
85
|
if (async->fiber != Qnil) {
|
84
|
-
|
85
|
-
async->active = 0;
|
86
|
-
fiber = async->fiber;
|
86
|
+
VALUE fiber = async->fiber;
|
87
87
|
async->fiber = Qnil;
|
88
|
-
|
89
|
-
}
|
90
|
-
else {
|
91
|
-
ev_async_stop(EV_DEFAULT, ev_async);
|
88
|
+
Gyro_schedule_fiber(fiber, Qnil);
|
92
89
|
}
|
93
90
|
}
|
94
91
|
|
@@ -113,9 +110,10 @@ static VALUE Gyro_Async_await(VALUE self) {
|
|
113
110
|
ev_async_start(EV_DEFAULT, &async->ev_async);
|
114
111
|
}
|
115
112
|
|
116
|
-
ret =
|
113
|
+
ret = Gyro_yield();
|
117
114
|
|
118
115
|
// fiber is resumed
|
116
|
+
async->fiber = Qnil;
|
119
117
|
if (RTEST(rb_obj_is_kind_of(ret, rb_eException))) {
|
120
118
|
if (async->active) {
|
121
119
|
async->active = 0;
|