polyphony 0.20 → 0.21

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.gitbook.yaml +1 -2
  3. data/.rubocop.yml +1 -0
  4. data/CHANGELOG.md +10 -0
  5. data/Gemfile.lock +1 -1
  6. data/README.md +18 -449
  7. data/TODO.md +0 -10
  8. data/docs/README.md +39 -0
  9. data/docs/getting-started/installing.md +28 -0
  10. data/docs/getting-started/tutorial.md +133 -0
  11. data/docs/summary.md +37 -3
  12. data/docs/technical-overview/concurrency.md +47 -0
  13. data/docs/technical-overview/design-principles.md +112 -0
  14. data/docs/technical-overview/exception-handling.md +34 -41
  15. data/docs/technical-overview/extending.md +80 -0
  16. data/docs/technical-overview/faq.md +74 -0
  17. data/docs/technical-overview/fiber-scheduling.md +23 -52
  18. data/docs/user-guide/web-server.md +129 -0
  19. data/examples/core/01-spinning-up-coprocesses.rb +21 -0
  20. data/examples/core/02-awaiting-coprocesses.rb +18 -0
  21. data/examples/core/03-interrupting.rb +34 -0
  22. data/examples/core/04-no-auto-run.rb +18 -0
  23. data/examples/core/mem-usage.rb +34 -0
  24. data/examples/core/spin_error.rb +0 -1
  25. data/examples/core/spin_uncaught_error.rb +0 -1
  26. data/examples/core/wait_for_signal.rb +14 -0
  27. data/examples/http/http_server_graceful.rb +25 -0
  28. data/examples/http/http_server_simple.rb +11 -0
  29. data/examples/interfaces/redis_pubsub_perf.rb +1 -1
  30. data/ext/gyro/async.c +4 -40
  31. data/ext/gyro/child.c +0 -42
  32. data/ext/gyro/io.c +0 -41
  33. data/lib/polyphony/core/coprocess.rb +8 -0
  34. data/lib/polyphony/core/supervisor.rb +29 -10
  35. data/lib/polyphony/extensions/core.rb +1 -1
  36. data/lib/polyphony/http/server/http2.rb +20 -4
  37. data/lib/polyphony/http/server/http2_stream.rb +35 -3
  38. data/lib/polyphony/version.rb +1 -1
  39. data/lib/polyphony.rb +17 -5
  40. data/test/test_async.rb +14 -7
  41. data/test/test_coprocess.rb +42 -12
  42. data/test/test_core.rb +26 -0
  43. data/test/test_io.rb +14 -5
  44. data/test/test_signal.rb +6 -10
  45. metadata +17 -5
  46. data/docs/getting-started/getting-started.md +0 -10
  47. data/examples/core/spin.rb +0 -14
  48. data/examples/core/spin_cancel.rb +0 -17
@@ -1,56 +1,24 @@
1
- # Fiber Scheduling
1
+ # How Fibers are Scheduled
2
2
 
