SystemTimer 1.0 → 1.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/ChangeLog +13 -0
- data/README +7 -0
- data/ext/system_timer/extconf.rb +0 -0
- data/ext/system_timer/system_timer_native.c +204 -127
- data/lib/system_timer.rb +65 -30
- data/lib/system_timer/concurrent_timer_pool.rb +83 -0
- data/lib/system_timer/thread_timer.rb +21 -0
- data/lib/system_timer_stub.rb +5 -0
- data/test/all_tests.rb +3 -1
- data/test/system_timer/concurrent_timer_pool_unit_test.rb +274 -0
- data/test/system_timer/thread_timer_test.rb +20 -0
- data/test/system_timer_functional_test.rb +260 -0
- data/test/system_timer_unit_test.rb +96 -0
- data/test/test_helper.rb +10 -0
- metadata +10 -4
- data/test/system_timer_test.rb +0 -121
@@ -0,0 +1,83 @@
|
|
1
|
+
# Copyright 2008 David Vollbracht & Philippe Hanrigou
|
2
|
+
|
3
|
+
module SystemTimer
|
4
|
+
|
5
|
+
class ConcurrentTimerPool
|
6
|
+
|
7
|
+
def registered_timers
|
8
|
+
@timers ||= []
|
9
|
+
end
|
10
|
+
|
11
|
+
def register_timer(trigger_time, thread)
|
12
|
+
new_timer = ThreadTimer.new(trigger_time, thread)
|
13
|
+
registered_timers << new_timer
|
14
|
+
new_timer
|
15
|
+
end
|
16
|
+
|
17
|
+
def add_timer(interval_in_seconds)
|
18
|
+
new_timer = register_timer(Time.now.to_i + interval_in_seconds, Thread.current)
|
19
|
+
log_registered_timers if SystemTimer.debug_enabled?
|
20
|
+
new_timer
|
21
|
+
end
|
22
|
+
|
23
|
+
def cancel(registered_timer)
|
24
|
+
registered_timers.delete registered_timer
|
25
|
+
end
|
26
|
+
|
27
|
+
def first_timer?
|
28
|
+
registered_timers.size == 1
|
29
|
+
end
|
30
|
+
|
31
|
+
def next_timer
|
32
|
+
registered_timers.sort {|x,y| x.trigger_time <=> y.trigger_time}.first
|
33
|
+
end
|
34
|
+
|
35
|
+
def next_trigger_time
|
36
|
+
timer = next_timer
|
37
|
+
timer.trigger_time unless timer.nil?
|
38
|
+
end
|
39
|
+
|
40
|
+
def next_trigger_interval_in_seconds
|
41
|
+
timer = next_timer
|
42
|
+
[0, (timer.trigger_time - Time.now.to_i)].max unless timer.nil?
|
43
|
+
end
|
44
|
+
|
45
|
+
def next_expired_timer(now_in_seconds_since_epoch)
|
46
|
+
candidate_timer = next_timer
|
47
|
+
return nil if candidate_timer.nil? ||
|
48
|
+
candidate_timer.trigger_time > now_in_seconds_since_epoch
|
49
|
+
candidate_timer
|
50
|
+
end
|
51
|
+
|
52
|
+
def trigger_next_expired_timer_at(now_in_seconds_since_epoch)
|
53
|
+
timer = next_expired_timer(now_in_seconds_since_epoch)
|
54
|
+
return if timer.nil?
|
55
|
+
|
56
|
+
cancel timer
|
57
|
+
log_timeout_received(timer) if SystemTimer.debug_enabled?
|
58
|
+
timer.thread.raise Timeout::Error.new("time's up!")
|
59
|
+
end
|
60
|
+
|
61
|
+
def trigger_next_expired_timer
|
62
|
+
trigger_next_expired_timer_at Time.now.to_i
|
63
|
+
end
|
64
|
+
|
65
|
+
def log_timeout_received(thread_timer) #:nodoc:
|
66
|
+
puts <<-EOS
|
67
|
+
==== Triger Timer ==== #{thread_timer}
|
68
|
+
Main thread : #{Thread.main}
|
69
|
+
Timed_thread : #{thread_timer.thread}
|
70
|
+
All Threads : #{Thread.list.inspect}
|
71
|
+
EOS
|
72
|
+
log_registered_timers
|
73
|
+
end
|
74
|
+
|
75
|
+
def log_registered_timers #:nodoc:
|
76
|
+
puts <<-EOS
|
77
|
+
Registered Timers: #{registered_timers.collect do |t| t.to_s end}
|
78
|
+
EOS
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Copyright 2008 David Vollbracht & Philippe Hanrigou
|
2
|
+
|
3
|
+
module SystemTimer
|
4
|
+
|
5
|
+
# Timer saving associated thread. This is needed because we trigger timers
|
6
|
+
# from a Ruby signal handler and Ruby signals are always delivered to
|
7
|
+
# main thread.
|
8
|
+
class ThreadTimer
|
9
|
+
attr_reader :trigger_time, :thread
|
10
|
+
|
11
|
+
def initialize(trigger_time, thread)
|
12
|
+
@trigger_time = trigger_time
|
13
|
+
@thread = thread
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
"<ThreadTimer :time => #{trigger_time}, :thread => #{thread}>"
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
data/lib/system_timer_stub.rb
CHANGED
data/test/all_tests.rb
CHANGED
@@ -0,0 +1,274 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
|
3
|
+
unit_tests do
|
4
|
+
|
5
|
+
test "registered_timers is empty when there is no registered timers" do
|
6
|
+
assert_equal [], SystemTimer::ConcurrentTimerPool.new.registered_timers
|
7
|
+
end
|
8
|
+
|
9
|
+
test "a new timer is added to the registered timer list when you register a timer" do
|
10
|
+
pool = SystemTimer::ConcurrentTimerPool.new
|
11
|
+
pool.register_timer :a_trigger_time, :a_thread
|
12
|
+
assert_equal [[:a_trigger_time, :a_thread]],
|
13
|
+
pool.registered_timers.collect {|t| [t.trigger_time, t.thread] }
|
14
|
+
end
|
15
|
+
|
16
|
+
test "register_timer returns the timer that was just added to the pool" do
|
17
|
+
pool = SystemTimer::ConcurrentTimerPool.new
|
18
|
+
timer = pool.register_timer :a_trigger_time, :a_thread
|
19
|
+
assert_equal [:a_trigger_time, :a_thread], [timer.trigger_time, timer.thread]
|
20
|
+
end
|
21
|
+
|
22
|
+
test "add_timer is a shortcut method to register a timer given its interval" do
|
23
|
+
pool = SystemTimer::ConcurrentTimerPool.new
|
24
|
+
Thread.stubs(:current).returns(:the_current_thread)
|
25
|
+
now = Time.now
|
26
|
+
Time.stubs(:now).returns(now)
|
27
|
+
|
28
|
+
pool.expects(:register_timer).with(now.to_i + 15, :the_current_thread)
|
29
|
+
pool.add_timer 15
|
30
|
+
end
|
31
|
+
|
32
|
+
test "cancel removes a timer from the registered timer list" do
|
33
|
+
pool = SystemTimer::ConcurrentTimerPool.new
|
34
|
+
registered_timer = pool.register_timer :a_trigger_time, :a_thread
|
35
|
+
pool.cancel registered_timer
|
36
|
+
assert_equal [], pool.registered_timers
|
37
|
+
end
|
38
|
+
|
39
|
+
test "cancel does not complain when timer is cancelled " +
|
40
|
+
"(useful for ensure blocks)" do
|
41
|
+
|
42
|
+
pool = SystemTimer::ConcurrentTimerPool.new
|
43
|
+
a_timer = pool.add_timer 123
|
44
|
+
another_timer = pool.add_timer 456
|
45
|
+
pool.cancel(another_timer)
|
46
|
+
pool.cancel(another_timer)
|
47
|
+
assert_equal [a_timer], pool.registered_timers
|
48
|
+
end
|
49
|
+
|
50
|
+
test "first_timer? returns false when there is no timer" do
|
51
|
+
assert_equal false, SystemTimer::ConcurrentTimerPool.new.first_timer?
|
52
|
+
end
|
53
|
+
|
54
|
+
test "first_timer? returns true when there is a single timer" do
|
55
|
+
pool = SystemTimer::ConcurrentTimerPool.new
|
56
|
+
pool.add_timer 7
|
57
|
+
assert_equal true, pool.first_timer?
|
58
|
+
end
|
59
|
+
|
60
|
+
test "first_timer? returns false when there is more than one timer" do
|
61
|
+
pool = SystemTimer::ConcurrentTimerPool.new
|
62
|
+
pool.add_timer 7
|
63
|
+
pool.add_timer 3
|
64
|
+
assert_equal false, pool.first_timer?
|
65
|
+
end
|
66
|
+
|
67
|
+
test "first_timer? returns false when there is a single timer left" do
|
68
|
+
pool = SystemTimer::ConcurrentTimerPool.new
|
69
|
+
first_timer = pool.add_timer 7
|
70
|
+
pool.add_timer 3
|
71
|
+
pool.cancel first_timer
|
72
|
+
assert_equal true, pool.first_timer?
|
73
|
+
end
|
74
|
+
|
75
|
+
test "next expired timer return nil when there is no registered timer" do
|
76
|
+
assert_nil SystemTimer::ConcurrentTimerPool.new.next_expired_timer(24)
|
77
|
+
end
|
78
|
+
|
79
|
+
test "next_timer returns nil when there is no registered timer" do
|
80
|
+
assert_nil SystemTimer::ConcurrentTimerPool.new.next_timer
|
81
|
+
end
|
82
|
+
|
83
|
+
test "next_timer returns the registered timer when " +
|
84
|
+
"there is only one registered timer" do
|
85
|
+
|
86
|
+
pool = SystemTimer::ConcurrentTimerPool.new
|
87
|
+
the_timer = pool.register_timer 24, stub_everything
|
88
|
+
assert_equal the_timer, pool.next_timer
|
89
|
+
end
|
90
|
+
|
91
|
+
test "next_timer returns the trigger time of the first timer to" +
|
92
|
+
"expire when there is more than one registered timer" do
|
93
|
+
|
94
|
+
pool = SystemTimer::ConcurrentTimerPool.new
|
95
|
+
late_timer = pool.register_timer 64, stub_everything
|
96
|
+
early_timer = pool.register_timer 24, stub_everything
|
97
|
+
assert_equal early_timer, pool.next_timer
|
98
|
+
end
|
99
|
+
|
100
|
+
test "next_trigger_time returns nil when next_timer is nil" do
|
101
|
+
pool = SystemTimer::ConcurrentTimerPool.new
|
102
|
+
pool.expects(:next_timer).returns(nil)
|
103
|
+
assert_nil pool.next_trigger_time
|
104
|
+
end
|
105
|
+
|
106
|
+
test "next_trigger_time returns trigger time of next timer when " +
|
107
|
+
"next timer is not nil" do
|
108
|
+
|
109
|
+
pool = SystemTimer::ConcurrentTimerPool.new
|
110
|
+
the_timer = SystemTimer::ThreadTimer.new 24, stub_everything
|
111
|
+
pool.expects(:next_timer).returns(the_timer)
|
112
|
+
assert_equal 24, pool.next_trigger_time
|
113
|
+
end
|
114
|
+
|
115
|
+
test "next_trigger_interval_in_seconds returns nil when next_timer is nil" do
|
116
|
+
pool = SystemTimer::ConcurrentTimerPool.new
|
117
|
+
pool.expects(:next_timer).returns(nil)
|
118
|
+
assert_nil pool.next_trigger_interval_in_seconds
|
119
|
+
end
|
120
|
+
|
121
|
+
test "next_trigger_interval_in_seconds returns the interval between now and " +
|
122
|
+
"next_timer timer time when next timer is in the future" do
|
123
|
+
pool = SystemTimer::ConcurrentTimerPool.new
|
124
|
+
now = Time.now
|
125
|
+
Time.stubs(:now).returns(now)
|
126
|
+
next_timer = SystemTimer::ThreadTimer.new((now.to_i + 7), stub_everything)
|
127
|
+
pool.expects(:next_timer).returns(next_timer)
|
128
|
+
assert_equal 7, pool.next_trigger_interval_in_seconds
|
129
|
+
end
|
130
|
+
|
131
|
+
test "next_trigger_interval_in_seconds returns 0 when next timer is now" do
|
132
|
+
pool = SystemTimer::ConcurrentTimerPool.new
|
133
|
+
now = Time.now
|
134
|
+
Time.stubs(:now).returns(now)
|
135
|
+
next_timer = SystemTimer::ThreadTimer.new now.to_i, stub_everything
|
136
|
+
pool.expects(:next_timer).returns(next_timer)
|
137
|
+
assert_equal 0, pool.next_trigger_interval_in_seconds
|
138
|
+
end
|
139
|
+
|
140
|
+
test "next_trigger_interval_in_seconds returns 0 when next timer is in the past" do
|
141
|
+
pool = SystemTimer::ConcurrentTimerPool.new
|
142
|
+
now = Time.now
|
143
|
+
Time.stubs(:now).returns(now)
|
144
|
+
next_timer = SystemTimer::ThreadTimer.new((now.to_i - 3), stub_everything)
|
145
|
+
pool.expects(:next_timer).returns(next_timer)
|
146
|
+
assert_equal 0, pool.next_trigger_interval_in_seconds
|
147
|
+
end
|
148
|
+
|
149
|
+
test "next_expired_timer returns the timer that was trigerred" +
|
150
|
+
"when a timer has expired" do
|
151
|
+
|
152
|
+
pool = SystemTimer::ConcurrentTimerPool.new
|
153
|
+
the_timer = pool.register_timer 24, :a_thread
|
154
|
+
assert_equal the_timer, pool.next_expired_timer(24)
|
155
|
+
end
|
156
|
+
|
157
|
+
test "next_expired_timer returns nil when no timer has expired yet" do
|
158
|
+
pool = SystemTimer::ConcurrentTimerPool.new
|
159
|
+
pool.register_timer 24, :a_thread
|
160
|
+
assert_nil pool.next_expired_timer(23)
|
161
|
+
end
|
162
|
+
|
163
|
+
test "next_expired_timer returns the timer that first expired " +
|
164
|
+
"when there is more than one expired timer" do
|
165
|
+
|
166
|
+
pool = SystemTimer::ConcurrentTimerPool.new
|
167
|
+
last_to_expire = pool.register_timer 64, :a_thread
|
168
|
+
first_to_expire = pool.register_timer 24, :a_thread
|
169
|
+
assert_equal first_to_expire, pool.next_expired_timer(100)
|
170
|
+
end
|
171
|
+
|
172
|
+
test "trigger_next_expired_timer_at does not raise when there is no registered timer" do
|
173
|
+
SystemTimer::ConcurrentTimerPool.new.trigger_next_expired_timer_at 1234
|
174
|
+
end
|
175
|
+
|
176
|
+
test "trigger_next_expired_timer_at raises a TimeoutError in the context of " +
|
177
|
+
"its thread when there is a registered timer that has expired" do
|
178
|
+
|
179
|
+
pool = SystemTimer::ConcurrentTimerPool.new
|
180
|
+
the_thread = mock('thread')
|
181
|
+
|
182
|
+
Timeout::Error.expects(:new).with("time's up!").returns(:the_exception)
|
183
|
+
the_thread.expects(:raise).with(:the_exception)
|
184
|
+
pool.register_timer 24, the_thread
|
185
|
+
pool.trigger_next_expired_timer_at 24
|
186
|
+
end
|
187
|
+
|
188
|
+
test "trigger_next_expired_timer_at does not raise when registered timer has not expired" do
|
189
|
+
pool = SystemTimer::ConcurrentTimerPool.new
|
190
|
+
pool.register_timer 24, stub_everything
|
191
|
+
pool.trigger_next_expired_timer_at(10)
|
192
|
+
end
|
193
|
+
|
194
|
+
test "trigger_next_expired_timer_at triggers the first registered timer that expired" do
|
195
|
+
pool = SystemTimer::ConcurrentTimerPool.new
|
196
|
+
first_to_expire = pool.register_timer 24, stub_everything
|
197
|
+
second_to_expire = pool.register_timer 64, stub_everything
|
198
|
+
pool.trigger_next_expired_timer_at(100)
|
199
|
+
assert_equal [second_to_expire], pool.registered_timers
|
200
|
+
end
|
201
|
+
|
202
|
+
test "trigger_next_expired_timer_at triggers the first registered timer that " +
|
203
|
+
"expired whatever the timer insertion order is" do
|
204
|
+
|
205
|
+
pool = SystemTimer::ConcurrentTimerPool.new
|
206
|
+
second_to_expire = pool.register_timer 64, stub_everything
|
207
|
+
first_to_expire = pool.register_timer 24, stub_everything
|
208
|
+
pool.trigger_next_expired_timer_at(100)
|
209
|
+
assert_equal [second_to_expire], pool.registered_timers
|
210
|
+
end
|
211
|
+
|
212
|
+
test "trigger_next_expired_timer_at remove the expired timer from the pool" do
|
213
|
+
pool = SystemTimer::ConcurrentTimerPool.new
|
214
|
+
pool.register_timer 24, stub_everything
|
215
|
+
pool.trigger_next_expired_timer_at 24
|
216
|
+
end
|
217
|
+
|
218
|
+
test "trigger_next_expired_timer_at logs timeout a registered timer has expired" +
|
219
|
+
"and SystemTimer debug mode is enabled " do
|
220
|
+
pool = SystemTimer::ConcurrentTimerPool.new
|
221
|
+
the_timer = pool.register_timer 24, stub_everything
|
222
|
+
SystemTimer.stubs(:debug_enabled?).returns(true)
|
223
|
+
|
224
|
+
pool.expects(:log_timeout_received).with(the_timer)
|
225
|
+
pool.trigger_next_expired_timer_at 24
|
226
|
+
end
|
227
|
+
|
228
|
+
test "trigger_next_expired_timer_at does not logs timeoout when SystemTimer " +
|
229
|
+
"debug mode is disabled " do
|
230
|
+
|
231
|
+
pool = SystemTimer::ConcurrentTimerPool.new
|
232
|
+
the_timer = pool.register_timer 24, stub_everything
|
233
|
+
SystemTimer.stubs(:debug_enabled?).returns(false)
|
234
|
+
|
235
|
+
pool.expects(:log_timeout_received).never
|
236
|
+
pool.trigger_next_expired_timer_at 24
|
237
|
+
end
|
238
|
+
|
239
|
+
test "trigger_next_expired_timer_at does not logs timeout no registered timer " +
|
240
|
+
"has expired and SystemTimer debug mode is enabled " do
|
241
|
+
|
242
|
+
pool = SystemTimer::ConcurrentTimerPool.new
|
243
|
+
the_timer = pool.register_timer 24, stub_everything
|
244
|
+
SystemTimer.stubs(:debug_enabled?).returns(true)
|
245
|
+
|
246
|
+
pool.expects(:log_timeout_received).never
|
247
|
+
pool.trigger_next_expired_timer_at 23
|
248
|
+
end
|
249
|
+
|
250
|
+
test "trigger_next_expired_timer is a shorcut method calling " +
|
251
|
+
"trigger_next_expired_timer_at with current epoch time" do
|
252
|
+
|
253
|
+
now = Time.now
|
254
|
+
pool = SystemTimer::ConcurrentTimerPool.new
|
255
|
+
Time.stubs(:now).returns(now)
|
256
|
+
|
257
|
+
pool.expects(:trigger_next_expired_timer_at).with(now.to_i)
|
258
|
+
pool.trigger_next_expired_timer
|
259
|
+
end
|
260
|
+
|
261
|
+
test "log_timeout_received does not raise" do
|
262
|
+
original_stdout = $stdout
|
263
|
+
begin
|
264
|
+
stdout = StringIO.new
|
265
|
+
$stdout = stdout
|
266
|
+
|
267
|
+
SystemTimer::ConcurrentTimerPool.new.log_timeout_received(SystemTimer::ThreadTimer.new(:a_time, :a_thread))
|
268
|
+
assert_match %r{==== Triger Timer ====}, stdout.string
|
269
|
+
ensure
|
270
|
+
$stdout = original_stdout
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
|
3
|
+
unit_tests do
|
4
|
+
|
5
|
+
test "trigger_time returns the time given in the constructor" do
|
6
|
+
timer = SystemTimer::ThreadTimer.new(:a_tigger_time, nil)
|
7
|
+
assert_equal :a_tigger_time, timer.trigger_time
|
8
|
+
end
|
9
|
+
|
10
|
+
test "thread returns the thread given in the constructor" do
|
11
|
+
timer = SystemTimer::ThreadTimer.new(nil, :a_thread)
|
12
|
+
assert_equal :a_thread, timer.thread
|
13
|
+
end
|
14
|
+
|
15
|
+
test "to_s retruns a human friendly description of the timer" do
|
16
|
+
assert_match /<ThreadTimer :time => 24, :thread => #<Thread(.*)>>/,
|
17
|
+
SystemTimer::ThreadTimer.new(24, Thread.current).to_s
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,260 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
functional_tests do
|
4
|
+
|
5
|
+
DEFAULT_ERROR_MARGIN = 2
|
6
|
+
|
7
|
+
test "original_ruby_sigalrm_handler is nil after reset" do
|
8
|
+
SystemTimer.send(:install_ruby_sigalrm_handler)
|
9
|
+
SystemTimer.send(:reset_original_ruby_sigalrm_handler)
|
10
|
+
assert_nil SystemTimer.send(:original_ruby_sigalrm_handler)
|
11
|
+
end
|
12
|
+
|
13
|
+
test "original_ruby_sigalrm_handler is set to existing handler after " +
|
14
|
+
"install_ruby_sigalrm_handler when save_previous_handler is true" do
|
15
|
+
SystemTimer.expects(:trap).with('SIGALRM').returns(:an_existing_handler)
|
16
|
+
SystemTimer.send(:install_ruby_sigalrm_handler)
|
17
|
+
assert_equal :an_existing_handler, SystemTimer.send(:original_ruby_sigalrm_handler)
|
18
|
+
end
|
19
|
+
|
20
|
+
test "restore_original_ruby_sigalrm_handler traps sigalrm using original_ruby_sigalrm_handler" do
|
21
|
+
SystemTimer.stubs(:original_ruby_sigalrm_handler).returns(:the_original_handler)
|
22
|
+
SystemTimer.expects(:trap).with('SIGALRM', :the_original_handler)
|
23
|
+
SystemTimer.send :restore_original_ruby_sigalrm_handler
|
24
|
+
end
|
25
|
+
|
26
|
+
test "restore_original_ruby_sigalrm_handler resets original_ruby_sigalrm_handler" do
|
27
|
+
SystemTimer.stubs(:trap)
|
28
|
+
SystemTimer.expects(:reset_original_ruby_sigalrm_handler)
|
29
|
+
SystemTimer.send :restore_original_ruby_sigalrm_handler
|
30
|
+
end
|
31
|
+
|
32
|
+
test "restore_original_ruby_sigalrm_handler reset SIGALRM handler to default when original_ruby_sigalrm_handler is nil" do
|
33
|
+
SystemTimer.stubs(:original_ruby_sigalrm_handler)
|
34
|
+
SystemTimer.expects(:trap).with('SIGALRM', 'DEFAULT')
|
35
|
+
SystemTimer.stubs(:reset_original_ruby_sigalrm_handler)
|
36
|
+
SystemTimer.send :restore_original_ruby_sigalrm_handler
|
37
|
+
end
|
38
|
+
|
39
|
+
test "restore_original_ruby_sigalrm_handler resets original_ruby_sigalrm_handler when trap raises" do
|
40
|
+
SystemTimer.stubs(:trap).returns(:the_original_handler)
|
41
|
+
SystemTimer.send(:install_ruby_sigalrm_handler)
|
42
|
+
SystemTimer.expects(:trap).raises("next time maybe...")
|
43
|
+
SystemTimer.expects(:reset_original_ruby_sigalrm_handler)
|
44
|
+
|
45
|
+
SystemTimer.send(:restore_original_ruby_sigalrm_handler) rescue nil
|
46
|
+
end
|
47
|
+
|
48
|
+
test "timeout_after raises TimeoutError if block takes too long" do
|
49
|
+
assert_raises(Timeout::Error) do
|
50
|
+
SystemTimer.timeout_after(1) {sleep 5}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
test "timeout_after does not raises Timeout Error if block completes in time" do
|
55
|
+
SystemTimer.timeout_after(5) {sleep 1}
|
56
|
+
end
|
57
|
+
|
58
|
+
test "timeout_after returns the value returned by the black" do
|
59
|
+
assert_equal :block_value, SystemTimer.timeout_after(1) {:block_value}
|
60
|
+
end
|
61
|
+
|
62
|
+
test "timeout_after raises TimeoutError in thread that called timeout_after" do
|
63
|
+
raised_thread = nil
|
64
|
+
other_thread = Thread.new do
|
65
|
+
begin
|
66
|
+
SystemTimer.timeout_after(1) {sleep 5}
|
67
|
+
flunk "Should have timed out"
|
68
|
+
rescue Timeout::Error
|
69
|
+
raised_thread = Thread.current
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
other_thread.join
|
74
|
+
assert_equal other_thread, raised_thread
|
75
|
+
end
|
76
|
+
|
77
|
+
test "cancelling a timer that was installed restores previous ruby handler for SIG_ALRM" do
|
78
|
+
begin
|
79
|
+
fake_original_ruby_handler = proc {}
|
80
|
+
initial_ruby_handler = trap "SIGALRM", fake_original_ruby_handler
|
81
|
+
SystemTimer.install_first_timer_and_save_original_configuration 3
|
82
|
+
SystemTimer.restore_original_configuration
|
83
|
+
assert_equal fake_original_ruby_handler, trap("SIGALRM", "IGNORE")
|
84
|
+
ensure # avoid interfering with test infrastructure
|
85
|
+
trap("SIGALRM", initial_ruby_handler) if initial_ruby_handler
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
test "debug_enabled returns true after enabling debug" do
|
90
|
+
begin
|
91
|
+
SystemTimer.disable_debug
|
92
|
+
SystemTimer.enable_debug
|
93
|
+
assert_equal true, SystemTimer.debug_enabled?
|
94
|
+
ensure
|
95
|
+
SystemTimer.disable_debug
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
test "debug_enabled returns false after disable debug" do
|
100
|
+
begin
|
101
|
+
SystemTimer.enable_debug
|
102
|
+
SystemTimer.disable_debug
|
103
|
+
assert_equal false, SystemTimer.debug_enabled?
|
104
|
+
ensure
|
105
|
+
SystemTimer.disable_debug
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
test "timeout offers an API fully compatible with timeout.rb" do
|
110
|
+
assert_raises(Timeout::Error) do
|
111
|
+
SystemTimer.timeout(1) {sleep 5}
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Disable this test as it is failing on Ubuntu. The problem is that
|
116
|
+
# for some reason M.R.I 1.8 is trapping the Ruby signals at the
|
117
|
+
# time the system SIGALRM is delivered, hence we do not timeout as
|
118
|
+
# quickly as we should. Needs further investigation. At least the
|
119
|
+
# SIGALRM ensures that the system will schedule M.R.I. native thread.
|
120
|
+
#
|
121
|
+
#
|
122
|
+
# test "while exact timeouts cannot be guaranted the timeout should not exceed the provided timeout by 2 seconds" do
|
123
|
+
# start = Time.now
|
124
|
+
# begin
|
125
|
+
# SystemTimer.timeout_after(2) do
|
126
|
+
# open "http://www.invalid.domain.comz"
|
127
|
+
# end
|
128
|
+
# raise "should never get there"
|
129
|
+
# rescue SocketError => e
|
130
|
+
# rescue Timeout::Error => e
|
131
|
+
# end
|
132
|
+
# elapsed = Time.now - start
|
133
|
+
# assert elapsed < 4, "Got #{elapsed} s, expected 2, at most 4"
|
134
|
+
# end
|
135
|
+
|
136
|
+
|
137
|
+
test "timeout are enforced on system calls" do
|
138
|
+
assert_timeout_within(3) do
|
139
|
+
SystemTimer.timeout(3) do
|
140
|
+
sleep 30
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
test "timeout work when spawning a different thread" do
|
146
|
+
assert_timeout_within(3) do
|
147
|
+
thread = Thread.new do
|
148
|
+
SystemTimer.timeout(3) do
|
149
|
+
sleep 60
|
150
|
+
end
|
151
|
+
end
|
152
|
+
thread.join
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
test "can set multiple serial timers" do
|
157
|
+
10.times do
|
158
|
+
assert_timeout_within(3) do
|
159
|
+
SystemTimer.timeout(3) do
|
160
|
+
sleep 60
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
test "timeout work when setting concurrent timers, the first one " +
|
167
|
+
"expiring before the second one" do
|
168
|
+
|
169
|
+
first_thread = Thread.new do
|
170
|
+
assert_timeout_within(3) do
|
171
|
+
SystemTimer.timeout(3) do
|
172
|
+
sleep 60
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
second_thread = Thread.new do
|
177
|
+
assert_timeout_within(5) do
|
178
|
+
SystemTimer.timeout(5) do
|
179
|
+
sleep 60
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
first_thread.join
|
184
|
+
second_thread.join
|
185
|
+
end
|
186
|
+
|
187
|
+
test "timeout work when setting concurrent timers, the second one " +
|
188
|
+
"expiring before the first one" do
|
189
|
+
|
190
|
+
first_thread = Thread.new do
|
191
|
+
assert_timeout_within(10) do
|
192
|
+
SystemTimer.timeout(10) do
|
193
|
+
sleep 60
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
second_thread = Thread.new do
|
198
|
+
assert_timeout_within(3) do
|
199
|
+
SystemTimer.timeout(3) do
|
200
|
+
sleep 60
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
first_thread.join
|
205
|
+
second_thread.join
|
206
|
+
end
|
207
|
+
|
208
|
+
test "timeout work when setting concurrent timers with the exact" +
|
209
|
+
"same timeout" do
|
210
|
+
|
211
|
+
first_thread = Thread.new do
|
212
|
+
assert_timeout_within(2) do
|
213
|
+
SystemTimer.timeout(2) do
|
214
|
+
sleep 60
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
second_thread = Thread.new do
|
219
|
+
assert_timeout_within(2) do
|
220
|
+
SystemTimer.timeout(2) do
|
221
|
+
sleep 60
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
first_thread.join
|
226
|
+
second_thread.join
|
227
|
+
end
|
228
|
+
|
229
|
+
test "timeout works with random concurrent timers dynamics" do
|
230
|
+
all_threads = []
|
231
|
+
|
232
|
+
10.times do
|
233
|
+
a_timeout = [1, (rand(10)).to_i].max
|
234
|
+
all_threads << Thread.new do
|
235
|
+
assert_timeout_within(a_timeout, 10) do
|
236
|
+
SystemTimer.timeout(a_timeout) do
|
237
|
+
sleep 180
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
all_threads.each {|t| t.join}
|
244
|
+
end
|
245
|
+
|
246
|
+
def assert_timeout_within(expected_timeout_in_seconds,
|
247
|
+
error_margin = DEFAULT_ERROR_MARGIN,
|
248
|
+
&block)
|
249
|
+
start = Time.now
|
250
|
+
yield
|
251
|
+
flunk "Did not timeout as expected!"
|
252
|
+
rescue Timeout::Error
|
253
|
+
elapsed = Time.now - start
|
254
|
+
assert elapsed >= expected_timeout_in_seconds,
|
255
|
+
"Timed out too early, expected #{expected_timeout_in_seconds}, got #{elapsed} s"
|
256
|
+
assert elapsed < (expected_timeout_in_seconds + error_margin),
|
257
|
+
"Timed out after #{elapsed} seconds, expected #{expected_timeout_in_seconds}"
|
258
|
+
end
|
259
|
+
|
260
|
+
end
|