polyphony 0.21 → 0.22

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/CHANGELOG.md +11 -0
  4. data/Gemfile.lock +5 -5
  5. data/TODO.md +83 -20
  6. data/docs/technical-overview/design-principles.md +3 -3
  7. data/docs/technical-overview/faq.md +11 -0
  8. data/docs/technical-overview/fiber-scheduling.md +46 -33
  9. data/examples/core/sleep_spin.rb +2 -2
  10. data/examples/http/http2_raw.rb +135 -0
  11. data/examples/http/http_client.rb +14 -3
  12. data/examples/http/http_get.rb +28 -2
  13. data/examples/http/http_server.rb +3 -1
  14. data/examples/http/http_server_forked.rb +3 -1
  15. data/examples/interfaces/pg_pool.rb +1 -0
  16. data/examples/io/echo_server.rb +1 -0
  17. data/ext/gyro/async.c +7 -9
  18. data/ext/gyro/child.c +5 -8
  19. data/ext/gyro/extconf.rb +2 -0
  20. data/ext/gyro/gyro.c +159 -204
  21. data/ext/gyro/gyro.h +16 -6
  22. data/ext/gyro/io.c +7 -10
  23. data/ext/gyro/signal.c +3 -0
  24. data/ext/gyro/timer.c +9 -18
  25. data/lib/polyphony/auto_run.rb +12 -5
  26. data/lib/polyphony/core/coprocess.rb +1 -1
  27. data/lib/polyphony/core/resource_pool.rb +49 -15
  28. data/lib/polyphony/core/supervisor.rb +3 -2
  29. data/lib/polyphony/extensions/core.rb +16 -3
  30. data/lib/polyphony/extensions/io.rb +2 -0
  31. data/lib/polyphony/extensions/openssl.rb +60 -0
  32. data/lib/polyphony/extensions/socket.rb +0 -4
  33. data/lib/polyphony/http/client/agent.rb +127 -0
  34. data/lib/polyphony/http/client/http1.rb +129 -0
  35. data/lib/polyphony/http/client/http2.rb +180 -0
  36. data/lib/polyphony/http/client/response.rb +32 -0
  37. data/lib/polyphony/http/client/site_connection_manager.rb +109 -0
  38. data/lib/polyphony/http/server/request.rb +0 -1
  39. data/lib/polyphony/http.rb +1 -1
  40. data/lib/polyphony/net.rb +2 -1
  41. data/lib/polyphony/version.rb +1 -1
  42. data/lib/polyphony.rb +4 -4
  43. data/polyphony.gemspec +1 -0
  44. data/test/test_gyro.rb +42 -10
  45. data/test/test_resource_pool.rb +107 -0
  46. metadata +10 -4
  47. data/lib/polyphony/http/agent.rb +0 -250
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 38c4ab294136de3c31b69438b38aaae16f611b87ccd4730cbe4399a9b6e3a7c6
4
- data.tar.gz: 35c69e1ff809d340dbe3a121536005338be13c051e8cf2d8c716fd2546c715bc
3
+ metadata.gz: 27942af70470ad8751268c35230cf822e5b671305450edc19dc4b998409ca1b2
4
+ data.tar.gz: 94393e31d741b484e188730496980fd5fc44fd4ff632abf8abe7e9ba3117b71f
5
5
  SHA512:
6
- metadata.gz: 9d41e1f35a06a5906e21defa0dece49f27faac107764401eceb6e55e9fc6cf68c09ff0fdb109afd8b5b53fee0fdb8ab9ee744dce08490f1c433cc5ec3fe5b7a0
7
- data.tar.gz: c69ee7c201fb812fc1c13120884df61f74ba7b4410ffa31a4a70190a8a95e04b2736f8208422c3018373ad02ca025acadadcdc4898b5eaed16071b14b17c00c7
6
+ metadata.gz: 2d97e95fd9b8d5c9bf8f3224652c88f29a8e5e930918819647a4e66f2d09be6c190c43c88cf202c3f3211b41faedf5afc0c1c67922fe6e31e2453750b0566777
7
+ data.tar.gz: 4f86964e496dcc418ed23f45cffe88db4d93489dc6ab7616157456f309f117e1fe3f4e12a2edc06347ee3b7ca3af76f2e2ec461499c5bf05caf30eea281bb6bd
data/.rubocop.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  AllCops:
2
- TargetRubyVersion: 2.5
2
+ TargetRubyVersion: 2.6
3
3
  RubyInterpreters:
4
4
  - ruby
5
5
  Exclude:
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.21)
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.3)
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.7)
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.0.pre.2
58
+ 2.1.3
data/TODO.md CHANGED
@@ -1,43 +1,106 @@
1
- # Roadmap:
1
+ # Add ability to cancel multiple coprocesses
2
2
 
3
- ## 0.20.1
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
- - Cull, edit and annotate examples
6
- - Work better mechanism supervising multiple coprocesses (`when_done` feels a
7
- bit hacky)
18
+ # Add ability to wait for signal
8
19
 