3
- Ruby provides two mechanisms for transferring control between fibers:
4
- `Fiber#resume` / `Fiber.yield` and `Fiber#transfer`. The first is inherently asymmetric and is famously used
5
- for implementing generators and [resumable enumerators](https://blog.appsignal.com/2018/11/27/ruby-magic-fibers-and-enumerators-in-ruby.html).
6
- Another limiting factor of using resume / yield is that the root fiber can't
7
- yield away, limiting its usability as a resumable fiber.
3
+ Ruby provides two mechanisms for transferring control between fibers: `Fiber#resume` / `Fiber.yield` and `Fiber#transfer`. The first is inherently asymmetric and is famously used for implementing generators and [resumable enumerators](https://blog.appsignal.com/2018/11/27/ruby-magic-fibers-and-enumerators-in-ruby.html). Another limiting factor of using resume / yield is that the root fiber can't yield away, limiting its usability as a resumable fiber.
8
4
 
9
- The second mechanism, using `Fiber#transfer`, is completely symmetric and allows
10
- use of the root fiber as a general purpose resumable execution context.
11
- Polyphony uses `Fiber#transfer` exclusively for scheduling fibers.
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.
12
6
 
13
7
  ## The Reactor Fiber
14
8
 
15
- Polyphony relies on [libev](http://software.schmorp.de/pkg/libev.html) for
16
- handling events such as I/O readiness, timers and signals. The libev event loop
17
- runs in a separate fiber (called the reactor fiber) and handles each event by
18
- resuming the appropriate fiber using `Fiber#transfer`. The event loop will
19
- continue running and scheduling fibers as long as there's active event watchers.
20
- When all event watchers have been deactivated, the event loop terminates and
21
- control is transferred to the root fiber, which will either terminate the
22
- 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. 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.
23
10
 
24
11
  ## Fiber scheduling
25
12
 
26
- When a new fiber is created, it is in a suspended state. To start it, it needs
27
- to be resumed using `Fiber#transfer`. Upon performing a blocking operation, such
28
- as `sleep` or `gets`, an event watcher will be created, and control will be
29
- transferred to the reactor fiber, which will resume running the event loop. A
30
- fiber waiting for an event will be resumed using `Fiber#transfer` once the event
31
- has been received, and will continue execution until encountering another
32
- blocking operation, at which point it will again create an event watcher and
33
- transfer control back to the reactor fiber.
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.
34
14
 
35
15
  ## Interrupting blocking operations
36
16
 
37
- Sometimes it is desirable to be able to interrupt a blocking operation, such as
38
- waiting for a socket to be readable, or sleeping for an extended period of time.
39
- This is especially useful when higher-level constructs are needed for
40
- controlling multiple concurrent operations.
17
+ 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.
41
18
 
42
- Polyphony provides the ability to interrupt a blocking operation by harnessing
43
- the ability to transfer values back and forth when using `Fiber#transfer`.
44
- Whenever a waiting fiber transfers control to the reactor fiber, the value
45
- received upon being resumed is checked. If the value is an exception, it will
46
- be raised in the context of the waiting fiber, effectively signalling that the
47
- blocking operation has been unsuccessful and allowing exception handling using
48
- the usual mechanisms offered by Ruby, namely `rescue` and `ensure` (see also
49
- [exception handling](exception-handling.md)).
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)\).
50
20
 
51
- Here's an siplified example of how this mechanism works when reading from an I/O
52
- object (the actual code for I/O reading in Polyphony is written in C and a bit
53
- more involved):
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\):
54
22
 
55
23
  ```ruby
56
24
  def read_from(io)
@@ -76,24 +44,27 @@ ensure
76
44
  end
77
45
  ```
78
46
 
79
- In the above example, the `wait_readable` method will normally wait indefinitely
80
- until the IO object has become readable. But we could interrupt it at any time
81
- by scheduling the corresponding fiber with an exception.
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.
82
48
 
83
49
  ## Deferred Operations
84
50
 
85
- In addition to waiting for blocking operations, Polyphony provides numerous APIs
86
- for suspending and scheduling fibers:
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
87
54
 
88
- - `Fiber#safe_transfer(value = nil)` - transfers control to another fiber with
89
55
  exception handling.
90
- - `Fiber#schedule(value = nil)` - schedules a fiber to be resumed once the
56
+
57
+ * `Fiber#schedule(value = nil)` - schedules a fiber to be resumed once the
58
+
91
59
  event loop becomes idle.
92
- - `Kernel#snooze` - transfers control to the reactor fiber while scheduling the
60
+
61
+ * `Kernel#snooze` - transfers control to the reactor fiber while scheduling the
62
+
93
63
  current fiber to be resumed immediately once the event loop is idle.
94
- - `Kernel#suspend` - suspends the current fiber indefinitely by transferring
64
+
65
+ * `Kernel#suspend` - suspends the current fiber indefinitely by transferring
66
+
95
67
  control to the reactor fiber.
