SystemTimer 1.0 → 1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -1,3 +1,5 @@
1
+ # Copyright 2008 David Vollbracht & Philippe Hanrigou
2
+
1
3
  require 'rubygems'
2
4
  require 'timeout'
3
5
 
@@ -9,6 +11,9 @@ module SystemTimer
9
11
  yield
10
12
  end
11
13
  end
14
+
15
+ # Backward compatibility with timeout.rb
16
+ alias timeout timeout_after
12
17
 
13
18
  end
14
19
 
@@ -1 +1,3 @@
1
- Dir["#{File.dirname __FILE__}/*_test.rb"].each { |test_case| require test_case }
1
+ Dir["#{File.dirname __FILE__}/**/*_test.rb"].each do |test_case|
2
+ require test_case
3
+ end
@@ -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