ruby-prof 2.0.0 → 2.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 +4 -4
- data/CHANGELOG.md +3 -0
- data/bin/ruby-prof-check-trace +45 -45
- data/docs/advanced-usage.md +132 -132
- data/docs/best-practices.md +27 -27
- data/docs/getting-started.md +130 -130
- data/docs/index.md +45 -45
- data/docs/profiling-rails.md +64 -64
- data/docs/public/examples/generate_reports.rb +92 -92
- data/docs/public/examples/reports/call_stack.html +835 -835
- data/docs/public/examples/reports/graph.html +1319 -1319
- data/docs/reports.md +150 -150
- data/lib/ruby-prof/version.rb +1 -1
- data/ruby-prof.gemspec +66 -66
- data/test/call_tree_builder.rb +126 -126
- data/test/exceptions_test.rb +24 -24
- data/test/marshal_test.rb +144 -144
- data/test/printer_call_stack_test.rb +28 -28
- data/test/printer_flame_graph_test.rb +82 -82
- data/test/printer_flat_test.rb +99 -99
- data/test/printer_graph_html_test.rb +62 -62
- data/test/printer_graph_test.rb +42 -42
- data/test/printers_test.rb +162 -162
- data/test/printing_recursive_graph_test.rb +81 -81
- data/test/profile_test.rb +101 -101
- data/test/rack_test.rb +103 -103
- data/test/scheduler.rb +367 -367
- data/test/singleton_test.rb +39 -39
- data/test/thread_test.rb +229 -229
- data/test/yarv_test.rb +56 -56
- metadata +3 -3
data/test/scheduler.rb
CHANGED
|
@@ -1,367 +1,367 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# This is an example and simplified scheduler for test purposes.
|
|
4
|
-
# It is not efficient for a large number of file descriptors as it uses IO.select().
|
|
5
|
-
# Production Fiber schedulers should use epoll/kqueue/etc.
|
|
6
|
-
|
|
7
|
-
require 'fiber'
|
|
8
|
-
require 'socket'
|
|
9
|
-
|
|
10
|
-
begin
|
|
11
|
-
require 'io/nonblock'
|
|
12
|
-
rescue LoadError
|
|
13
|
-
# Ignore.
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
class Scheduler
|
|
17
|
-
experimental = Warning[:experimental]
|
|
18
|
-
begin
|
|
19
|
-
Warning[:experimental] = false
|
|
20
|
-
IO::Buffer.new(0)
|
|
21
|
-
ensure
|
|
22
|
-
Warning[:experimental] = experimental
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def initialize
|
|
26
|
-
@readable = {}
|
|
27
|
-
@writable = {}
|
|
28
|
-
@waiting = {}
|
|
29
|
-
|
|
30
|
-
@closed = false
|
|
31
|
-
|
|
32
|
-
@lock = Thread::Mutex.new
|
|
33
|
-
@blocking = Hash.new.compare_by_identity
|
|
34
|
-
@ready = []
|
|
35
|
-
|
|
36
|
-
@urgent = IO.pipe
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
attr :readable
|
|
40
|
-
attr :writable
|
|
41
|
-
attr :waiting
|
|
42
|
-
|
|
43
|
-
def next_timeout
|
|
44
|
-
_fiber, timeout = @waiting.min_by{|key, value| value}
|
|
45
|
-
|
|
46
|
-
if timeout
|
|
47
|
-
offset = timeout - current_time
|
|
48
|
-
|
|
49
|
-
if offset < 0
|
|
50
|
-
return 0
|
|
51
|
-
else
|
|
52
|
-
return offset
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def run
|
|
58
|
-
# $stderr.puts [__method__, Fiber.current].inspect
|
|
59
|
-
|
|
60
|
-
while @readable.any? or @writable.any? or @waiting.any? or @blocking.any?
|
|
61
|
-
# Can only handle file descriptors up to 1024...
|
|
62
|
-
readable, writable = IO.select(@readable.keys + [@urgent.first], @writable.keys, [], next_timeout)
|
|
63
|
-
|
|
64
|
-
# puts "readable: #{readable}" if readable&.any?
|
|
65
|
-
# puts "writable: #{writable}" if writable&.any?
|
|
66
|
-
|
|
67
|
-
selected = {}
|
|
68
|
-
|
|
69
|
-
readable&.each do |io|
|
|
70
|
-
if fiber = @readable.delete(io)
|
|
71
|
-
@writable.delete(io) if @writable[io] == fiber
|
|
72
|
-
selected[fiber] = IO::READABLE
|
|
73
|
-
elsif io == @urgent.first
|
|
74
|
-
@urgent.first.read_nonblock(1024)
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
writable&.each do |io|
|
|
79
|
-
if fiber = @writable.delete(io)
|
|
80
|
-
@readable.delete(io) if @readable[io] == fiber
|
|
81
|
-
selected[fiber] = selected.fetch(fiber, 0) | IO::WRITABLE
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
selected.each do |fiber, events|
|
|
86
|
-
fiber.resume(events)
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
if @waiting.any?
|
|
90
|
-
time = current_time
|
|
91
|
-
waiting, @waiting = @waiting, {}
|
|
92
|
-
|
|
93
|
-
waiting.each do |fiber, timeout|
|
|
94
|
-
if fiber.alive?
|
|
95
|
-
if timeout <= time
|
|
96
|
-
fiber.resume
|
|
97
|
-
else
|
|
98
|
-
@waiting[fiber] = timeout
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
if @ready.any?
|
|
105
|
-
ready = nil
|
|
106
|
-
|
|
107
|
-
@lock.synchronize do
|
|
108
|
-
ready, @ready = @ready, []
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
ready.each do |fiber|
|
|
112
|
-
fiber.resume
|
|
113
|
-
end
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
def scheduler_close
|
|
119
|
-
close(true)
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
def close(internal = false)
|
|
123
|
-
# $stderr.puts [__method__, Fiber.current].inspect
|
|
124
|
-
|
|
125
|
-
unless internal
|
|
126
|
-
if Fiber.scheduler == self
|
|
127
|
-
return Fiber.set_scheduler(nil)
|
|
128
|
-
end
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
if @closed
|
|
132
|
-
raise "Scheduler already closed!"
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
self.run
|
|
136
|
-
ensure
|
|
137
|
-
if @urgent
|
|
138
|
-
@urgent.each(&:close)
|
|
139
|
-
@urgent = nil
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
@closed ||= true
|
|
143
|
-
|
|
144
|
-
# We freeze to detect any unintended modifications after the scheduler is closed:
|
|
145
|
-
self.freeze
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
def closed?
|
|
149
|
-
@closed
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
def current_time
|
|
153
|
-
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
def timeout_after(duration, klass, message, &block)
|
|
157
|
-
fiber = Fiber.current
|
|
158
|
-
|
|
159
|
-
self.fiber do
|
|
160
|
-
sleep(duration)
|
|
161
|
-
|
|
162
|
-
if fiber&.alive?
|
|
163
|
-
fiber.raise(klass, message)
|
|
164
|
-
end
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
begin
|
|
168
|
-
yield(duration)
|
|
169
|
-
ensure
|
|
170
|
-
fiber = nil
|
|
171
|
-
end
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
def process_wait(pid, flags)
|
|
175
|
-
# $stderr.puts [__method__, pid, flags, Fiber.current].inspect
|
|
176
|
-
|
|
177
|
-
# This is a very simple way to implement a non-blocking wait:
|
|
178
|
-
Thread.new do
|
|
179
|
-
Process::Status.wait(pid, flags)
|
|
180
|
-
end.value
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
def io_wait(io, events, duration)
|
|
184
|
-
# $stderr.puts [__method__, io, events, duration, Fiber.current].inspect
|
|
185
|
-
|
|
186
|
-
unless (events & IO::READABLE).zero?
|
|
187
|
-
@readable[io] = Fiber.current
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
unless (events & IO::WRITABLE).zero?
|
|
191
|
-
@writable[io] = Fiber.current
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
Fiber.yield
|
|
195
|
-
ensure
|
|
196
|
-
@readable.delete(io)
|
|
197
|
-
@writable.delete(io)
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
def io_select(...)
|
|
201
|
-
# Emulate the operation using a non-blocking thread:
|
|
202
|
-
Thread.new do
|
|
203
|
-
IO.select(...)
|
|
204
|
-
end.value
|
|
205
|
-
end
|
|
206
|
-
|
|
207
|
-
# Used for Kernel#sleep and Thread::Mutex#sleep
|
|
208
|
-
def kernel_sleep(duration = nil)
|
|
209
|
-
# $stderr.puts [__method__, duration, Fiber.current].inspect
|
|
210
|
-
|
|
211
|
-
self.block(:sleep, duration)
|
|
212
|
-
|
|
213
|
-
return true
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
# Used when blocking on synchronization (Thread::Mutex#lock,
|
|
217
|
-
# Thread::Queue#pop, Thread::SizedQueue#push, ...)
|
|
218
|
-
def block(blocker, timeout = nil)
|
|
219
|
-
# $stderr.puts [__method__, blocker, timeout].inspect
|
|
220
|
-
|
|
221
|
-
fiber = Fiber.current
|
|
222
|
-
|
|
223
|
-
if timeout
|
|
224
|
-
@waiting[fiber] = current_time + timeout
|
|
225
|
-
begin
|
|
226
|
-
Fiber.yield
|
|
227
|
-
ensure
|
|
228
|
-
# Remove from @waiting in the case #unblock was called before the timeout expired:
|
|
229
|
-
@waiting.delete(fiber)
|
|
230
|
-
end
|
|
231
|
-
else
|
|
232
|
-
@blocking[fiber] = true
|
|
233
|
-
begin
|
|
234
|
-
Fiber.yield
|
|
235
|
-
ensure
|
|
236
|
-
@blocking.delete(fiber)
|
|
237
|
-
end
|
|
238
|
-
end
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
# Used when synchronization wakes up a previously-blocked fiber
|
|
242
|
-
# (Thread::Mutex#unlock, Thread::Queue#push, ...).
|
|
243
|
-
# This might be called from another thread.
|
|
244
|
-
def unblock(blocker, fiber)
|
|
245
|
-
# $stderr.puts [__method__, blocker, fiber].inspect
|
|
246
|
-
# $stderr.puts blocker.backtrace.inspect
|
|
247
|
-
# $stderr.puts fiber.backtrace.inspect
|
|
248
|
-
|
|
249
|
-
@lock.synchronize do
|
|
250
|
-
@ready << fiber
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
io = @urgent.last
|
|
254
|
-
io.write_nonblock('.')
|
|
255
|
-
end
|
|
256
|
-
|
|
257
|
-
def fiber(&block)
|
|
258
|
-
fiber = Fiber.new(blocking: false, &block)
|
|
259
|
-
|
|
260
|
-
fiber.resume
|
|
261
|
-
|
|
262
|
-
return fiber
|
|
263
|
-
end
|
|
264
|
-
|
|
265
|
-
def fiber_interrupt(fiber, exception)
|
|
266
|
-
fiber.raise(exception)
|
|
267
|
-
end
|
|
268
|
-
|
|
269
|
-
def address_resolve(hostname)
|
|
270
|
-
Thread.new do
|
|
271
|
-
Addrinfo.getaddrinfo(hostname, nil).map(&:ip_address).uniq
|
|
272
|
-
end.value
|
|
273
|
-
end
|
|
274
|
-
end
|
|
275
|
-
|
|
276
|
-
class IOBufferScheduler < Scheduler
|
|
277
|
-
EAGAIN = -Errno::EAGAIN::Errno
|
|
278
|
-
|
|
279
|
-
def io_read(io, buffer, length, offset)
|
|
280
|
-
total = 0
|
|
281
|
-
io.nonblock = true
|
|
282
|
-
|
|
283
|
-
while true
|
|
284
|
-
maximum_size = buffer.size - offset
|
|
285
|
-
result = blocking{buffer.read(io, maximum_size, offset)}
|
|
286
|
-
|
|
287
|
-
if result > 0
|
|
288
|
-
total += result
|
|
289
|
-
offset += result
|
|
290
|
-
break if total >= length
|
|
291
|
-
elsif result == 0
|
|
292
|
-
break
|
|
293
|
-
elsif result == EAGAIN
|
|
294
|
-
if length > 0
|
|
295
|
-
self.io_wait(io, IO::READABLE, nil)
|
|
296
|
-
else
|
|
297
|
-
return result
|
|
298
|
-
end
|
|
299
|
-
elsif result < 0
|
|
300
|
-
return result
|
|
301
|
-
end
|
|
302
|
-
end
|
|
303
|
-
|
|
304
|
-
return total
|
|
305
|
-
end
|
|
306
|
-
|
|
307
|
-
def io_write(io, buffer, length, offset)
|
|
308
|
-
total = 0
|
|
309
|
-
io.nonblock = true
|
|
310
|
-
|
|
311
|
-
while true
|
|
312
|
-
maximum_size = buffer.size - offset
|
|
313
|
-
result = blocking{buffer.write(io, maximum_size, offset)}
|
|
314
|
-
|
|
315
|
-
if result > 0
|
|
316
|
-
total += result
|
|
317
|
-
offset += result
|
|
318
|
-
break if total >= length
|
|
319
|
-
elsif result == 0
|
|
320
|
-
break
|
|
321
|
-
elsif result == EAGAIN
|
|
322
|
-
if length > 0
|
|
323
|
-
self.io_wait(io, IO::WRITABLE, nil)
|
|
324
|
-
else
|
|
325
|
-
return result
|
|
326
|
-
end
|
|
327
|
-
elsif result < 0
|
|
328
|
-
return result
|
|
329
|
-
end
|
|
330
|
-
end
|
|
331
|
-
|
|
332
|
-
return total
|
|
333
|
-
end
|
|
334
|
-
|
|
335
|
-
def blocking(&block)
|
|
336
|
-
Fiber.blocking(&block)
|
|
337
|
-
end
|
|
338
|
-
end
|
|
339
|
-
|
|
340
|
-
class BrokenUnblockScheduler < Scheduler
|
|
341
|
-
def unblock(blocker, fiber)
|
|
342
|
-
super
|
|
343
|
-
|
|
344
|
-
raise "Broken unblock!"
|
|
345
|
-
end
|
|
346
|
-
end
|
|
347
|
-
|
|
348
|
-
class SleepingUnblockScheduler < Scheduler
|
|
349
|
-
# This method is invoked when the thread is exiting.
|
|
350
|
-
def unblock(blocker, fiber)
|
|
351
|
-
super
|
|
352
|
-
|
|
353
|
-
# This changes the current thread state to `THREAD_RUNNING` which causes `thread_join_sleep` to hang.
|
|
354
|
-
sleep(0.1)
|
|
355
|
-
end
|
|
356
|
-
end
|
|
357
|
-
|
|
358
|
-
class SleepingBlockingScheduler < Scheduler
|
|
359
|
-
def kernel_sleep(duration = nil)
|
|
360
|
-
# Deliberaly sleep in a blocking state which can trigger a deadlock if the implementation is not correct.
|
|
361
|
-
Fiber.blocking{sleep 0.0001}
|
|
362
|
-
|
|
363
|
-
self.block(:sleep, duration)
|
|
364
|
-
|
|
365
|
-
return true
|
|
366
|
-
end
|
|
367
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# This is an example and simplified scheduler for test purposes.
|
|
4
|
+
# It is not efficient for a large number of file descriptors as it uses IO.select().
|
|
5
|
+
# Production Fiber schedulers should use epoll/kqueue/etc.
|
|
6
|
+
|
|
7
|
+
require 'fiber'
|
|
8
|
+
require 'socket'
|
|
9
|
+
|
|
10
|
+
begin
|
|
11
|
+
require 'io/nonblock'
|
|
12
|
+
rescue LoadError
|
|
13
|
+
# Ignore.
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class Scheduler
|
|
17
|
+
experimental = Warning[:experimental]
|
|
18
|
+
begin
|
|
19
|
+
Warning[:experimental] = false
|
|
20
|
+
IO::Buffer.new(0)
|
|
21
|
+
ensure
|
|
22
|
+
Warning[:experimental] = experimental
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def initialize
|
|
26
|
+
@readable = {}
|
|
27
|
+
@writable = {}
|
|
28
|
+
@waiting = {}
|
|
29
|
+
|
|
30
|
+
@closed = false
|
|
31
|
+
|
|
32
|
+
@lock = Thread::Mutex.new
|
|
33
|
+
@blocking = Hash.new.compare_by_identity
|
|
34
|
+
@ready = []
|
|
35
|
+
|
|
36
|
+
@urgent = IO.pipe
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
attr :readable
|
|
40
|
+
attr :writable
|
|
41
|
+
attr :waiting
|
|
42
|
+
|
|
43
|
+
def next_timeout
|
|
44
|
+
_fiber, timeout = @waiting.min_by{|key, value| value}
|
|
45
|
+
|
|
46
|
+
if timeout
|
|
47
|
+
offset = timeout - current_time
|
|
48
|
+
|
|
49
|
+
if offset < 0
|
|
50
|
+
return 0
|
|
51
|
+
else
|
|
52
|
+
return offset
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def run
|
|
58
|
+
# $stderr.puts [__method__, Fiber.current].inspect
|
|
59
|
+
|
|
60
|
+
while @readable.any? or @writable.any? or @waiting.any? or @blocking.any?
|
|
61
|
+
# Can only handle file descriptors up to 1024...
|
|
62
|
+
readable, writable = IO.select(@readable.keys + [@urgent.first], @writable.keys, [], next_timeout)
|
|
63
|
+
|
|
64
|
+
# puts "readable: #{readable}" if readable&.any?
|
|
65
|
+
# puts "writable: #{writable}" if writable&.any?
|
|
66
|
+
|
|
67
|
+
selected = {}
|
|
68
|
+
|
|
69
|
+
readable&.each do |io|
|
|
70
|
+
if fiber = @readable.delete(io)
|
|
71
|
+
@writable.delete(io) if @writable[io] == fiber
|
|
72
|
+
selected[fiber] = IO::READABLE
|
|
73
|
+
elsif io == @urgent.first
|
|
74
|
+
@urgent.first.read_nonblock(1024)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
writable&.each do |io|
|
|
79
|
+
if fiber = @writable.delete(io)
|
|
80
|
+
@readable.delete(io) if @readable[io] == fiber
|
|
81
|
+
selected[fiber] = selected.fetch(fiber, 0) | IO::WRITABLE
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
selected.each do |fiber, events|
|
|
86
|
+
fiber.resume(events)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
if @waiting.any?
|
|
90
|
+
time = current_time
|
|
91
|
+
waiting, @waiting = @waiting, {}
|
|
92
|
+
|
|
93
|
+
waiting.each do |fiber, timeout|
|
|
94
|
+
if fiber.alive?
|
|
95
|
+
if timeout <= time
|
|
96
|
+
fiber.resume
|
|
97
|
+
else
|
|
98
|
+
@waiting[fiber] = timeout
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
if @ready.any?
|
|
105
|
+
ready = nil
|
|
106
|
+
|
|
107
|
+
@lock.synchronize do
|
|
108
|
+
ready, @ready = @ready, []
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
ready.each do |fiber|
|
|
112
|
+
fiber.resume
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def scheduler_close
|
|
119
|
+
close(true)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def close(internal = false)
|
|
123
|
+
# $stderr.puts [__method__, Fiber.current].inspect
|
|
124
|
+
|
|
125
|
+
unless internal
|
|
126
|
+
if Fiber.scheduler == self
|
|
127
|
+
return Fiber.set_scheduler(nil)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
if @closed
|
|
132
|
+
raise "Scheduler already closed!"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
self.run
|
|
136
|
+
ensure
|
|
137
|
+
if @urgent
|
|
138
|
+
@urgent.each(&:close)
|
|
139
|
+
@urgent = nil
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
@closed ||= true
|
|
143
|
+
|
|
144
|
+
# We freeze to detect any unintended modifications after the scheduler is closed:
|
|
145
|
+
self.freeze
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def closed?
|
|
149
|
+
@closed
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def current_time
|
|
153
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def timeout_after(duration, klass, message, &block)
|
|
157
|
+
fiber = Fiber.current
|
|
158
|
+
|
|
159
|
+
self.fiber do
|
|
160
|
+
sleep(duration)
|
|
161
|
+
|
|
162
|
+
if fiber&.alive?
|
|
163
|
+
fiber.raise(klass, message)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
begin
|
|
168
|
+
yield(duration)
|
|
169
|
+
ensure
|
|
170
|
+
fiber = nil
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def process_wait(pid, flags)
|
|
175
|
+
# $stderr.puts [__method__, pid, flags, Fiber.current].inspect
|
|
176
|
+
|
|
177
|
+
# This is a very simple way to implement a non-blocking wait:
|
|
178
|
+
Thread.new do
|
|
179
|
+
Process::Status.wait(pid, flags)
|
|
180
|
+
end.value
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def io_wait(io, events, duration)
|
|
184
|
+
# $stderr.puts [__method__, io, events, duration, Fiber.current].inspect
|
|
185
|
+
|
|
186
|
+
unless (events & IO::READABLE).zero?
|
|
187
|
+
@readable[io] = Fiber.current
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
unless (events & IO::WRITABLE).zero?
|
|
191
|
+
@writable[io] = Fiber.current
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
Fiber.yield
|
|
195
|
+
ensure
|
|
196
|
+
@readable.delete(io)
|
|
197
|
+
@writable.delete(io)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def io_select(...)
|
|
201
|
+
# Emulate the operation using a non-blocking thread:
|
|
202
|
+
Thread.new do
|
|
203
|
+
IO.select(...)
|
|
204
|
+
end.value
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Used for Kernel#sleep and Thread::Mutex#sleep
|
|
208
|
+
def kernel_sleep(duration = nil)
|
|
209
|
+
# $stderr.puts [__method__, duration, Fiber.current].inspect
|
|
210
|
+
|
|
211
|
+
self.block(:sleep, duration)
|
|
212
|
+
|
|
213
|
+
return true
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Used when blocking on synchronization (Thread::Mutex#lock,
|
|
217
|
+
# Thread::Queue#pop, Thread::SizedQueue#push, ...)
|
|
218
|
+
def block(blocker, timeout = nil)
|
|
219
|
+
# $stderr.puts [__method__, blocker, timeout].inspect
|
|
220
|
+
|
|
221
|
+
fiber = Fiber.current
|
|
222
|
+
|
|
223
|
+
if timeout
|
|
224
|
+
@waiting[fiber] = current_time + timeout
|
|
225
|
+
begin
|
|
226
|
+
Fiber.yield
|
|
227
|
+
ensure
|
|
228
|
+
# Remove from @waiting in the case #unblock was called before the timeout expired:
|
|
229
|
+
@waiting.delete(fiber)
|
|
230
|
+
end
|
|
231
|
+
else
|
|
232
|
+
@blocking[fiber] = true
|
|
233
|
+
begin
|
|
234
|
+
Fiber.yield
|
|
235
|
+
ensure
|
|
236
|
+
@blocking.delete(fiber)
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Used when synchronization wakes up a previously-blocked fiber
|
|
242
|
+
# (Thread::Mutex#unlock, Thread::Queue#push, ...).
|
|
243
|
+
# This might be called from another thread.
|
|
244
|
+
def unblock(blocker, fiber)
|
|
245
|
+
# $stderr.puts [__method__, blocker, fiber].inspect
|
|
246
|
+
# $stderr.puts blocker.backtrace.inspect
|
|
247
|
+
# $stderr.puts fiber.backtrace.inspect
|
|
248
|
+
|
|
249
|
+
@lock.synchronize do
|
|
250
|
+
@ready << fiber
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
io = @urgent.last
|
|
254
|
+
io.write_nonblock('.')
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def fiber(&block)
|
|
258
|
+
fiber = Fiber.new(blocking: false, &block)
|
|
259
|
+
|
|
260
|
+
fiber.resume
|
|
261
|
+
|
|
262
|
+
return fiber
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def fiber_interrupt(fiber, exception)
|
|
266
|
+
fiber.raise(exception)
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def address_resolve(hostname)
|
|
270
|
+
Thread.new do
|
|
271
|
+
Addrinfo.getaddrinfo(hostname, nil).map(&:ip_address).uniq
|
|
272
|
+
end.value
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
class IOBufferScheduler < Scheduler
|
|
277
|
+
EAGAIN = -Errno::EAGAIN::Errno
|
|
278
|
+
|
|
279
|
+
def io_read(io, buffer, length, offset)
|
|
280
|
+
total = 0
|
|
281
|
+
io.nonblock = true
|
|
282
|
+
|
|
283
|
+
while true
|
|
284
|
+
maximum_size = buffer.size - offset
|
|
285
|
+
result = blocking{buffer.read(io, maximum_size, offset)}
|
|
286
|
+
|
|
287
|
+
if result > 0
|
|
288
|
+
total += result
|
|
289
|
+
offset += result
|
|
290
|
+
break if total >= length
|
|
291
|
+
elsif result == 0
|
|
292
|
+
break
|
|
293
|
+
elsif result == EAGAIN
|
|
294
|
+
if length > 0
|
|
295
|
+
self.io_wait(io, IO::READABLE, nil)
|
|
296
|
+
else
|
|
297
|
+
return result
|
|
298
|
+
end
|
|
299
|
+
elsif result < 0
|
|
300
|
+
return result
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
return total
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def io_write(io, buffer, length, offset)
|
|
308
|
+
total = 0
|
|
309
|
+
io.nonblock = true
|
|
310
|
+
|
|
311
|
+
while true
|
|
312
|
+
maximum_size = buffer.size - offset
|
|
313
|
+
result = blocking{buffer.write(io, maximum_size, offset)}
|
|
314
|
+
|
|
315
|
+
if result > 0
|
|
316
|
+
total += result
|
|
317
|
+
offset += result
|
|
318
|
+
break if total >= length
|
|
319
|
+
elsif result == 0
|
|
320
|
+
break
|
|
321
|
+
elsif result == EAGAIN
|
|
322
|
+
if length > 0
|
|
323
|
+
self.io_wait(io, IO::WRITABLE, nil)
|
|
324
|
+
else
|
|
325
|
+
return result
|
|
326
|
+
end
|
|
327
|
+
elsif result < 0
|
|
328
|
+
return result
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
return total
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def blocking(&block)
|
|
336
|
+
Fiber.blocking(&block)
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
class BrokenUnblockScheduler < Scheduler
|
|
341
|
+
def unblock(blocker, fiber)
|
|
342
|
+
super
|
|
343
|
+
|
|
344
|
+
raise "Broken unblock!"
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
class SleepingUnblockScheduler < Scheduler
|
|
349
|
+
# This method is invoked when the thread is exiting.
|
|
350
|
+
def unblock(blocker, fiber)
|
|
351
|
+
super
|
|
352
|
+
|
|
353
|
+
# This changes the current thread state to `THREAD_RUNNING` which causes `thread_join_sleep` to hang.
|
|
354
|
+
sleep(0.1)
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
class SleepingBlockingScheduler < Scheduler
|
|
359
|
+
def kernel_sleep(duration = nil)
|
|
360
|
+
# Deliberaly sleep in a blocking state which can trigger a deadlock if the implementation is not correct.
|
|
361
|
+
Fiber.blocking{sleep 0.0001}
|
|
362
|
+
|
|
363
|
+
self.block(:sleep, duration)
|
|
364
|
+
|
|
365
|
+
return true
|
|
366
|
+
end
|
|
367
|
+
end
|