96
68
 
97
- In addition, a lower level API allows running arbitrary code in the context of
98
- the reactor loop using `Kernel#defer`. Using this API will run the given block
99
- the next time the event loop is idle.
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.
70
+
@@ -0,0 +1,129 @@
1
+ # Web Server
2
+
3
+ Polyphony's web server functionality offers a powerful and flexible way to
4
+ create Ruby-based web servers and web applications. In addition to supporting
5
+ both HTTP 1 and HTTP 2, it supports seamless Websocket upgrades (and indeed
6
+ arbitrary protocol upgrade), TLS termination, and automatic ALPN-based protocol
7
+ selection. In addition, it includes a Rack adapter for running Rack
8
+ applications. Polyphony's web server offers excellent performance
9
+ characteristics, in terms of throughput, memory consumption and scalability
10
+ (benchmarks are forthcoming).
11
+
12
+ What makes Polyphony's web server design stand out is the fact that incoming
13
+ requests can be processed immediately upon receiving the complete headers,
14
+ without needing to wait for the entire request body to be received. This design
15
+ allows web applications to properly buffer uploads of large files without
16
+ consuming large amounts of RAM, as well as reject requests without waiting for
17
+ the entire request body.
18
+
19
+ ## A basic web server
20
+
21
+ ```ruby
22
+ require 'polyphony/http'
23
+
24
+ Polyphony::HTTP::Server.serve('0.0.0.0', 1234) do |request|
25
+ request.respond("Hello world!\n")
26
+ end
27
+ ```
28
+
29
+ Note that requests are handled using a callback block which takes a single
30
+ argument. The `request` object provides the entire API for responding to the
31
+ client.
32
+
33
+ Each client connection will be handled in a separate coprocess, allowing
34
+ concurrent processing of incoming requests.
35
+
36
+ ## HTTP 2 support
37
+
38
+ HTTP 2 support is baked in to the server, which supports both HTTP 2 upgrades
39
+ (for example on a non-encrypted connection) and ALPN-based protocol selection,
40
+ in a completely effortless manner.
41
+
42
+ Since HTTP 2 connections are multiplexed, allowing multiple concurrent requests
43
+ on a single connection, each HTTP 2 stream is handled in a separate coprocess.
44
+
45
+ ## TLS termination
46
+
47
+ TLS termination can be handled by passing a `secure_context` option to the
48
+ server:
49
+
50
+ ```ruby
51
+ require 'polyphony/http'
52
+ require 'localhost/authority'
53
+
54
+ authority = Localhost::Authority.fetch
55
+ opts = { secure_context: authority.server_context }
56
+
57
+ Polyphony::HTTP::Server.serve('0.0.0.0', 1234, opts) do |request|
58
+ request.respond("Hello world!\n")
59
+ end
60
+ ```
61
+
62
+ ## Websockets && HTTP upgrades
63
+
64
+ Polyphony's web server makes it really easy to integrate websocket communication
65
+ with normal HTTP processing:
66
+
67
+ ```ruby
68
+ require 'polyphony/http'
69
+ require 'polyphony/websocket'
70
+
71
+ ws_handler = Polyphony::Websocket.handler do |ws|
72
+ while (msg = ws.recv)
73
+ ws << "you said: #{msg}"
74
+ end
75
+ end
76
+
77
+ opts = {
78
+ upgrade: { websocket: ws_handler }
79
+ }
80
+
81
+ Polyphony::HTTP::Server.serve('0.0.0.0', 1234, opts) do |request|
82
+ request.respond("Hello world!\n")
83
+ end
84
+ ```
85
+
86
+ Polyphony also supports general-purpose HTTP upgrades using the same mechanism:
87
+
88
+ ```ruby
89
+ require 'polyphony/http'
90
+
91
+ opts = {
92
+ upgrade: {
93
+ echo: ->(conn) {
94
+ while (msg = conn.gets)
95
+ conn << "You said: #{msg}"
96
+ end
97
+ }
98
+ }
99
+ }
100
+
101
+ Polyphony::HTTP::Server.serve('0.0.0.0', 1234, opts) do |request|
102
+ request.respond("Hello world!\n")
103
+ end
104
+ ```
105
+
106
+ ## Sending HTTP responses
107
+
108
+ The response API provides multiple ways of responding, with or without a body,
109
+ and enables streaming (using chunked encoding for HTTP/1.1 connections). Here's
110
+ an example of an SSE response:
111
+
112
+ ```ruby
113
+ require 'polyphony/http'
114
+
115
+ def sse_response(request)
116
+ request.send_headers('Content-Type': 'text/event-stream')
117
+ move_on_after(10) {
118
+ loop {
119
+ request.send_chunk("data: #{Time.now}\n\n")
120
+ sleep 1
121
+ }
122
+ }
123
+ ensure
124
+ request.send_chunk("retry: 0\n\n", done: true)
125
+ end
126
+
127
+ Polyphony::HTTP::Server.serve('0.0.0.0', 1234, &method(:sse_response))
128
+ ```
129
+
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+
5
+ # In order to automatically start the reactor, we need to require
6
+ # `polyphony/auto_run`. Otherwise, we can just require `polyphony`
7
+ require 'polyphony/auto_run'
8
+
9
+ def nap(tag, t)
10
+ puts "#{Time.now} #{tag} napping for #{t} seconds..."
11
+ sleep t
12
+ puts "#{Time.now} #{tag} done napping"
13
+ end
14
+
15
+ # We launch two concurrent coprocesses, each sleeping for the given duration.
16
+ spin { nap(:a, 1) }
17
+ spin { nap(:b, 2) }
18
+
19
+ # Having required `polyphony/auto_run`, once our program is done, the
20
+ # libev-based event reactor is started, and runs until there's no more work left
21
+ # for it to handle.
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony/auto_run'
5
+
6
+ sleeper = spin do
7
+ puts 'going to sleep'
8
+ sleep 1
9
+ puts 'woke up'
10
+ end
11
+
12
+ # One way to synchronize coprocesses is by using `Coprocess#await`, which blocks
13
+ # until the coprocess has finished running or has been interrupted.
14
+ waiter = spin do
15
+ puts 'waiting for coprocess to terminate'
16
+ sleeper.await
17
+ puts 'done waiting'
18
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony/auto_run'
5
+
6
+ # Let's see how a long-running blocking operation can be interrupted. Polyphony
7
+ # provides several APIs for interrupting an ongoing operation, and distinguishes
8
+ # between two different types of interruptions: *cancel* and *move on*. A
9
+ # *cancel* will interrupt an ongoing operation and raise an exception. A *move
10
+ # on* will interrupt an ongoing operation without raising an exception,
11
+ # optionally returning an arbitrary value as the result of that operation.
12
+
13
+ def nap(tag, t)
14
+ puts "#{Time.now} #{tag} napping for #{t} seconds..."
15
+ sleep t
16
+ ensure
17
+ puts "#{Time.now} #{tag} done napping"
18
+ end
19
+
20
+ # The Kernel#cancel_after interrupts a blocking operation by raising a
21
+ # Polyphony::Cancel exception after the given timeout
22
+ spin do
23
+ # cancel after 1 second
24
+ cancel_after(1) { nap(:cancel, 2) }
25
+ rescue Polyphony::Cancel => e
26
+ puts "got exception: #{e}"
27
+ end
28
+
29
+ spin do
30
+ # move on after 1 second
31
+ move_on_after(1) do
32
+ nap(:move_on, 2)
33
+ end
34
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+
5
+ # Notice we require 'polyphony' and not 'polyphony/auto_run'
6
+ require 'polyphony'
7
+
8
+ def nap(tag, t)
9
+ puts "#{Time.now} #{tag} napping for #{t} seconds..."
10
+ sleep t
11
+ puts "#{Time.now} #{tag} done napping"
12
+ end
13
+
14
+ spin { nap(:a, 1) }
15
+
16
+ # If polyphony/auto_run has not been `require`d, the reactor fiber needs to be
17
+ # started manually. This is done by transferring control to it using `suspend`:
18
+ suspend
@@ -0,0 +1,34 @@
1
+ require 'fiber'
2
+
3
+ def mem_usage
4
+ `ps -o rss #{$$}`.strip.split.last.to_i
5
+ end
6
+
7
+ def calculate_fiber_memory_cost(count)
8
+ GC.disable
9
+ rss0 = mem_usage
10
+ count.times { Fiber.new { sleep 1 } }
11
+ rss1 = mem_usage
12
+ GC.start
13
+ cost = (rss1 - rss0).to_f / count
14
+
15
+ puts "fiber memory cost: #{cost}KB"
16
+ end
17
+
18
+ calculate_fiber_memory_cost(10000)
19
+
20
+ require 'bundler/setup'
21
+ require 'polyphony'
22
+
23
+ def calculate_coprocess_memory_cost(count)
24
+ GC.disable
25
+ rss0 = mem_usage
26
+ count.times { spin { :foo } }
27
+ rss1 = mem_usage
28
+ GC.start
29
+ cost = (rss1 - rss0).to_f / count
30
+
31
+ puts "coprocess memory cost: #{cost}KB"
32
+ end
33
+
34
+ calculate_coprocess_memory_cost(10000)
@@ -2,7 +2,6 @@
2
2
 
