goru 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -2
- data/README.md +28 -0
- data/lib/goru/bridge.rb +53 -0
- data/lib/goru/bridges/readable.rb +23 -0
- data/lib/goru/bridges/writable.rb +23 -0
- data/lib/goru/reactor.rb +84 -116
- data/lib/goru/routine.rb +17 -7
- data/lib/goru/routines/channel.rb +1 -0
- data/lib/goru/routines/channels/readable.rb +0 -6
- data/lib/goru/routines/channels/writable.rb +0 -6
- data/lib/goru/routines/io.rb +85 -39
- data/lib/goru/scheduler.rb +24 -21
- data/lib/goru/version.rb +1 -1
- metadata +6 -6
- data/lib/goru/routines/bridge.rb +0 -46
- data/lib/goru/routines/bridges/readable.rb +0 -37
- data/lib/goru/routines/bridges/writable.rb +0 -37
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 65d57e96ef8a7bef1abaf43db02e461af067b60238728379b9a5e48cea91c2f9
|
4
|
+
data.tar.gz: feba54796b8120fe753014a9f904c29c3e379ef50910e6cfde0507ca85d4d695
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c65b2730c39ec515c1ab44ee83909c4ea93c53072cb33887aebafaedfeb0131a37c0b3d1bb323b79ce21acdc5248be3fefa3d19c007c9aa5c56217e9a7195541
|
7
|
+
data.tar.gz: f4d7f05d63ad6ce781053946fa0d0e22ec9562b7e20fed83bca8f84441b4f6ec9c30b8b4b79fa7435cc93a76425ec793b6db7ac334aba0fddac84320777226b3
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,21 @@
|
|
1
|
-
## v0.
|
1
|
+
## [v0.3.0](https://github.com/bryanp/goru/releases/tag/v0.3.0)
|
2
2
|
|
3
|
-
*
|
3
|
+
*released on 2023-07-10*
|
4
|
+
|
5
|
+
* `chg` [#16](https://github.com/bryanp/goru/pull/16) Improve control flow ([bryanp](https://github.com/bryanp))
|
6
|
+
* `fix` [#17](https://github.com/bryanp/goru/pull/17) Handle `IOError` from closed selector ([bryanp](https://github.com/bryanp))
|
7
|
+
* `chg` [#15](https://github.com/bryanp/goru/pull/15) Rename `default_scheduler_count` ([bryanp](https://github.com/bryanp))
|
8
|
+
* `chg` [#14](https://github.com/bryanp/goru/pull/14) Improve cold start of scheduler ([bryanp](https://github.com/bryanp))
|
9
|
+
* `chg` [#13](https://github.com/bryanp/goru/pull/13) Go back to selector-based reactor ([bryanp](https://github.com/bryanp))
|
10
|
+
* `chg` [#12](https://github.com/bryanp/goru/pull/12) Refactor bridges (again) ([bryanp](https://github.com/bryanp))
|
11
|
+
* `dep` [#11](https://github.com/bryanp/goru/pull/11) Change responsibilities of routine sleep behavior to be more clear ([bryanp](https://github.com/bryanp))
|
12
|
+
* `chg` [#10](https://github.com/bryanp/goru/pull/10) Optimize how reactor status is set ([bryanp](https://github.com/bryanp))
|
13
|
+
* `chg` [#9](https://github.com/bryanp/goru/pull/9) Refactor bridges ([bryanp](https://github.com/bryanp))
|
14
|
+
* `chg` [#8](https://github.com/bryanp/goru/pull/8) Cleanup finished routines on next tick ([bryanp](https://github.com/bryanp))
|
15
|
+
|
16
|
+
## [v0.2.0](https://github.com/bryanp/goru/releases/tag/v0.2.0)
|
17
|
+
|
18
|
+
*released on 2023-05-01*
|
4
19
|
|
5
20
|
* `fix` [#6](https://github.com/bryanp/goru/pull/6) Finish routines on error ([bryanp](https://github.com/bryanp))
|
6
21
|
* `fix` [#5](https://github.com/bryanp/goru/pull/5) Correctly set channel status to `finished` when closed ([bryanp](https://github.com/bryanp))
|
data/README.md
CHANGED
@@ -201,6 +201,34 @@ Goru::Scheduler.go(io: io, intent: :r) { |routine|
|
|
201
201
|
}
|
202
202
|
```
|
203
203
|
|
204
|
+
## Bridges
|
205
|
+
|
206
|
+
Goru supports coordinated buffered io using bridges:
|
207
|
+
|
208
|
+
```ruby
|
209
|
+
writer = Goru::Channel.new
|
210
|
+
|
211
|
+
Goru::Scheduler.go(io: io, intent: :r) { |routine|
|
212
|
+
case routine.intent
|
213
|
+
when :r
|
214
|
+
routine.bridge(intent: :w, channel: writer) { |bridge|
|
215
|
+
bridge << SecureRandom.hex
|
216
|
+
}
|
217
|
+
when :w
|
218
|
+
if (data = writer.read)
|
219
|
+
routine.write(data)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
}
|
223
|
+
```
|
224
|
+
|
225
|
+
Using bridges, the io routine is only called again when two conditions are met:
|
226
|
+
|
227
|
+
1. The io object matches the bridged intent (e.g. it is writable).
|
228
|
+
2. The channel is in the correct state to reciprocate the intent (e.g. it has data).
|
229
|
+
|
230
|
+
See the [server example](./examples/server.rb) for a more complete use-case.
|
231
|
+
|
204
232
|
## Credits
|
205
233
|
|
206
234
|
Goru was designed while writing a project in Go and imagining what Go-like concurrency might look like in Ruby.
|
data/lib/goru/bridge.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Goru
|
4
|
+
# [public]
|
5
|
+
#
|
6
|
+
class Bridge
|
7
|
+
def initialize(routine:, channel:)
|
8
|
+
@routine = routine
|
9
|
+
@channel = channel
|
10
|
+
@channel.add_observer(self)
|
11
|
+
update_status
|
12
|
+
end
|
13
|
+
|
14
|
+
# [public]
|
15
|
+
#
|
16
|
+
attr_reader :status
|
17
|
+
|
18
|
+
# [public]
|
19
|
+
#
|
20
|
+
private def set_status(status)
|
21
|
+
@status = status
|
22
|
+
status_changed
|
23
|
+
end
|
24
|
+
|
25
|
+
# [public]
|
26
|
+
#
|
27
|
+
def update_status
|
28
|
+
# noop
|
29
|
+
end
|
30
|
+
|
31
|
+
private def status_changed
|
32
|
+
case @status
|
33
|
+
when :ready
|
34
|
+
@routine.bridged
|
35
|
+
when :finished
|
36
|
+
@channel.remove_observer(self)
|
37
|
+
@routine.unbridge
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def channel_received
|
42
|
+
update_status
|
43
|
+
end
|
44
|
+
|
45
|
+
def channel_read
|
46
|
+
update_status
|
47
|
+
end
|
48
|
+
|
49
|
+
def channel_closed
|
50
|
+
update_status
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../bridge"
|
4
|
+
|
5
|
+
module Goru
|
6
|
+
module Bridges
|
7
|
+
class Readable < Bridge
|
8
|
+
private def update_status
|
9
|
+
status = if @routine.status == :finished
|
10
|
+
:finished
|
11
|
+
elsif @channel.full?
|
12
|
+
:idle
|
13
|
+
elsif @channel.closed?
|
14
|
+
:finished
|
15
|
+
else
|
16
|
+
:ready
|
17
|
+
end
|
18
|
+
|
19
|
+
set_status(status)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../bridge"
|
4
|
+
|
5
|
+
module Goru
|
6
|
+
module Bridges
|
7
|
+
class Writable < Bridge
|
8
|
+
private def update_status
|
9
|
+
status = if @routine.status == :finished
|
10
|
+
:finished
|
11
|
+
elsif @channel.any?
|
12
|
+
:ready
|
13
|
+
elsif @channel.closed?
|
14
|
+
:finished
|
15
|
+
else
|
16
|
+
:idle
|
17
|
+
end
|
18
|
+
|
19
|
+
set_status(status)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/goru/reactor.rb
CHANGED
@@ -1,11 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "nio"
|
4
|
-
|
5
4
|
require "timers/group"
|
6
5
|
require "timers/wait"
|
7
6
|
|
8
|
-
require_relative "routines/bridge"
|
9
7
|
require_relative "routines/io"
|
10
8
|
|
11
9
|
module Goru
|
@@ -16,12 +14,11 @@ module Goru
|
|
16
14
|
@queue = queue
|
17
15
|
@scheduler = scheduler
|
18
16
|
@routines = Set.new
|
19
|
-
@bridges = Set.new
|
20
17
|
@timers = Timers::Group.new
|
21
|
-
@selector = NIO::Selector.new
|
22
18
|
@stopped = false
|
23
19
|
@status = nil
|
24
|
-
@
|
20
|
+
@selector = NIO::Selector.new
|
21
|
+
@commands = []
|
25
22
|
end
|
26
23
|
|
27
24
|
# [public]
|
@@ -31,169 +28,140 @@ module Goru
|
|
31
28
|
# [public]
|
32
29
|
#
|
33
30
|
def run
|
34
|
-
|
35
|
-
set_status(:running)
|
36
|
-
|
37
|
-
@routines.each do |routine|
|
38
|
-
call_routine(routine)
|
39
|
-
end
|
31
|
+
set_status(:running)
|
40
32
|
|
41
|
-
|
42
|
-
|
43
|
-
rescue ThreadError
|
44
|
-
interval = @timers.wait_interval
|
45
|
-
|
46
|
-
if interval.nil?
|
47
|
-
if @routines.empty?
|
48
|
-
if @selector.empty?
|
49
|
-
become_idle
|
50
|
-
else
|
51
|
-
wait_for_bridge do
|
52
|
-
wait_for_selector
|
53
|
-
end
|
54
|
-
end
|
55
|
-
else
|
56
|
-
wait_for_bridge do
|
57
|
-
wait_for_selector(0)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
elsif interval > 0
|
61
|
-
if @selector.empty?
|
62
|
-
wait_for_interval(interval)
|
63
|
-
else
|
64
|
-
wait_for_bridge do
|
65
|
-
wait_for_selector(interval)
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
@timers.fire
|
71
|
-
end
|
33
|
+
until @stopped
|
34
|
+
tick
|
72
35
|
end
|
73
36
|
ensure
|
37
|
+
@timers.cancel
|
74
38
|
@selector.close
|
75
39
|
set_status(:finished)
|
76
40
|
end
|
77
41
|
|
78
|
-
private def
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
42
|
+
private def tick
|
43
|
+
# Apply queued commands.
|
44
|
+
#
|
45
|
+
while (command = @commands.shift)
|
46
|
+
action, routine = command
|
47
|
+
|
48
|
+
case action
|
49
|
+
when :adopt
|
50
|
+
routine.reactor = self
|
51
|
+
@routines << routine
|
52
|
+
routine.adopted
|
53
|
+
when :cleanup
|
54
|
+
@routines.delete(routine)
|
55
|
+
when :register
|
56
|
+
monitor = @selector.register(routine.io, routine.intent)
|
57
|
+
monitor.value = routine.method(:wakeup)
|
58
|
+
routine.monitor = monitor
|
59
|
+
when :deregister
|
60
|
+
routine.monitor&.close
|
61
|
+
routine.monitor = nil
|
62
|
+
end
|
87
63
|
end
|
88
|
-
end
|
89
64
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
yield
|
95
|
-
end
|
96
|
-
end
|
65
|
+
# Call each ready routine.
|
66
|
+
#
|
67
|
+
@routines.each do |routine|
|
68
|
+
next unless routine.ready?
|
97
69
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
rescue ThreadError
|
102
|
-
# nothing to do
|
70
|
+
catch :continue do
|
71
|
+
routine.call
|
72
|
+
end
|
103
73
|
end
|
104
|
-
end
|
105
74
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
adopt_routine(routine)
|
110
|
-
end
|
111
|
-
elsif (routine = @queue.pop(!block))
|
75
|
+
# Adopt a new routine if available.
|
76
|
+
#
|
77
|
+
if (routine = @queue.pop(true))
|
112
78
|
adopt_routine(routine)
|
113
79
|
end
|
80
|
+
rescue ThreadError
|
81
|
+
interval = @timers.wait_interval
|
82
|
+
|
83
|
+
if interval.nil? && @routines.empty?
|
84
|
+
set_status(:idle)
|
85
|
+
@scheduler.signal
|
86
|
+
wait
|
87
|
+
set_status(:running)
|
88
|
+
elsif interval.nil?
|
89
|
+
wait unless @routines.any?(&:ready?)
|
90
|
+
elsif interval > 0
|
91
|
+
wait(timeout: interval)
|
92
|
+
end
|
93
|
+
|
94
|
+
@timers.fire
|
114
95
|
end
|
115
96
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
@mutex.synchronize do
|
120
|
-
@status == :idle || @status == :stopped
|
97
|
+
private def wait(timeout: nil)
|
98
|
+
@selector.select(timeout) do |monitor|
|
99
|
+
monitor.value.call
|
121
100
|
end
|
122
101
|
end
|
123
102
|
|
124
103
|
# [public]
|
125
104
|
#
|
126
|
-
def
|
127
|
-
|
128
|
-
@selector.wakeup
|
129
|
-
end
|
105
|
+
def finished?
|
106
|
+
@status == :idle || @status == :stopped
|
130
107
|
end
|
131
108
|
|
132
109
|
# [public]
|
133
110
|
#
|
134
111
|
def wakeup
|
135
|
-
|
136
|
-
|
112
|
+
@selector.wakeup
|
113
|
+
rescue IOError
|
114
|
+
# nothing to do
|
137
115
|
end
|
138
116
|
|
139
117
|
# [public]
|
140
118
|
#
|
141
119
|
def stop
|
142
120
|
@stopped = true
|
143
|
-
|
144
|
-
rescue
|
121
|
+
wakeup
|
122
|
+
rescue ClosedQueueError
|
123
|
+
# nothing to do
|
145
124
|
end
|
146
125
|
|
147
126
|
# [public]
|
148
127
|
#
|
149
|
-
def
|
128
|
+
def asleep_for(seconds)
|
150
129
|
@timers.after(seconds) {
|
151
|
-
|
130
|
+
yield
|
152
131
|
}
|
153
132
|
end
|
154
133
|
|
155
134
|
# [public]
|
156
135
|
#
|
157
136
|
def adopt_routine(routine)
|
158
|
-
|
159
|
-
when Routines::IO
|
160
|
-
monitor = @selector.register(routine.io, routine.intent)
|
161
|
-
monitor.value = routine
|
162
|
-
routine.monitor = monitor
|
163
|
-
routine.reactor = self
|
164
|
-
when Routines::Bridge
|
165
|
-
routine.reactor = self
|
166
|
-
@bridges << routine
|
167
|
-
when Routine
|
168
|
-
routine.reactor = self
|
169
|
-
@routines << routine
|
170
|
-
end
|
137
|
+
command(:adopt, routine)
|
171
138
|
end
|
172
139
|
|
173
140
|
# [public]
|
174
141
|
#
|
175
142
|
def routine_finished(routine)
|
176
|
-
|
177
|
-
when Routines::Bridge
|
178
|
-
@bridges.delete(routine)
|
179
|
-
when Routines::IO
|
180
|
-
@selector.deregister(routine.io)
|
181
|
-
else
|
182
|
-
@routines.delete(routine)
|
183
|
-
end
|
143
|
+
command(:cleanup, routine)
|
184
144
|
end
|
185
145
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
146
|
+
# [public]
|
147
|
+
#
|
148
|
+
def register(routine)
|
149
|
+
command(:register, routine)
|
190
150
|
end
|
191
151
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
152
|
+
# [public]
|
153
|
+
#
|
154
|
+
def deregister(routine)
|
155
|
+
command(:deregister, routine)
|
156
|
+
end
|
157
|
+
|
158
|
+
private def command(action, routine)
|
159
|
+
@commands << [action, routine]
|
160
|
+
wakeup
|
161
|
+
end
|
162
|
+
|
163
|
+
private def set_status(status)
|
164
|
+
@status = status
|
197
165
|
end
|
198
166
|
end
|
199
167
|
end
|
data/lib/goru/routine.rb
CHANGED
@@ -53,10 +53,10 @@ module Goru
|
|
53
53
|
# [public]
|
54
54
|
#
|
55
55
|
def finished(result = nil)
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
56
|
+
@result = result
|
57
|
+
set_status(:finished)
|
58
|
+
|
59
|
+
throw :continue
|
60
60
|
end
|
61
61
|
|
62
62
|
# [public]
|
@@ -80,13 +80,17 @@ module Goru
|
|
80
80
|
#
|
81
81
|
def sleep(seconds)
|
82
82
|
set_status(:idle)
|
83
|
-
@reactor.
|
83
|
+
@reactor.asleep_for(seconds) do
|
84
|
+
set_status(:ready)
|
85
|
+
end
|
86
|
+
|
87
|
+
throw :continue
|
84
88
|
end
|
85
89
|
|
86
90
|
# [public]
|
87
91
|
#
|
88
|
-
def
|
89
|
-
|
92
|
+
def ready?
|
93
|
+
@status == :ready
|
90
94
|
end
|
91
95
|
|
92
96
|
# [public]
|
@@ -104,5 +108,11 @@ module Goru
|
|
104
108
|
@reactor&.routine_finished(self)
|
105
109
|
end
|
106
110
|
end
|
111
|
+
|
112
|
+
# [public]
|
113
|
+
#
|
114
|
+
def adopted
|
115
|
+
# noop
|
116
|
+
end
|
107
117
|
end
|
108
118
|
end
|
data/lib/goru/routines/io.rb
CHANGED
@@ -1,51 +1,87 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "../routine"
|
4
|
-
require_relative "bridges/readable"
|
5
|
-
require_relative "bridges/writable"
|
4
|
+
require_relative "../bridges/readable"
|
5
|
+
require_relative "../bridges/writable"
|
6
6
|
|
7
7
|
module Goru
|
8
8
|
module Routines
|
9
9
|
# [public]
|
10
10
|
#
|
11
11
|
class IO < Routine
|
12
|
-
def initialize(state = nil, io:, intent:, &block)
|
12
|
+
def initialize(state = nil, io:, intent:, event_loop:, &block)
|
13
13
|
super(state, &block)
|
14
14
|
|
15
15
|
@io = io
|
16
16
|
@intent = normalize_intent(intent)
|
17
|
-
@
|
17
|
+
@event_loop = event_loop
|
18
|
+
@status = :orphaned
|
18
19
|
@monitor = nil
|
19
|
-
@finishers = []
|
20
20
|
end
|
21
21
|
|
22
22
|
# [public]
|
23
23
|
#
|
24
24
|
attr_reader :io, :intent
|
25
25
|
|
26
|
-
|
26
|
+
attr_accessor :monitor
|
27
|
+
|
28
|
+
# [public]
|
29
|
+
#
|
30
|
+
def adopted
|
31
|
+
set_status(:ready)
|
32
|
+
end
|
33
|
+
|
34
|
+
# [public]
|
35
|
+
#
|
36
|
+
def wakeup
|
37
|
+
# Keep this io from being selected again until the underlying routine is called.
|
38
|
+
# Interests are reset in `#call`.
|
39
|
+
#
|
40
|
+
@monitor&.interests = nil
|
41
|
+
|
42
|
+
set_status(:io_ready)
|
43
|
+
end
|
44
|
+
|
45
|
+
READY_STATUSES = [:io_ready, :ready].freeze
|
46
|
+
READY_BRIDGE_STATUSES = [nil, :ready].freeze
|
47
|
+
|
48
|
+
# [public]
|
49
|
+
#
|
50
|
+
def ready?
|
51
|
+
READY_STATUSES.include?(@status) && READY_BRIDGE_STATUSES.include?(@bridge&.status)
|
52
|
+
end
|
53
|
+
|
54
|
+
def call
|
55
|
+
super
|
56
|
+
|
57
|
+
@monitor&.interests = @intent
|
58
|
+
end
|
27
59
|
|
28
60
|
# [public]
|
29
61
|
#
|
30
62
|
def accept
|
31
63
|
@io.accept_nonblock
|
64
|
+
rescue Errno::EAGAIN
|
65
|
+
wait
|
66
|
+
rescue Errno::ECONNRESET, Errno::EPIPE, EOFError
|
67
|
+
finished
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
|
71
|
+
def wait
|
72
|
+
set_status(:selecting)
|
73
|
+
@reactor.register(self) unless @monitor
|
74
|
+
|
75
|
+
throw :continue
|
32
76
|
end
|
33
77
|
|
34
78
|
# [public]
|
35
79
|
#
|
36
80
|
def read(bytes)
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
finished
|
42
|
-
nil
|
43
|
-
when :wait_readable
|
44
|
-
# nothing to do
|
45
|
-
else
|
46
|
-
result
|
47
|
-
end
|
48
|
-
rescue Errno::ECONNRESET
|
81
|
+
@io.read_nonblock(bytes)
|
82
|
+
rescue Errno::EAGAIN
|
83
|
+
wait
|
84
|
+
rescue Errno::ECONNRESET, Errno::EPIPE, EOFError
|
49
85
|
finished
|
50
86
|
nil
|
51
87
|
end
|
@@ -53,18 +89,10 @@ module Goru
|
|
53
89
|
# [public]
|
54
90
|
#
|
55
91
|
def write(data)
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
finished
|
61
|
-
nil
|
62
|
-
when :wait_writable
|
63
|
-
# nothing to do
|
64
|
-
else
|
65
|
-
result
|
66
|
-
end
|
67
|
-
rescue Errno::ECONNRESET
|
92
|
+
@io.write_nonblock(data)
|
93
|
+
rescue Errno::EAGAIN
|
94
|
+
wait
|
95
|
+
rescue Errno::ECONNRESET, Errno::EPIPE, EOFError
|
68
96
|
finished
|
69
97
|
nil
|
70
98
|
end
|
@@ -75,38 +103,56 @@ module Goru
|
|
75
103
|
intent = normalize_intent(intent)
|
76
104
|
validate_intent!(intent)
|
77
105
|
|
78
|
-
@monitor
|
106
|
+
@monitor&.interests = intent
|
79
107
|
@intent = intent
|
80
108
|
end
|
81
109
|
|
82
110
|
# [public]
|
83
111
|
#
|
84
|
-
def bridge(
|
112
|
+
def bridge(state = nil, intent:, channel:, &block)
|
113
|
+
raise "routine is already bridged" if @bridge
|
114
|
+
|
85
115
|
intent = normalize_intent(intent)
|
86
116
|
validate_intent!(intent)
|
117
|
+
self.intent = intent
|
87
118
|
|
88
|
-
bridge = case intent
|
119
|
+
@bridge = case intent
|
89
120
|
when :r
|
90
121
|
Bridges::Readable.new(routine: self, channel: channel)
|
91
122
|
when :w
|
92
123
|
Bridges::Writable.new(routine: self, channel: channel)
|
93
124
|
end
|
94
125
|
|
95
|
-
|
96
|
-
|
97
|
-
|
126
|
+
routine = case intent
|
127
|
+
when :r
|
128
|
+
Routines::Channels::Readable.new(state, channel: channel, &block)
|
129
|
+
when :w
|
130
|
+
Routines::Channels::Writable.new(state, channel: channel, &block)
|
131
|
+
end
|
132
|
+
|
133
|
+
@reactor.adopt_routine(routine)
|
134
|
+
@reactor.wakeup
|
135
|
+
|
136
|
+
routine
|
137
|
+
end
|
138
|
+
|
139
|
+
# [public]
|
140
|
+
#
|
141
|
+
def bridged
|
142
|
+
@reactor.wakeup
|
98
143
|
end
|
99
144
|
|
100
145
|
# [public]
|
101
146
|
#
|
102
|
-
def
|
103
|
-
@
|
147
|
+
def unbridge
|
148
|
+
@bridge = nil
|
149
|
+
@reactor.wakeup
|
104
150
|
end
|
105
151
|
|
106
152
|
private def status_changed
|
107
153
|
case @status
|
108
154
|
when :finished
|
109
|
-
@
|
155
|
+
@reactor&.deregister(self)
|
110
156
|
end
|
111
157
|
|
112
158
|
super
|
data/lib/goru/scheduler.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "etc"
|
4
|
+
require "nio"
|
4
5
|
require "is/global"
|
5
6
|
|
6
7
|
require_relative "channel"
|
@@ -16,7 +17,6 @@ module Goru
|
|
16
17
|
#
|
17
18
|
class Scheduler
|
18
19
|
include Is::Global
|
19
|
-
include MonitorMixin
|
20
20
|
|
21
21
|
class << self
|
22
22
|
# Prevent issues when including `Goru` at the toplevel.
|
@@ -29,17 +29,18 @@ module Goru
|
|
29
29
|
|
30
30
|
# [public]
|
31
31
|
#
|
32
|
-
def
|
32
|
+
def default_reactor_count
|
33
33
|
Etc.nprocessors
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
-
def initialize(count: self.class.
|
37
|
+
def initialize(count: self.class.default_reactor_count)
|
38
38
|
super()
|
39
39
|
|
40
|
-
@
|
40
|
+
@waiting = false
|
41
|
+
@stopped = false
|
41
42
|
@routines = Thread::Queue.new
|
42
|
-
@
|
43
|
+
@selector = NIO::Selector.new
|
43
44
|
|
44
45
|
@reactors = count.times.map {
|
45
46
|
Reactor.new(queue: @routines, scheduler: self)
|
@@ -60,7 +61,7 @@ module Goru
|
|
60
61
|
raise ArgumentError, "cannot set both `io` and `channel`" if io && channel
|
61
62
|
|
62
63
|
routine = if io
|
63
|
-
Routines::IO.new(state, io: io, intent: intent, &block)
|
64
|
+
Routines::IO.new(state, io: io, intent: intent, event_loop: @io_event_loop, &block)
|
64
65
|
elsif channel
|
65
66
|
case intent
|
66
67
|
when :r
|
@@ -73,7 +74,7 @@ module Goru
|
|
73
74
|
end
|
74
75
|
|
75
76
|
@routines << routine
|
76
|
-
@reactors.each(&:
|
77
|
+
@reactors.each(&:wakeup)
|
77
78
|
|
78
79
|
routine
|
79
80
|
end
|
@@ -81,12 +82,11 @@ module Goru
|
|
81
82
|
# [public]
|
82
83
|
#
|
83
84
|
def wait
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
rescue Interrupt
|
85
|
+
@waiting = true
|
86
|
+
@reactors.each(&:wakeup)
|
87
|
+
@selector.select while @waiting
|
88
|
+
rescue IOError, Interrupt
|
89
|
+
# nothing to do
|
90
90
|
ensure
|
91
91
|
stop
|
92
92
|
end
|
@@ -94,22 +94,25 @@ module Goru
|
|
94
94
|
# [public]
|
95
95
|
#
|
96
96
|
def stop
|
97
|
-
@
|
97
|
+
@stopped = true
|
98
98
|
@routines.close
|
99
|
+
@selector.close
|
99
100
|
@reactors.each(&:stop)
|
100
101
|
@threads.each(&:join)
|
101
102
|
end
|
102
103
|
|
103
104
|
# [public]
|
104
105
|
#
|
105
|
-
def signal
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
106
|
+
def signal
|
107
|
+
return unless @waiting && @reactors.all?(&:finished?)
|
108
|
+
@waiting = false
|
109
|
+
wakeup
|
110
|
+
end
|
110
111
|
|
111
|
-
|
112
|
-
|
112
|
+
def wakeup
|
113
|
+
@selector.wakeup
|
114
|
+
rescue IOError
|
115
|
+
# nothing to do
|
113
116
|
end
|
114
117
|
end
|
115
118
|
end
|
data/lib/goru/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: goru
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bryan Powell
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-07-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: core-extension
|
@@ -90,12 +90,12 @@ files:
|
|
90
90
|
- LICENSE
|
91
91
|
- README.md
|
92
92
|
- lib/goru.rb
|
93
|
+
- lib/goru/bridge.rb
|
94
|
+
- lib/goru/bridges/readable.rb
|
95
|
+
- lib/goru/bridges/writable.rb
|
93
96
|
- lib/goru/channel.rb
|
94
97
|
- lib/goru/reactor.rb
|
95
98
|
- lib/goru/routine.rb
|
96
|
-
- lib/goru/routines/bridge.rb
|
97
|
-
- lib/goru/routines/bridges/readable.rb
|
98
|
-
- lib/goru/routines/bridges/writable.rb
|
99
99
|
- lib/goru/routines/channel.rb
|
100
100
|
- lib/goru/routines/channels/readable.rb
|
101
101
|
- lib/goru/routines/channels/writable.rb
|
@@ -121,7 +121,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
121
121
|
- !ruby/object:Gem::Version
|
122
122
|
version: '0'
|
123
123
|
requirements: []
|
124
|
-
rubygems_version: 3.4.
|
124
|
+
rubygems_version: 3.4.12
|
125
125
|
signing_key:
|
126
126
|
specification_version: 4
|
127
127
|
summary: Concurrent routines for Ruby.
|
data/lib/goru/routines/bridge.rb
DELETED
@@ -1,46 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative "../routine"
|
4
|
-
|
5
|
-
module Goru
|
6
|
-
module Routines
|
7
|
-
# [public]
|
8
|
-
#
|
9
|
-
class Bridge < Routine
|
10
|
-
def initialize(state = nil, routine:, channel:, &block)
|
11
|
-
super(state, &block)
|
12
|
-
|
13
|
-
@routine = routine
|
14
|
-
@channel = channel
|
15
|
-
@channel.add_observer(self)
|
16
|
-
end
|
17
|
-
|
18
|
-
# [public]
|
19
|
-
#
|
20
|
-
def ready?
|
21
|
-
@status == :ready
|
22
|
-
end
|
23
|
-
|
24
|
-
private def status_changed
|
25
|
-
case @status
|
26
|
-
when :finished
|
27
|
-
@channel.remove_observer(self)
|
28
|
-
end
|
29
|
-
|
30
|
-
super
|
31
|
-
end
|
32
|
-
|
33
|
-
def channel_received
|
34
|
-
update_status
|
35
|
-
end
|
36
|
-
|
37
|
-
def channel_read
|
38
|
-
update_status
|
39
|
-
end
|
40
|
-
|
41
|
-
def channel_closed
|
42
|
-
update_status
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
@@ -1,37 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative "../bridge"
|
4
|
-
|
5
|
-
module Goru
|
6
|
-
module Routines
|
7
|
-
module Bridges
|
8
|
-
class Readable < Bridge
|
9
|
-
def initialize(...)
|
10
|
-
super
|
11
|
-
|
12
|
-
update_status
|
13
|
-
end
|
14
|
-
|
15
|
-
# [public]
|
16
|
-
#
|
17
|
-
def applicable?
|
18
|
-
@routine.intent == :r
|
19
|
-
end
|
20
|
-
|
21
|
-
private def update_status
|
22
|
-
status = if @routine.status == :finished
|
23
|
-
:finished
|
24
|
-
elsif @channel.full?
|
25
|
-
:idle
|
26
|
-
elsif @channel.closed?
|
27
|
-
:finished
|
28
|
-
else
|
29
|
-
:ready
|
30
|
-
end
|
31
|
-
|
32
|
-
set_status(status)
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
@@ -1,37 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative "../bridge"
|
4
|
-
|
5
|
-
module Goru
|
6
|
-
module Routines
|
7
|
-
module Bridges
|
8
|
-
class Writable < Bridge
|
9
|
-
def initialize(...)
|
10
|
-
super
|
11
|
-
|
12
|
-
update_status
|
13
|
-
end
|
14
|
-
|
15
|
-
# [public]
|
16
|
-
#
|
17
|
-
def applicable?
|
18
|
-
@routine.intent == :w
|
19
|
-
end
|
20
|
-
|
21
|
-
private def update_status
|
22
|
-
status = if @routine.status == :finished
|
23
|
-
:finished
|
24
|
-
elsif @channel.any?
|
25
|
-
:ready
|
26
|
-
elsif @channel.closed?
|
27
|
-
:finished
|
28
|
-
else
|
29
|
-
:idle
|
30
|
-
end
|
31
|
-
|
32
|
-
set_status(status)
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|