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