3
3
  require 'bundler/setup'
4
4
  require 'polyphony/auto_run'
5
- require 'polyphony/extensions/backtrace'
6
5
 
7
6
  def error(t)
8
7
  raise "hello #{t}"
@@ -2,7 +2,6 @@
2
2
 
3
3
  require 'bundler/setup'
4
4
  require 'polyphony/auto_run'
5
- require 'polyphony/extensions/backtrace'
6
5
 
7
6
  def foo
8
7
  spin do
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony/auto_run'
5
+
6
+ waiter = spin do
7
+ puts 'Waiting for HUP'
8
+ Polyphony.wait_for_signal('SIGHUP')
9
+ puts 'Got HUP'
10
+ end
11
+
12
+ sleep 1
13
+ puts 'Sending HUP'
14
+ Process.kill('SIGHUP', Process.pid)
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony/auto_run'
5
+ require 'polyphony/http'
6
+
7
+ opts = {
8
+ reuse_addr: true,
9
+ dont_linger: true
10
+ }
11
+
12
+ server = spin do
13
+ Polyphony::HTTP::Server.serve('0.0.0.0', 1234, opts) do |req|
14
+ req.respond("Hello world!\n")
15
+ end
16
+ end
17
+
18
+ trap('SIGHUP') do
19
+ puts 'got hup'
20
+ server.interrupt
21
+ end
22
+
23
+ puts "pid: #{Process.pid}"
24
+ puts 'Send HUP to stop gracefully'
25
+ puts 'Listening on port 1234...'
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony/http'
5
+
6
+ puts "pid: #{Process.pid}"
7
+ puts 'Listening on port 1234...'
8
+
9
+ Polyphony::HTTP::Server.serve('0.0.0.0', 1234) do |req|
10
+ req.respond("Hello world!\n")
11
+ end
@@ -61,7 +61,7 @@ spin do
61
61
  end
