libev_scheduler 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Libev
4
+ VERSION = '0.1'
5
+ 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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ Dir.glob("#{__dir__}/test_*.rb").each do |path|
4
+ require(path)
5
+ end
@@ -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
@@ -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
@@ -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
@@ -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