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.
@@ -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