62
62
  end
63
63
 
64
- Polyphony.trap(:int) do
64
+ trap(:int) do
65
65
  puts 'bye...'
66
66
  exit!
67
67
  end
data/ext/gyro/async.c CHANGED
@@ -3,7 +3,6 @@
3
3
  struct Gyro_Async {
4
4
  struct ev_async ev_async;
5
5
  int active;
6
- VALUE callback;
7
6
  VALUE fiber;
8
7
  };
9
8
 
@@ -18,8 +17,6 @@ static size_t Gyro_Async_size(const void *ptr);
18
17
  /* Methods */
19
18
  static VALUE Gyro_Async_initialize(VALUE self);
20
19
 
21
- static VALUE Gyro_Async_start(VALUE self);
22
- static VALUE Gyro_Async_stop(VALUE self);
23
20
  static VALUE Gyro_Async_signal(VALUE self);
24
21
  static VALUE Gyro_Async_await(VALUE self);
25
22
 
@@ -31,8 +28,6 @@ void Init_Gyro_Async() {
31
28
  rb_define_alloc_func(cGyro_Async, Gyro_Async_allocate);
32
29
 
33
30
  rb_define_method(cGyro_Async, "initialize", Gyro_Async_initialize, 0);
34
- rb_define_method(cGyro_Async, "start", Gyro_Async_start, 0);
35
- rb_define_method(cGyro_Async, "stop", Gyro_Async_stop, 0);
36
31
  rb_define_method(cGyro_Async, "signal!", Gyro_Async_signal, 0);
37
32
  rb_define_method(cGyro_Async, "await", Gyro_Async_await, 0);
38
33
  }