9
- ## 0.22 Full Rack adapter implementation
20
+ ```ruby
21
+ sig = Gyro::Signal('SIGUP')
10
22
 
11
- - Homogenize HTTP 1 and HTTP 2 headers - upcase ? downcase ?
12
- - Rewrite agent code to use sequential API (like I did for server)
13
- - Streaming bodies for HTTP client
14
-
15
- ```ruby
16
- def download_doc
17
- response = Polyphony::HTTP::Agent.get('https://acme.com/doc.pdf')
18
- File.open('doc.pdf', 'wb+') do |f|
19
- response.each { |chunk| f << chunk } # streaming body
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.23 Working Sinatra application
89
+ ## 0.24 Working Sinatra application
27
90
 
28
91
  - app with database access (postgresql)
29
92
  - benchmarks!
30
93
 
31
- ## 0.24 Support for multi-threading
94
+ ## 0.25 Support for multi-threading
32
95
 
33
96
  - Separate event loop for each thread
34
97
 
35
- ## 0.25 Testing
98
+ ## 0.26 Testing
36
99
 
37
100
  - test thread / thread_pool modules
38
101
  - report test coverage
39
102
 
40
- ## 0.26 Documentation
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 the reactor without any decoration or
25
- wrapper APIs. This means no `async/await` notation, and no built-in concept of
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
- ## The Reactor Fiber
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. The libev event loop runs in a separate fiber \(called the reactor fiber\) and handles each event by resuming the appropriate fiber using `Fiber#transfer`. The event loop will continue running and scheduling fibers as long as there's active event watchers. When all event watchers have been deactivated, the event loop terminates and control is transferred to the root fiber, which will either terminate the program, or go on with other work, and possibly another run of the event loop.
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
- ## Fiber scheduling
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
- When a new fiber is created, it is in a suspended state. To start it, it needs to be resumed using `Fiber#transfer`. Upon performing a blocking operation, such as `sleep` or `gets`, an event watcher will be created, and control will be transferred to the reactor fiber, which will resume running the event loop. A fiber waiting for an event will be resumed using `Fiber#transfer` once the event has been received, and will continue execution until encountering another blocking operation, at which point it will again create an event watcher and transfer control back to the reactor fiber.
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 when using `Fiber#transfer`. Whenever a waiting fiber transfers control to the reactor 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 usual mechanisms offered by Ruby, namely `rescue` and `ensure` \(see also [exception handling](exception-handling.md)\).
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 an siplified example of how this mechanism works when reading from an I/O object \(the actual code for I/O reading in Polyphony is written in C and a bit more involved\):
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
- watcher = Gyro::IO.new(io, :read)
37
- # transfer control to event loop
38
- result = $__reactor_fiber__.transfer
39
- # got control back, check if result is an exception
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
+ ```
@@ -5,14 +5,14 @@ require 'polyphony/auto_run'
5
5
 
6
6
  spin do
7
7
  10.times do |i|
8
- sleep 0.1
8
+ sleep 0.05
9
9
  p i
10
10
  end
11
11
  end
12
12
 
13
13
  spin do
14
14
  puts 'going to sleep...'
15
- sleep 0.8
15
+ sleep 0.4
16
16
  puts 'woke up'
17
17
  end.await
18
18
 
@@ -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('https://ui.realiteq.net/', q: :time).json
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 = 10
16
+ X = 1
11
17
  puts "Making #{X} requests..."
12
18
  t0 = Time.now
13
19
  supervise do |s|
14
- X.times { s.spin { get_server_time } }
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"
@@ -2,6 +2,32 @@
2
2
 
3
3
  require 'bundler/setup'
4
4
  require 'polyphony/http'
5
+ require 'polyphony/auto_run'
5
6
 
6
- resp = Polyphony::HTTP::Agent.get('https://google.com/')
7
- p resp.body
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 "Coprocess count: #{Polyphony::Coprocess.list.size}"
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| EV::Child.new(pid).await }
29
+ child_pids.each { |pid| Gyro::Child.new(pid).await }
@@ -27,6 +27,7 @@ puts "concurrency: #{CONCURRENCY}"
27
27
  DBPOOL.preheat!
28
28
  t0 = Time.now
29
29
  count = 0
30
+
30
31
  coprocs = CONCURRENCY.times.map do
31
32
  spin do
32
33
  loop do
@@ -4,6 +4,7 @@ require 'bundler/setup'
4
4
  require 'polyphony/auto_run'
5
5
 
6
6
  server = TCPServer.open('127.0.0.1', 1234)
7
+ puts "Pid: #{Process.pid}"
7
8
  puts 'Echoing on port 1234...'
8
9
  while (client = server.accept)
9
10
  spin do
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
- ev_async_stop(EV_DEFAULT, ev_async);
85
- async->active = 0;
86
- fiber = async->fiber;
86
+ VALUE fiber = async->fiber;
87
87
  async->fiber = Qnil;
88
- SCHEDULE_FIBER(fiber, 0);
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 = YIELD_TO_REACTOR();
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;