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