@@ -51,9 +46,6 @@ static VALUE Gyro_Async_allocate(VALUE klass) {
51
46
 
52
47
  static void Gyro_Async_mark(void *ptr) {
53
48
  struct Gyro_Async *async = ptr;
54
- if (async->callback != Qnil) {
55
- rb_gc_mark(async->callback);
56
- }
57
49
  if (async->fiber != Qnil) {
58
50
  rb_gc_mark(async->fiber);
59
51
  }
@@ -76,16 +68,11 @@ static VALUE Gyro_Async_initialize(VALUE self) {
76
68
  struct Gyro_Async *async;
77
69
  GetGyro_Async(self, async);
78
70
 
79
- if (rb_block_given_p()) {
80
- async->callback = rb_block_proc();
81
- }
82
71
  async->fiber = Qnil;
72
+ async->active = 0;
83
73
 
84
74
  ev_async_init(&async->ev_async, Gyro_Async_callback);
85
75
 
86
- async->active = 1;
87
- ev_async_start(EV_DEFAULT, &async->ev_async);
88
-
89
76
  return Qnil;
90
77
  }
91
78
 
@@ -94,38 +81,15 @@ void Gyro_Async_callback(struct ev_loop *ev_loop, struct ev_async *ev_async, int
94
81
  struct Gyro_Async *async = (struct Gyro_Async*)ev_async;
95
82
 
96
83
  if (async->fiber != Qnil) {
84
+ ev_async_stop(EV_DEFAULT, ev_async);
97
85
  async->active = 0;
98
86
  fiber = async->fiber;
99
87
  async->fiber = Qnil;
100
88
  SCHEDULE_FIBER(fiber, 0);
101
89
  }
102
- else if (async->callback != Qnil) {
103
- rb_funcall(async->callback, ID_call, 1, Qtrue);
104
- }
105
- }
106
-
107
- static VALUE Gyro_Async_start(VALUE self) {
108
- struct Gyro_Async *async;
109
- GetGyro_Async(self, async);
110
-
111
- if (!async->active) {
112
- ev_async_start(EV_DEFAULT, &async->ev_async);
113
- async->active = 1;
114
- }
115
-
116
- return self;
117
- }
118
-
119
- static VALUE Gyro_Async_stop(VALUE self) {
120
- struct Gyro_Async *async;
121
- GetGyro_Async(self, async);
122
-
123
- if (async->active) {
124
- ev_async_stop(EV_DEFAULT, &async->ev_async);
125
- async->active = 0;
90
+ else {
91
+ ev_async_stop(EV_DEFAULT, ev_async);
126
92
  }
127
-
128
- return self;
129
93
  }
130
94
 
131
95
  static VALUE Gyro_Async_signal(VALUE self) {
data/ext/gyro/child.c CHANGED
@@ -5,7 +5,6 @@ struct Gyro_Child {
5
5
  int active;
6
6
  int pid;
7
7
  VALUE self;
8
- VALUE callback;
9
8
  VALUE fiber;
10
9
  };
11
10
 
@@ -20,8 +19,6 @@ static size_t Gyro_Child_size(const void *ptr);
20
19
  /* Methods */
21
20
  static VALUE Gyro_Child_initialize(VALUE self, VALUE pid);
22
21
 
23
- static VALUE Gyro_Child_start(VALUE self);
24
- static VALUE Gyro_Child_stop(VALUE self);
25
22
  static VALUE Gyro_Child_await(VALUE self);
26
23
 
27
24
  void Gyro_Child_callback(struct ev_loop *ev_loop, struct ev_child *child, int revents);
@@ -32,8 +29,6 @@ void Init_Gyro_Child() {
32
29
  rb_define_alloc_func(cGyro_Child, Gyro_Child_allocate);
33
30
 
34
31
  rb_define_method(cGyro_Child, "initialize", Gyro_Child_initialize, 1);
35
- rb_define_method(cGyro_Child, "start", Gyro_Child_start, 0);
36
- rb_define_method(cGyro_Child, "stop", Gyro_Child_stop, 0);
37
32
  rb_define_method(cGyro_Child, "await", Gyro_Child_await, 0);
38
33
  }
39
34
 
@@ -51,9 +46,6 @@ static VALUE Gyro_Child_allocate(VALUE klass) {
51
46
 
52
47
  static void Gyro_Child_mark(void *ptr) {
53
48
  struct Gyro_Child *child = ptr;
54
- if (child->callback != Qnil) {
55
- rb_gc_mark(child->callback);
56
- }
57
49
  if (child->fiber != Qnil) {
58
50
  rb_gc_mark(child->fiber);
59
51
  }
@@ -80,7 +72,6 @@ static VALUE Gyro_Child_initialize(VALUE self, VALUE pid) {
80
72
  GetGyro_Child(self, child);
81
73
 
82
74
  child->self = self;
83
- child->callback = Qnil;
84
75
  child->fiber = Qnil;
85
76
  child->pid = NUM2INT(pid);
86
77
  child->active = 0;
@@ -105,39 +96,6 @@ void Gyro_Child_callback(struct ev_loop *ev_loop, struct ev_child *ev_child, int
105
96
  child->fiber = Qnil;
106
97
  SCHEDULE_FIBER(fiber, 1, resume_value);
107
98
  }
108
- else if (child->callback != Qnil) {
109
- rb_funcall(child->callback, ID_call, 1, resume_value);
110
- }
111
- }
112
-
113
- static VALUE Gyro_Child_start(VALUE self) {
114
- struct Gyro_Child *child;
115
- GetGyro_Child(self, child);
116
-
117
- if (rb_block_given_p()) {
118
- child->callback = rb_block_proc();
119
- }
120
-
121
- if (!child->active) {
122
- ev_child_start(EV_DEFAULT, &child->ev_child);
123
- child->active = 1;
124
- Gyro_add_watcher_ref(self);
125
- }
126
-
127
- return self;
128
- }
129
-
130
- static VALUE Gyro_Child_stop(VALUE self) {
131
- struct Gyro_Child *child;
132
- GetGyro_Child(self, child);
133
-
134
- if (child->active) {
135
- ev_child_stop(EV_DEFAULT, &child->ev_child);
136
- child->active = 0;
137
- Gyro_del_watcher_ref(self);
138
- }
139
-
140
- return self;
141
99
  }
142
100
 
143
101
  static VALUE Gyro_Child_await(VALUE self) {