polyphony 0.28 → 0.29

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +0 -4
  3. data/CHANGELOG.md +12 -0
  4. data/Gemfile.lock +1 -1
  5. data/LICENSE +1 -1
  6. data/README.md +23 -21
  7. data/Rakefile +2 -0
  8. data/TODO.md +0 -3
  9. data/docs/_includes/prevnext.html +17 -0
  10. data/docs/_layouts/default.html +106 -0
  11. data/docs/_sass/custom/custom.scss +21 -0
  12. data/docs/faq.md +13 -10
  13. data/docs/getting-started/installing.md +2 -0
  14. data/docs/getting-started/tutorial.md +5 -3
  15. data/docs/index.md +4 -5
  16. data/docs/technical-overview/concurrency.md +21 -19
  17. data/docs/technical-overview/design-principles.md +12 -20
  18. data/docs/technical-overview/exception-handling.md +70 -1
  19. data/docs/technical-overview/extending.md +1 -0
  20. data/docs/technical-overview/fiber-scheduling.md +109 -88
  21. data/docs/user-guide/all-about-timers.md +126 -0
  22. data/docs/user-guide/web-server.md +2 -2
  23. data/docs/user-guide.md +1 -1
  24. data/examples/core/xx-deferring-an-operation.rb +2 -2
  25. data/examples/core/xx-sleep-forever.rb +9 -0
  26. data/examples/core/xx-snooze-starve.rb +16 -0
  27. data/examples/core/xx-spin_error_backtrace.rb +1 -1
  28. data/examples/core/xx-trace.rb +1 -2
  29. data/examples/core/xx-worker-thread.rb +30 -0
  30. data/examples/io/xx-happy-eyeballs.rb +37 -0
  31. data/ext/gyro/gyro.c +8 -3
  32. data/ext/gyro/gyro.h +7 -1
  33. data/ext/gyro/queue.c +35 -3
  34. data/ext/gyro/selector.c +31 -2
  35. data/ext/gyro/thread.c +18 -16
  36. data/lib/polyphony/core/global_api.rb +0 -1
  37. data/lib/polyphony/core/thread_pool.rb +5 -0
  38. data/lib/polyphony/core/throttler.rb +0 -1
  39. data/lib/polyphony/extensions/fiber.rb +14 -3
  40. data/lib/polyphony/extensions/thread.rb +16 -4
  41. data/lib/polyphony/irb.rb +7 -1
  42. data/lib/polyphony/trace.rb +44 -11
  43. data/lib/polyphony/version.rb +1 -1
  44. data/lib/polyphony.rb +1 -0
  45. data/test/helper.rb +1 -3
  46. data/test/test_async.rb +1 -1
  47. data/test/test_cancel_scope.rb +3 -3
  48. data/test/test_fiber.rb +157 -54
  49. data/test/test_global_api.rb +51 -1
  50. data/test/test_gyro.rb +4 -156
  51. data/test/test_io.rb +1 -1
  52. data/test/test_supervisor.rb +2 -2
  53. data/test/test_thread.rb +72 -1
  54. data/test/test_thread_pool.rb +6 -2
  55. data/test/test_throttler.rb +7 -5
  56. data/test/test_trace.rb +6 -6
  57. metadata +10 -5
  58. data/examples/core/xx-extended_fibers.rb +0 -150
  59. data/examples/core/xx-mt-scheduler.rb +0 -349
@@ -4,19 +4,72 @@ title: How Fibers are Scheduled
4
4
  nav_order: 3
5
5
  parent: Technical Overview
6
6
  permalink: /technical-overview/fiber-scheduling/
7
+ prev_title: Concurrency the Easy Way
8
+ next_title: Exception Handling
7
9
  ---
10
+
8
11
  # How Fibers are Scheduled
9
12
 
13
+ Before we discuss how fibers are scheduled in Polyphony, let's examine how
14
+ switching between fibers works in Ruby.
15
+
10
16
  Ruby provides two mechanisms for transferring control between fibers:
