libev_scheduler 0.1
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 +7 -0
- data/.github/test.yml +31 -0
- data/.gitignore +58 -0
- data/CHANGELOG.md +0 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +32 -0
- data/LICENSE +21 -0
- data/README.md +5 -0
- data/Rakefile +18 -0
- data/TODO.md +0 -0
- data/examples/io.rb +31 -0
- data/examples/sleep.rb +10 -0
- data/ext/libev/Changes +548 -0
- data/ext/libev/LICENSE +37 -0
- data/ext/libev/README +59 -0
- data/ext/libev/README.embed +3 -0
- data/ext/libev/ev.c +5279 -0
- data/ext/libev/ev.h +856 -0
- data/ext/libev/ev_epoll.c +296 -0
- data/ext/libev/ev_kqueue.c +224 -0
- data/ext/libev/ev_linuxaio.c +642 -0
- data/ext/libev/ev_poll.c +156 -0
- data/ext/libev/ev_port.c +192 -0
- data/ext/libev/ev_select.c +316 -0
- data/ext/libev/ev_vars.h +215 -0
- data/ext/libev/ev_win32.c +162 -0
- data/ext/libev/ev_wrap.h +216 -0
- data/ext/libev/test_libev_win32.c +123 -0
- data/ext/libev_scheduler/extconf.rb +22 -0
- data/ext/libev_scheduler/libev.c +2 -0
- data/ext/libev_scheduler/libev.h +11 -0
- data/ext/libev_scheduler/libev_scheduler_ext.c +5 -0
- data/ext/libev_scheduler/scheduler.c +330 -0
- data/lib/libev_scheduler.rb +23 -0
- data/lib/libev_scheduler/version.rb +5 -0
- data/libev_scheduler.gemspec +26 -0
- data/test/run.rb +5 -0
- data/test/test_enumerator.rb +45 -0
- data/test/test_io.rb +57 -0
- data/test/test_mutex.rb +230 -0
- data/test/test_process.rb +38 -0
- data/test/test_sleep.rb +52 -0
- metadata +134 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
require_relative './libev_scheduler_ext'
|
2
|
+
|
3
|
+
module Libev
|
4
|
+
class Scheduler
|
5
|
+
def fiber(&block)
|
6
|
+
fiber = Fiber.new(blocking: false, &block)
|
7
|
+
unblock(nil, fiber)
|
8
|
+
# fiber.resume
|
9
|
+
return fiber
|
10
|
+
end
|
11
|
+
|
12
|
+
def kernel_sleep(duration = nil)
|
13
|
+
block(:sleep, duration)
|
14
|
+
end
|
15
|
+
|
16
|
+
def process_wait(pid, flags)
|
17
|
+
# This is a very simple way to implement a non-blocking wait:
|
18
|
+
Thread.new do
|
19
|
+
Process::Status.wait(pid, flags)
|
20
|
+
end.value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative './lib/libev_scheduler/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'libev_scheduler'
|
5
|
+
s.version = Libev::VERSION
|
6
|
+
s.licenses = ['MIT']
|
7
|
+
s.summary = 'Libev-based fiber scheduler for Ruby 3.0'
|
8
|
+
s.author = 'Sharon Rosner'
|
9
|
+
s.email = 'sharon@noteflakes.com'
|
10
|
+
s.files = `git ls-files`.split
|
11
|
+
s.homepage = 'https://github.com/digital-fabric/libev_scheduler'
|
12
|
+
s.metadata = {
|
13
|
+
'source_code_uri' => 'https://github.com/digital-fabric/libev_scheduler',
|
14
|
+
'homepage_uri' => 'https://github.com/digital-fabric/libev_scheduler',
|
15
|
+
'changelog_uri' => 'https://github.com/digital-fabric/libev_scheduler/blob/master/CHANGELOG.md'
|
16
|
+
}
|
17
|
+
s.rdoc_options = ['--title', "libev_scheduler", '--main', 'README.md']
|
18
|
+
s.extra_rdoc_files = ['README.md']
|
19
|
+
s.extensions = ['ext/libev_scheduler/extconf.rb']
|
20
|
+
s.require_paths = ['lib']
|
21
|
+
s.required_ruby_version = '>= 3.0'
|
22
|
+
|
23
|
+
s.add_development_dependency 'rake-compiler', '1.1.1'
|
24
|
+
s.add_development_dependency 'minitest', '5.14.2'
|
25
|
+
s.add_development_dependency 'minitest-reporters', '1.4.2'
|
26
|
+
end
|
data/test/run.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'minitest/autorun'
|
4
|
+
require 'minitest/reporters'
|
5
|
+
require 'libev_scheduler'
|
6
|
+
|
7
|
+
class TestFiberEnumerator < MiniTest::Test
|
8
|
+
MESSAGE = "Hello World"
|
9
|
+
|
10
|
+
def test_read_characters
|
11
|
+
i, o = IO.pipe
|
12
|
+
|
13
|
+
message = String.new
|
14
|
+
|
15
|
+
thread = Thread.new do
|
16
|
+
scheduler = Libev::Scheduler.new
|
17
|
+
Fiber.set_scheduler scheduler
|
18
|
+
|
19
|
+
e = i.to_enum(:each_char)
|
20
|
+
|
21
|
+
Fiber.schedule do
|
22
|
+
o.write("Hello World")
|
23
|
+
o.close
|
24
|
+
end
|
25
|
+
|
26
|
+
Fiber.schedule do
|
27
|
+
begin
|
28
|
+
while c = e.next
|
29
|
+
message << c
|
30
|
+
end
|
31
|
+
rescue StopIteration
|
32
|
+
# Ignore.
|
33
|
+
end
|
34
|
+
|
35
|
+
i.close
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
thread.join
|
40
|
+
|
41
|
+
assert_equal(MESSAGE, message)
|
42
|
+
assert_predicate(i, :closed?)
|
43
|
+
assert_predicate(o, :closed?)
|
44
|
+
end
|
45
|
+
end
|
data/test/test_io.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'minitest/autorun'
|
4
|
+
require 'minitest/reporters'
|
5
|
+
require 'libev_scheduler'
|
6
|
+
|
7
|
+
class TestFiberIO < MiniTest::Test
|
8
|
+
MESSAGE = "Hello World"
|
9
|
+
|
10
|
+
def test_read
|
11
|
+
i, o = IO.pipe
|
12
|
+
|
13
|
+
message = nil
|
14
|
+
|
15
|
+
thread = Thread.new do
|
16
|
+
scheduler = Libev::Scheduler.new
|
17
|
+
Fiber.set_scheduler scheduler
|
18
|
+
|
19
|
+
Fiber.schedule do
|
20
|
+
message = i.read(20)
|
21
|
+
i.close
|
22
|
+
end
|
23
|
+
|
24
|
+
Fiber.schedule do
|
25
|
+
o.write("Hello World")
|
26
|
+
o.close
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
thread.join
|
31
|
+
|
32
|
+
assert_equal MESSAGE, message
|
33
|
+
assert_predicate(i, :closed?)
|
34
|
+
assert_predicate(o, :closed?)
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_heavy_read
|
38
|
+
16.times.map do
|
39
|
+
Thread.new do
|
40
|
+
i, o = IO.pipe
|
41
|
+
|
42
|
+
scheduler = Libev::Scheduler.new
|
43
|
+
Fiber.set_scheduler scheduler
|
44
|
+
|
45
|
+
Fiber.schedule do
|
46
|
+
i.read(20)
|
47
|
+
i.close
|
48
|
+
end
|
49
|
+
|
50
|
+
Fiber.schedule do
|
51
|
+
o.write("Hello World")
|
52
|
+
o.close
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end.each(&:join)
|
56
|
+
end
|
57
|
+
end
|
data/test/test_mutex.rb
ADDED
@@ -0,0 +1,230 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'minitest/autorun'
|
4
|
+
require 'minitest/reporters'
|
5
|
+
require 'libev_scheduler'
|
6
|
+
|
7
|
+
class TestFiberMutex < MiniTest::Test
|
8
|
+
def test_mutex_synchronize
|
9
|
+
mutex = Mutex.new
|
10
|
+
|
11
|
+
thread = Thread.new do
|
12
|
+
scheduler = Libev::Scheduler.new
|
13
|
+
Fiber.set_scheduler scheduler
|
14
|
+
|
15
|
+
Fiber.schedule do
|
16
|
+
assert !Fiber.blocking?
|
17
|
+
|
18
|
+
mutex.synchronize do
|
19
|
+
assert !Fiber.blocking?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
thread.join
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_mutex_interleaved_locking
|
28
|
+
mutex = Mutex.new
|
29
|
+
|
30
|
+
thread = Thread.new do
|
31
|
+
scheduler = Libev::Scheduler.new
|
32
|
+
Fiber.set_scheduler scheduler
|
33
|
+
|
34
|
+
Fiber.schedule do
|
35
|
+
mutex.lock
|
36
|
+
sleep 0.1
|
37
|
+
mutex.unlock
|
38
|
+
end
|
39
|
+
|
40
|
+
Fiber.schedule do
|
41
|
+
mutex.lock
|
42
|
+
sleep 0.1
|
43
|
+
mutex.unlock
|
44
|
+
end
|
45
|
+
|
46
|
+
scheduler.run
|
47
|
+
end
|
48
|
+
|
49
|
+
thread.join
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_mutex_thread
|
53
|
+
mutex = Mutex.new
|
54
|
+
mutex.lock
|
55
|
+
|
56
|
+
thread = Thread.new do
|
57
|
+
scheduler = Libev::Scheduler.new
|
58
|
+
Fiber.set_scheduler scheduler
|
59
|
+
|
60
|
+
Fiber.schedule do
|
61
|
+
mutex.lock
|
62
|
+
sleep 0.1
|
63
|
+
mutex.unlock
|
64
|
+
end
|
65
|
+
|
66
|
+
scheduler.run
|
67
|
+
end
|
68
|
+
|
69
|
+
sleep 0.1
|
70
|
+
mutex.unlock
|
71
|
+
|
72
|
+
thread.join
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_mutex_fiber_raise
|
76
|
+
skip "stuck"
|
77
|
+
mutex = Mutex.new
|
78
|
+
ran = false
|
79
|
+
|
80
|
+
main = Thread.new do
|
81
|
+
p [1, :pre_lock]
|
82
|
+
mutex.lock
|
83
|
+
p [1, :post_lock]
|
84
|
+
|
85
|
+
thread = Thread.new do
|
86
|
+
scheduler = Libev::Scheduler.new
|
87
|
+
Fiber.set_scheduler scheduler
|
88
|
+
|
89
|
+
f = Fiber.schedule do
|
90
|
+
assert_raise_message("bye") do
|
91
|
+
p [2, :pre_lock]
|
92
|
+
mutex.lock
|
93
|
+
p [2, :post_lock]
|
94
|
+
end
|
95
|
+
|
96
|
+
ran = true
|
97
|
+
end
|
98
|
+
|
99
|
+
Fiber.schedule do
|
100
|
+
p [3, :pre_raise]
|
101
|
+
f.raise "bye"
|
102
|
+
p [3, :post_raise]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
thread.join
|
107
|
+
end
|
108
|
+
|
109
|
+
main.join # causes mutex to be released
|
110
|
+
assert_equal false, mutex.locked?
|
111
|
+
assert_equal true, ran
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_condition_variable
|
115
|
+
mutex = Mutex.new
|
116
|
+
condition = ConditionVariable.new
|
117
|
+
|
118
|
+
signalled = 0
|
119
|
+
|
120
|
+
Thread.new do
|
121
|
+
scheduler = Libev::Scheduler.new
|
122
|
+
Fiber.set_scheduler scheduler
|
123
|
+
|
124
|
+
Fiber.schedule do
|
125
|
+
mutex.synchronize do
|
126
|
+
3.times do
|
127
|
+
condition.wait(mutex)
|
128
|
+
signalled += 1
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
Fiber.schedule do
|
134
|
+
3.times do
|
135
|
+
mutex.synchronize do
|
136
|
+
condition.signal
|
137
|
+
end
|
138
|
+
|
139
|
+
sleep 0.1
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
scheduler.run
|
144
|
+
end.join
|
145
|
+
|
146
|
+
assert_equal 3, signalled
|
147
|
+
end
|
148
|
+
|
149
|
+
def test_queue
|
150
|
+
queue = Queue.new
|
151
|
+
processed = 0
|
152
|
+
|
153
|
+
thread = Thread.new do
|
154
|
+
scheduler = Libev::Scheduler.new
|
155
|
+
Fiber.set_scheduler scheduler
|
156
|
+
|
157
|
+
Fiber.schedule do
|
158
|
+
3.times do |i|
|
159
|
+
queue << i
|
160
|
+
sleep 0.1
|
161
|
+
end
|
162
|
+
|
163
|
+
queue.close
|
164
|
+
end
|
165
|
+
|
166
|
+
Fiber.schedule do
|
167
|
+
while item = queue.pop
|
168
|
+
processed += 1
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
scheduler.run
|
173
|
+
end
|
174
|
+
|
175
|
+
thread.join
|
176
|
+
|
177
|
+
assert_equal 3, processed
|
178
|
+
end
|
179
|
+
|
180
|
+
def test_queue_pop_waits
|
181
|
+
queue = Queue.new
|
182
|
+
running = false
|
183
|
+
|
184
|
+
thread = Thread.new do
|
185
|
+
scheduler = Libev::Scheduler.new
|
186
|
+
Fiber.set_scheduler scheduler
|
187
|
+
|
188
|
+
result = nil
|
189
|
+
Fiber.schedule do
|
190
|
+
result = queue.pop
|
191
|
+
end
|
192
|
+
|
193
|
+
running = true
|
194
|
+
scheduler.run
|
195
|
+
result
|
196
|
+
end
|
197
|
+
|
198
|
+
Thread.pass until running
|
199
|
+
sleep 0.1
|
200
|
+
|
201
|
+
queue << :done
|
202
|
+
assert_equal :done, thread.value
|
203
|
+
end
|
204
|
+
|
205
|
+
def test_mutex_deadlock
|
206
|
+
skip "no impl of assert_in_out_err"
|
207
|
+
error_pattern = /No live threads left. Deadlock\?/
|
208
|
+
|
209
|
+
assert_in_out_err %W[-I#{__dir__} -], <<-RUBY, ['in synchronize'], error_pattern, success: false
|
210
|
+
require 'scheduler'
|
211
|
+
mutex = Mutex.new
|
212
|
+
|
213
|
+
thread = Thread.new do
|
214
|
+
scheduler = Libev::Scheduler.new
|
215
|
+
Fiber.set_scheduler scheduler
|
216
|
+
|
217
|
+
Fiber.schedule do
|
218
|
+
mutex.synchronize do
|
219
|
+
puts 'in synchronize'
|
220
|
+
Fiber.yield
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
mutex.lock
|
225
|
+
end
|
226
|
+
|
227
|
+
thread.join
|
228
|
+
RUBY
|
229
|
+
end
|
230
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'minitest/autorun'
|
4
|
+
require 'minitest/reporters'
|
5
|
+
require 'libev_scheduler'
|
6
|
+
|
7
|
+
class TestFiberProcess < MiniTest::Test
|
8
|
+
def test_process_wait
|
9
|
+
Thread.new do
|
10
|
+
scheduler = Libev::Scheduler.new
|
11
|
+
Fiber.set_scheduler scheduler
|
12
|
+
|
13
|
+
Fiber.schedule do
|
14
|
+
pid = Process.spawn("true")
|
15
|
+
Process.wait(pid)
|
16
|
+
|
17
|
+
# TODO test that scheduler was invoked.
|
18
|
+
|
19
|
+
assert_predicate $?, :success?
|
20
|
+
end
|
21
|
+
end.join
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_system
|
25
|
+
Thread.new do
|
26
|
+
scheduler = Libev::Scheduler.new
|
27
|
+
Fiber.set_scheduler scheduler
|
28
|
+
|
29
|
+
Fiber.schedule do
|
30
|
+
system("true")
|
31
|
+
|
32
|
+
# TODO test that scheduler was invoked (currently it's not).
|
33
|
+
|
34
|
+
assert_predicate $?, :success?
|
35
|
+
end
|
36
|
+
end.join
|
37
|
+
end
|
38
|
+
end
|
data/test/test_sleep.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'minitest/autorun'
|
4
|
+
require 'minitest/reporters'
|
5
|
+
require 'libev_scheduler'
|
6
|
+
|
7
|
+
class TestFiberSleep < MiniTest::Test
|
8
|
+
ITEMS = [0, 1, 2, 3, 4]
|
9
|
+
|
10
|
+
def setup
|
11
|
+
sleep 0.1
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_sleep
|
15
|
+
items = []
|
16
|
+
|
17
|
+
thread = Thread.new do
|
18
|
+
scheduler = Libev::Scheduler.new
|
19
|
+
Fiber.set_scheduler scheduler
|
20
|
+
|
21
|
+
5.times do |i|
|
22
|
+
Fiber.schedule do
|
23
|
+
assert_operator sleep(i/100.0), :>=, 0
|
24
|
+
items << i
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
thread.join
|
30
|
+
|
31
|
+
assert_equal ITEMS, items
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_sleep_returns_seconds_slept
|
35
|
+
seconds = nil
|
36
|
+
t0 = Time.now
|
37
|
+
|
38
|
+
thread = Thread.new do
|
39
|
+
scheduler = Libev::Scheduler.new
|
40
|
+
Fiber.set_scheduler scheduler
|
41
|
+
Fiber.schedule do
|
42
|
+
seconds = sleep(1)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
thread.join
|
47
|
+
elapsed = Time.now - t0
|
48
|
+
|
49
|
+
assert_operator seconds, :>=, 1.0, "actual: %p" % seconds
|
50
|
+
assert_operator elapsed, :>=, 1.0, "actual: %p" % elapsed
|
51
|
+
end
|
52
|
+
end
|