11
17
  `Fiber#resume` /`Fiber.yield` and `Fiber#transfer`. The first is inherently
12
18
  asymmetric and is famously used for implementing generators and [resumable
13
19
  enumerators](https://blog.appsignal.com/2018/11/27/ruby-magic-fibers-and-enumerators-in-ruby.html).
14
- Another limiting factor of using resume / yield is that the main fiber can't
15
- yield away, limiting its usability as a resumable fiber.
20
+ Here's a small example:
21
+
22
+ ```ruby
23
+ fib = Fiber.new do
24
+ x, y = 0, 1
25
+ loop do
26
+ Fiber.yield y
27
+ x, y = y, x + y
28
+ end
29
+ end
30
+
31
+ 10.times { puts fib.resume }
32
+ ```
33
+
34
+ Another implication of using resume / yield is that the main fiber can't yield
35
+ away, meaning we cannot pause the main fiber using `Fiber.yield`.
36
+
37
+ The other fiber control mechanism, using `Fiber#transfer`, is fully symmetric:
38
+
39
+ ```ruby
40
+ require 'fiber'
41
+
42
+ ping = Fiber.new { loop { puts "ping"; pong.transfer } }
43
+ pong = Fiber.new { loop { puts "pong"; ping.transfer } }
44
+ ping.transfer
45
+ ```
46
+
47
+ `Fiber#transform` also allows using the main fiber as a general purpose
48
+ resumable execution context. Polyphony uses `Fiber#transfer` exclusively for
49
+ scheduling fibers.
50
+
51
+ ## The Different Fiber states
16
52
 
17
- The second mechanism, using `Fiber#transfer`, is completely symmetric and allows
18
- use of the main fiber as a general purpose resumable execution context.
19
- Polyphony uses `Fiber#transfer` exclusively for scheduling fibers.
53
+ In Polyphony, each fiber has one four possible states:
54
+
55
+ A new fiber will start in a `:waiting` state. The `#spin` global method will
56
+ create the fiber and schedule it for execution, marking it as `:runnable`. When
57
+ the fiber is run, it is in a `:running` state. Finally, when the fiber has
58
+ terminated, it transitions to the `:dead` state.
59
+
60
+ Whenever a fiber performs a blocking operation - such as waiting for a timer to
61
+ elapse, or for a socket to become readable, or for a child process to terminate -
62
+ it transitions to a `:waiting` state. Once the timer has elapsed, the socket
63
+ has become readable, or the child process has terminated, the fiber is marked as
64
+ `:runnable`. It then waits its turn to run.
65
+
66
+ ## Switchpoints
67
+
68
+ A switchpoint is any point in time at which control might switch from the
69
+ currently running fiber to another fiber that is `:runnable`. This usually
70
+ occurs when the currently running fiber starts a blocking operation. It also
71
+ occurs when the running fiber has yielded control using `#snooze` or `#suspend`.
72
+ A Switchpoint will also occur when the currently running fiber has terminated.
20
73
 
21
74
  ## Scheduler-less scheduling
22
75
 
@@ -27,15 +80,14 @@ reactor-based libraries and frameworks, such as `nio4r`, `EventMachine` or
27
80
  user-supplied code *from inside the loop*. In Polyphony, however, we have chosen
28
81
  a programming model that does not use a loop to schedule fibers. In fact, in
29
82
  Polyphony there's no such thing as a reactor loop, and there's no *scheduler*
30
- running on a separate execution context.
83
+ per se running on a separate execution context.
31
84
 
32
- Instead, Polyphony maintains for each thread a list of scheduled fibers, fibers
33
- that will be resumed once the current fiber yields control, which will occur on
34
- every blocking operation. If no fibers are scheduled, the libev event reactor
35
- will be ran until one or more events have occurred. Events are handled by adding
36
- the corresponding fibers onto the *scheduled fibers* list. Finally, control is
37
- transferred to the first scheduled fiber, which will run until it blocks or
38
- terminates, at which point control is transferred to the next scheduled fiber.
85
+ Instead, Polyphony maintains for each thread a run queue, a list of `:runnable`
86
+ fibers. If no fiber is `:runnable`, the libev event reactor will be ran until
87
+ one or more events have occurred. Events are handled by adding the corresponding
88
+ fibers onto the run queue. Finally, control is transferred to the first fiber on
89
+ the run queue, which will run until it blocks or terminates, at which point
90
+ control is transferred to the next runnable fiber.
39
91
 
40
92
  This approach has numerous benefits:
41
93
 
@@ -45,89 +97,25 @@ This approach has numerous benefits:
45
97
  scheduling code.
46
98
  - Much less time is spent in reactor loop callbacks, letting the reactor loop
47
99
  run more efficiently.
48
- - Fibers are resumed outside of the event reactor code, making it easier to
100
+ - Fibers are switched outside of the event reactor code, making it easier to
49
101
  avoid race conditions and unexpected behaviours.
50
102
 
51
- ## A concrete example - fiber scheduling in an echo server
52
-
53
- Here's a barebones echo server written in Polyphony:
54
-
55
- ```ruby
56
- require 'polyphony'
57
-
58
- server = TCPServer.open('127.0.0.1', 8081)
59
- while (client = server.accept)
60
- spin do
61
- while (data = client.gets)
62
- client << ">>>you sent: #{data}"
63
- break if data =~ /quit/i
64
- end
65
- end
66
- end
67
- ```
68
-
69
- Let's examine the the flow of control in our echo server program:
70
-
71
- <p class="img-figure"><img src="../../assets/img/echo-fibers.svg"></p>
72
-
73
- > In the above figure, the fat blue dots represents moments at which fibers can
74
- > be switched. The light blue horizontal arrows represent switching from one
75
- > fiber to another. The blue vertical lines represent a train of execution on a
76
- > single fiber.
77
-
78
- - The main fiber (fiber 1) runs a loop waiting for incoming connections.
79
- - The call to `server.accept` blocks, and an I/O event watcher is set up. The
80
- main fiber is suspended.
81
- - Since there's no other runnable fiber, the associated event loop is run.
82
- - An event is generated for the server's socket, and fiber 1 is added to the run
83
- queue.
84
- - Fiber 1 is pulled from the run queue and resumed. The `server.accept` call
85
- returns a new client socket (an instance of `TCPSocket`).
86
- - Fiber 1 continues by `spin`ning up a new fiber for handling the client. The
87
- new fiber (fiber 2) is added to the run queue.
88
- - Fiber 1 goes back to waiting for an incoming connection by calling
89
- `server.accept` again. The call blocks, the main fiber suspends, and switches
90
- to the next fiber in the run queue, fiber 2.
91
- - Fiber 2 starts a loop and calls `client.gets`. The call blocks, an I/O event
92
- watcher is set up, and the fiber suspends.
93
- - Since no other fiber is runnable, the event loop is run, waiting for at least
94
- one event to fire.
95
- - An event fires for the acceptor socket, and fiber 1 is put on the run queue.
96
- - An event fires for the client socket, and fiber 2 is put on the run queue.
97
- - Fiber 1 is resumed and spins up a new client handling fiber (fiber 3), which
98
- is put on the run queue.
99
- - Fiber 1 calls `server.accept` again, and suspends, switching to the next
100
- runnable fiber, fiber 2.
101
- - Fiber 2 resumes and completes reading a line from the socket.
102
- - Fiber 2 calls `client.<<`, blocks, sets up an I/O watcher, and suspends,
103
- switching to the next runnable fiber, fiber 3.
104
- - Fiber 3 resumes and calls `client.gets`. The call blocks, an I/O event watcher
105
- is set up, and the fiber suspends.
106
-
107
- ## Fiber states
108
-
109
- In Polyphony, each fiber has one of the following states at any given moment:
110
-
111
- - `:running`: this is the state of the currently running fiber.
112
- - `:dead`: the fiber has terminated.
113
- - `:waiting`: the fiber is suspended, waiting for something to wake it up.
114
- - `:runnable`: the fiber is on the run queue and will be run shortly.
115
-
116
103
  ## Fiber scheduling and fiber switching
117
104
 
118
105
  The Polyphony scheduling model makes a clear separation between the scheduling
119
106
  of fibers and the switching of fibers. The scheduling of fibers is the act of
120
- marking the fiber to be run at the earliest opportunity, but not immediately.
121
- The switching of fibers is the act of transferring control to another fiber, in
122
- this case the first fiber in the list of *currently* scheduled fibers.
107
+ marking the fiber as `:runnable`, to be run at the earliest opportunity, but not
108
+ immediately. The switching of fibers is the act of actually transferring control
109
+ to another fiber, namely the first fiber in the run queue.
123
110
 
124
111
  The scheduling of fibers can occur at any time, either as a result of an event
125
112
  occuring, an exception being raised, or using `Fiber#schedule`. The switching of
126
- fibers will occur only when a blocking operation is started, or upon calling
127
- `Fiber#suspend` or `Fiber#snooze`. In order to switch to a scheduled fiber,
128
- Polyphony uses `Fiber#transfer`.
113
+ fibers will occur only when the currently running fiber has reached a
114
+ switchpoint, e.g. when a blocking operation is started, or upon calling
115
+ `Fiber#suspend` or `Fiber#snooze`. As mentioned earlier, in order to switch to a
116
+ scheduled fiber, Polyphony uses `Fiber#transfer`.
129
117
 
130
- When a fiber terminates, any other scheduled fibers will be run. If no fibers
118
+ When a fiber terminates, any other runnable fibers will be run. If no fibers
131
119
  are waiting and the main fiber is done running, the Ruby process will terminate.
132
120
 
133
121
  ## Interrupting blocking operations
@@ -185,9 +173,42 @@ by scheduling the corresponding fiber with an exception:
185
173
  ```ruby
186
174
  def timeout(duration)
187
175
  fiber = Fiber.current
188
- watcher = Gyro::Timer.new(duration) { fiber.transfer(TimerException.new) }
176
+ interrupter = spin do
177
+ Gyro::Timer.new(duration, 0).await
178
+ fiber.transfer(TimerException.new)
179
+ end
189
180
  yield
190
181
  ensure
191
- watcher.active = false
182
+ interrupter.stop
192
183
  end
193
184
  ```
185
+
186
+ ## Fiber Scheduling in a Multithreaded Program
187
+
188
+ Polyphony performs fiber scheduling separately for each thread. Each thread,
189
+ therefore, will be able to run multiple fibers independently from other threads.
190
+
191
+ ## The fiber scheduling algorithm in full
192
+
193
+ Here is the summary of the Polyphony scheduling algorithm:
194
+
195
+ - loop
196
+ - pull first runnable fiber from run queue
197
+ - if runnable fiber is not nil
198
+ - if the ref count greater than 0
199
+ - increment the run_no_wait counter
200
+ - if the run_no_wait counter is greater than 10 and greater than the run
201
+ queue length
202
+ - reset the run_no_wait counter
203
+ - run the event loop once without waiting for events (using
204
+ `EVRUN_NOWAIT`)
205
+ - break out of the loop
206
+ - if the ref count is 0
207
+ - break out of the loop
208
+ - run the event loop until one or more events are generated (using
209
+ `EVRUN_ONCE`)
210
+ - if next runnable fiber is nil
211
+ - return
212
+ - get scheduled resume value for next runnable fiber
213
+ - mark the next runnable fiber as not runnable
214
+ - switch to the next runnable fiber using `Fiber#transfer`
@@ -0,0 +1,126 @@
1
+ ---
2
+ layout: page
3
+ title: All About Timers
4
+ nav_order: 1
5
+ parent: User Guide
6
+ permalink: /user-guide/all-about-timers/
7
+ ---
8
+ # All About Timers
9
+
10
+ Timers form a major part of writing dynamic concurrent programs. They allow
11
+ programmers to create delays and to perform recurring operations with a
12
+ controllable frequency. Crucially, they also enable the implementation of
13
+ timeouts, which are an important aspect of concurrent programming.
14
+
15
+ ## Sleeping
16
+
17
+ Sometimes, your code needs to wait for a certain period of time. For example,
18
+ implementing a retry mechanism for failed HTTP requests might involve waiting
19
+ for a few seconds before retrying. Polyphony patches the `Kernel#sleep` method
20
+ to be fiber-aware, that is to yield control of execution while waiting for a
21
+ timer to elapse.
22
+
23
+ ```ruby
24
+ # This is a naive retry implementation
25
+ def fetch(url)
26
+ fetch_url(url)
27
+ rescue
28
+ sleep 1
29
+ retry
30
+ end
31
+ ```
32
+
33
+ ## Sleeping Forever
34
+
35
+ The `#sleep` method can also be used to sleep forever, if no argument is given:
36
+
37
+ ```ruby
38
+ puts "Go to sleep"
39
+ sleep
40
+ puts "Woke up" # this line will not be executed
41
+ ```
42
+
43
+ The `#sleep` forever call can be used for example in the main fiber when we do
44
+ all our work in other fibers, since once the main fiber terminates the program
45
+ exits.
46
+
47
+ ## Doing Work Later
48
+
49
+ While `#sleep` allows you to block execution of the current fiber, sometimes you
50
+ want to perform some work later, while not blocking the current fiber. This is done simply by spinning off another fiber:
51
+
52
+ ```ruby
53
+ do_some_stuff
54
+ spin do
55
+ sleep 3
56
+ do_stuff_later
57
+ end
58
+ do_some_more_stuff
59
+ ```
60
+
61
+ ## Using timeouts
62
+
63
+ Polyphony provides the following global methods for using timeouts:
64
+
65
+ - `#move_on_after` - used for cancelling an operation after a certain period of time without raising an exception:
66
+
67
+ ```ruby
68
+ move_on_after 1 do
69
+ sleep 60
70
+ end
71
+ ```
72
+
73
+ This method also takes an optional return value argument:
74
+
75
+ ```ruby
76
+ move_on_after 1, with_value: 'bar' do
77
+ sleep 60
78
+ 'foo'
79
+ end #=> 'bar'
80
+ ```
81
+
82
+ - `#cancel_after` - used for cancelling an operation after a certain period of time with a `Cancel` exception:
83
+
84
+ ```ruby
85
+ cancel_after 1 do
86
+ sleep 60
87
+ end #=> raises Cancel
88
+ ```
89
+
90
+ Polyphony also provides a fiber-aware version of the core Ruby `Timeout` API, which may be used directly or indirectly to interrupt blocking operations.
91
+
92
+ ## Using Raw Timers
93
+
94
+ Polyphony implements timers through the `Gyro::Timer` class, which encapsulates
95
+ libev timer watchers. Using `Gyro::Timer` you can create both one-time and
96
+ recurring timers:
97
+
98
+ ```ruby
99
+ # Create a one-time timer
100
+ one_time = Gyro::Timer.new(1, 0)
101
+
102
+ # Create a recurring timer
103
+ recurring = Gyro::Timer.new(0.5, 1.5)
104
+ ```
105
+
106
+ Once your timer is created, you can wait for it using the `#await` method:
107
+
108
+ ```ruby
109
+ def delay(duration)
110
+ timer = Gyro::Timer.new(duration, 0)
111
+ timer.await
112
+ end
113
+ ```
114
+
115
+ Waiting for the timer will *block* the current timer. This means that if you
116
+ want to do other work while waiting for the timer, you need to put it on a
117
+ different fiber:
118
+
119
+ ```ruby
120
+ timer = Gyro::Timer.new(3, 0)
121
+ spin {
122
+ sleep 3
123
+ do_something_else
124
+ }
125
+ do_blocking_operation
126
+ ```
@@ -37,7 +37,7 @@ Note that requests are handled using a callback block which takes a single
37
37
  argument. The `request` object provides the entire API for responding to the
38
38
  client.
39
39
 
40
- Each client connection will be handled in a separate coprocess, allowing
40
+ Each client connection will be handled in a separate fiber, allowing
41
41
  concurrent processing of incoming requests.
42
42
 
43
43
  ## HTTP 2 support
@@ -47,7 +47,7 @@ HTTP 2 support is baked in to the server, which supports both HTTP 2 upgrades
47
47
  in a completely effortless manner.
48
48
 
49
49
  Since HTTP 2 connections are multiplexed, allowing multiple concurrent requests
50
- on a single connection, each HTTP 2 stream is handled in a separate coprocess.
50
+ on a single connection, each HTTP 2 stream is handled in a separate fiber.
51
51
 
52
52
  ## TLS termination
53
53
 
data/docs/user-guide.md CHANGED
@@ -6,5 +6,5 @@ has_children: true
6
6
  section: true
7
7
  has_toc: false
8
8
  nav_order: 4
9
- section_link: /user-guide/web-server
9
+ section_link: /user-guide/all-about-timers
10
10
  ---
@@ -3,9 +3,9 @@
3
3
  require 'bundler/setup'
4
4
  require 'polyphony'
5
5
 
6
- defer do
6
+ spin do
7
7
  puts 'two'
8
- defer { puts 'four' }
8
+ spin { puts 'four' }
9
9
  puts 'three'
10
10
  end
11
11
 
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ Exception.__disable_sanitized_backtrace__ = true
7
+
8
+ puts "press Ctrl-C!"
9
+ sleep
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ Exception.__disable_sanitized_backtrace__ = true
7
+
8
+ f = spin_loop {
9
+ snooze
10
+ }
11
+
12
+ puts 'going to sleep...'
13
+ sleep 1
14
+ puts 'woke up'
15
+ f.stop
16
+
@@ -9,7 +9,7 @@ end
9
9
 
10
10
  def deferred_error(t)
11
11
  puts "deferred_error"
12
- defer { de2(t) }
12
+ spin { de2(t) }
13
13
  end
14
14
 
15
15
  def de2(t)
@@ -5,13 +5,12 @@ require 'polyphony'
5
5
 
6
6
  Exception.__disable_sanitized_backtrace__ = true
7
7
 
8
- Gyro.trace(true)
9
8
 
10
9
  sleep 0
11
10
  $records = []
12
11
 
12
+ Gyro.trace(true)
13
13
  trace = Polyphony::Trace.new { |r| $records << r }
14
-
15
14
  trace.enable
16
15
 
17
16
  f2 = spin(:f2) { 3.times { sleep 0.1 } }
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ def do_work(client)
7
+ result = yield
8
+ client.schedule(result)
9
+ rescue Exception => e
10
+ client.schedule(e)
11
+ end
12
+
13
+ $worker = Thread.new do
14
+ Fiber.current.tag = :worker
15
+ loop do
16
+ client, block = receive
17
+ do_work(client, &block)
18
+ end
19
+ rescue Exception => e
20
+ p e
21
+ end
22
+
23
+ def process(&block)
24
+ $worker.main_fiber << [Fiber.current, block]
25
+ receive
26
+ end
27
+
28
+ sleep 0.1
29
+
30
+ p process { 1 + 1 }
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ # idea taken from the example given in trio:
4
+ # https://www.youtube.com/watch?v=oLkfnc_UMcE
5
+
6
+ require 'bundler/setup'
7
+ require 'polyphony'
8
+
9
+ def try_connect(target, supervisor)
10
+ puts "trying #{target[2]}"
11
+ socket = Polyphony::Net.tcp_connect(target[2], 80)
12
+ # connection successful
13
+ supervisor.stop([target[2], socket])
14
+ rescue IOError, SystemCallError
15
+ # ignore error
16
+ end
17
+
18
+ def happy_eyeballs(hostname, port, max_wait_time: 0.025)
19
+ targets = Socket.getaddrinfo(hostname, port, :INET, :STREAM)
20
+ t0 = Time.now
21
+ cancel_after(5) do
22
+ success = supervise do |supervisor|
23
+ targets.each_with_index do |t, idx|
24
+ sleep(max_wait_time) if idx > 0
25
+ supervisor.spin { try_connect(t, supervisor) }
26
+ end
27
+ end
28
+ if success
29
+ puts format('success: %s (%.3fs)', success[0], Time.now - t0)
30
+ else
31
+ puts "timed out (#{Time.now - t0}s)"
32
+ end
33
+ end
34
+ end
35
+
36
+ # Let's try it out:
37
+ happy_eyeballs('debian.org', 'https')
data/ext/gyro/gyro.c CHANGED
@@ -11,6 +11,7 @@ ID ID_inspect;
11
11
  ID ID_new;
12
12
  ID ID_raise;
13
13
  ID ID_ivar_running;
14
+ ID ID_ivar_thread;
14
15
  ID ID_runnable;
15
16
  ID ID_runnable_value;
16
17
  ID ID_size;
@@ -117,10 +118,13 @@ static VALUE Fiber_state(VALUE self) {
117
118
  }
118
119
 
119
120
  inline void Gyro_schedule_fiber(VALUE fiber, VALUE value) {
120
- if (__tracing_enabled__) {
121
- rb_funcall(rb_cObject, ID_fiber_trace, 3, SYM_fiber_schedule, fiber, value);
121
+ VALUE thread = rb_ivar_get(fiber, ID_ivar_thread);
122
+ if (thread != Qnil) {
123
+ Thread_schedule_fiber(thread, fiber, value);
124
+ }
125
+ else {
126
+ rb_warn("No thread set for fiber");
122
127
  }
123
- Thread_schedule_fiber(rb_thread_current(), fiber, value);
124
128
  }
125
129
 
126
130
  VALUE Gyro_trace(VALUE self, VALUE enabled) {
@@ -154,6 +158,7 @@ void Init_Gyro() {
154
158
  ID_empty = rb_intern("empty?");
155
159
  ID_inspect = rb_intern("inspect");
156
160
  ID_ivar_running = rb_intern("@running");
161
+ ID_ivar_thread = rb_intern("@thread");
157
162
  ID_new = rb_intern("new");
158
163
  ID_pop = rb_intern("pop");
159
164
  ID_push = rb_intern("push");
data/ext/gyro/gyro.h CHANGED
@@ -27,7 +27,8 @@ VALUE IO_read_watcher(VALUE io);
27
27
  VALUE IO_write_watcher(VALUE io);
28
28
  VALUE Gyro_IO_await(VALUE self);
29
29
 
30
- VALUE Gyro_Selector_run(VALUE self);
30
+ VALUE Gyro_Selector_run(VALUE self, VALUE current_fiber);
31
+ void Gyro_Selector_run_no_wait(VALUE self, VALUE current_fiber, long runnable_count);
31
32
 
32
33
  VALUE Gyro_Timer_await(VALUE self);
33
34
 
@@ -36,6 +37,7 @@ void io_set_read_length(VALUE str, long n, int shrinkable);
36
37
  VALUE io_enc_str(VALUE str, rb_io_t *fptr);
37
38
 
38
39
  struct ev_loop *Gyro_Selector_ev_loop(VALUE selector);
40
+ ev_tstamp Gyro_Selector_now(VALUE selector);
39
41
  struct ev_loop *Gyro_Selector_current_thread_ev_loop();
40
42
  long Gyro_Selector_pending_count(VALUE self);
41
43
 
@@ -50,6 +52,9 @@ VALUE Thread_post_fork(VALUE thread);
50
52
 
51
53
  #define OBJ_ID(obj) (NUM2LONG(rb_funcall(obj, rb_intern("object_id"), 0)))
52
54
  #define INSPECT(...) (rb_funcall(rb_cObject, rb_intern("p"), __VA_ARGS__))
55
+ #define FIBER_TRACE(...) if (__tracing_enabled__) { \
56
+ rb_funcall(rb_cObject, ID_fiber_trace, __VA_ARGS__); \
57
+ }
53
58
 
54
59
  extern VALUE mGyro;
55
60
  extern VALUE cGyro_Async;
@@ -65,6 +70,7 @@ extern ID ID_each;
65
70
  extern ID ID_fiber_trace;
66
71
  extern ID ID_inspect;
67
72
  extern ID ID_ivar_running;
73
+ extern ID ID_ivar_thread;
68
74
  extern ID ID_new;
69
75
  extern ID ID_raise;
70
76
  extern ID ID_runnable;
data/ext/gyro/queue.c CHANGED
@@ -78,22 +78,50 @@ VALUE Gyro_Queue_shift(VALUE self) {
78
78
  return Gyro_Async_await(async);
79
79
  }
80
80
 
81
+ VALUE Gyro_Queue_shift_no_wait(VALUE self) {
82
+ struct Gyro_Queue *queue;
83
+ GetGyro_Queue(self, queue);
84
+
85
+ return rb_ary_shift(queue->queue);
86
+ }
87
+
81
88
  VALUE Gyro_Queue_shift_all(VALUE self) {
82
89
  struct Gyro_Queue *queue;
83
90
  GetGyro_Queue(self, queue);
84
91
 
92
+ VALUE old_queue = queue->queue;
93
+ queue->queue = rb_ary_new();
94
+
85
95
  if (rb_block_given_p()) {
86
- while (RARRAY_LEN(queue->queue) > 0) {
87
- rb_yield(rb_ary_shift(queue->queue));
96
+ long len = RARRAY_LEN(old_queue);
97
+ for (long i = 0; i < len; i++) {
98
+ rb_yield(RARRAY_AREF(old_queue, i));
88
99
  }
100
+ // while (RARRAY_LEN(old_queue) > 0) {
101
+ // rb_yield(rb_ary_shift(old_queue));
102
+ // }
103
+ return self;
89
104
  }
90
105
  else {
91
- rb_ary_clear(queue->queue);
106
+ return old_queue;
92
107
  }
108
+ }
109
+
110
+ VALUE Gyro_Queue_clear(VALUE self) {
111
+ struct Gyro_Queue *queue;
112
+ GetGyro_Queue(self, queue);
93
113
 
114
+ rb_ary_clear(queue->queue);
94
115
  return self;
95
116
  }
96
117
 
118
+ VALUE Gyro_Queue_empty_p(VALUE self) {
119
+ struct Gyro_Queue *queue;
120
+ GetGyro_Queue(self, queue);
121
+
122
+ return (RARRAY_LEN(queue->queue) == 0) ? Qtrue : Qfalse;
123
+ }
124
+
97
125
  void Init_Gyro_Queue() {
98
126
  cGyro_Queue = rb_define_class_under(mGyro, "Queue", rb_cData);
99
127
  rb_define_alloc_func(cGyro_Queue, Gyro_Queue_allocate);
@@ -104,6 +132,10 @@ void Init_Gyro_Queue() {
104
132
 
105
133
  rb_define_method(cGyro_Queue, "pop", Gyro_Queue_shift, 0);
106
134
  rb_define_method(cGyro_Queue, "shift", Gyro_Queue_shift, 0);
135
+
136
+ rb_define_method(cGyro_Queue, "shift_no_wait", Gyro_Queue_shift_no_wait, 0);
107
137
 
108
138
  rb_define_method(cGyro_Queue, "shift_each", Gyro_Queue_shift_all, 0);
139
+ rb_define_method(cGyro_Queue, "clear", Gyro_Queue_clear, 0);
140
+ rb_define_method(cGyro_Queue, "empty?", Gyro_Queue_empty_p, 0);
109
141
  }