SystemTimer 1.1.3 → 1.2

Sign up to get free protection for your applications and to get access to all the features.
data/ChangeLog CHANGED
@@ -1,4 +1,18 @@
1
- === 1.1.3/ 2009-29-11
1
+ === 1.2 / 2010-02-25
2
+
3
+ * Changed from using Mutex to Monitor. Mutex causes thread join
4
+ errors when Ruby is compiled with -disable-pthreads
5
+ (Contributed by Dmytro Shteflyuk <http://kpumuk.info/>)
6
+
7
+ * Timeouts can now be specified as a float and be a fraction of a second.
8
+ e.g. `SystemTimer.timeout(0.5)`
9
+ (Based on a contribution by Dmytro Shteflyuk <http://kpumuk.info/>)
10
+
11
+ * Added support for custom timeout exception. Useful to avoid interference
12
+ with other libraries using `Timeout::Error` (e.g. `Net::HTTP`)
13
+ (Contributed by runix <http://github.com/runix>)
14
+
15
+ === 1.1.3/ 2009-11-29
2
16
 
3
17
  * Preparing GemCutter migration
4
18
 
data/README CHANGED
@@ -1,47 +1,110 @@
1
- == Synopsis
1
+ Synopsis
2
+ ========
2
3
 
3
- System Timer, a timer based on underlying SIGALRM system timers, is a
4
+ System Timer, a timer based on underlying `SIGALRM` system timers, is a
4
5
  solution to Ruby processes which hang beyond the time limit when accessing
5
- external resources. This is useful when timeout.rb, which relies on green
6
- threads, does not work consistently.
6
+ external resources. This is useful when `timeout.rb`, which, on M.R.I 1.8,
7
+ relies on green threads, does not work consistently.
7
8
 
8
9
  More background on:
9
10
 
10
- * http://ph7spot.com/articles/system_timer
11
- * http://davidvollbracht.com/2008/6/2/30-days-of-teach-day-1-systemtimer
11
+ * [http://ph7spot.com/musings/system-timer](http://ph7spot.com/musings/system-timer)
12
+ * [http://davidvollbracht.com/2008/6/2/30-days-of-teach-day-1-systemtimer](http://davidvollbracht.com/2008/6/2/30-days-of-teach-day-1-systemtimer)
12
13
 
13
- == Usage
14
+ Usage
15
+ =====
14
16
 
15
- require 'systemtimer'
17
+ require 'systemtimer'
18
+
19
+ SystemTimer.timeout_after(5) do
20
+
21
+ # Something that should be interrupted if it takes too much time...
22
+ # ... even if blocked on a system call!
23
+
24
+ end
16
25
 
17
- SystemTimer.timeout_after(5) do
26
+ Timeouts as Floats
27
+ ------------------
18
28
 
19
- # Something that should be interrupted if it takes too much time...
20
- # ... even if blocked on a system call!
29
+ You can use a floating point number when specifying the timeout in
30
+ seconds but SystemTimer will not allow you to go below 200ms, e.g.
21
31
 
22
- end
32
+ SystemTimer.timeout_after(0.5) do
33
+ # timeout after 500ms
34
+ end
23
35
 
24
- == Requirements
36
+ SystemTimer.timeout_after(0.01) do
37
+ # timeout after (uncompressable) 200ms even if 10ms is requested
38
+ end
39
+
40
+ Note that SystemTimer is going through too many layers to be
41
+ able to reliably guarantee a sub-second timeout on all platforms,
42
+ so your mileage may vary when specifying timeouts under one second.
43
+
44
+ Custom Timeout Exceptions
45
+ -------------------------
46
+
47
+ You can also use a custom timeout exception to be raised on timeouts (to
48
+ avoid interference with other libraries using `Timeout::Error` -- e.g. `Net::HTTP`)
49
+
50
+ require 'systemtimer'
51
+
52
+ begin
53
+
54
+ SystemTimer.timeout_after(5, MyCustomTimeoutException) do
55
+
56
+ # Something that should be interrupted if it takes too much time...
57
+ # ... even if blocked on a system call!
58
+
59
+ end
60
+
61
+ rescue MyCustomTimeoutException => e
62
+ # Recovering strategy
63
+ end
64
+
65
+
66
+ Requirements
67
+ ============
25
68
 
26
69
  SystemTimer only works on UNIX platforms (Mac OS X, Linux, Solaris, BSD, ...).
27
70
  You can install the gem on Microsoft Windows, but you will only get
28
71
  a convenience shell wrapping a simple call to timeout.rb under the cover.
29
72
 
30
- == Install
73
+ Install
74
+ =======
31
75
 
32
76
  sudo gem install systemtimer
33
77
 
34
78
 
35
- == Authors
79
+ Authors
80
+ =======
36
81
 
37
82
  * David Vollbracht <http://davidvollbracht.com>
38
- * Philippe Hanrigou <http:/ph7spot.com>
39
-
40
- == Copyright
83
+ * Philippe Hanrigou <http://ph7spot.com>
84
+
85
+ Contributor
86
+ ===========
87
+
88
+ * Dmytro Shteflyuk <http://kpumuk.info/> :
89
+ - Changed from using Mutex to Monitor. Evidently Mutex causes thread
90
+ join errors when Ruby is compiled with -disable-pthreads
91
+ <http://github.com/kpumuk/system-micro-timer/commit/fe28f4dcf7d4126e53b7c642c5ec35fe8bc1e081>
92
+ - First tentative to support float timeouts
93
+ <http://github.com/kpumuk/system-micro-timer/commit/57fff73849aad7c94f8b9234352b7288d1314d21>
94
+
95
+ * runix <http://github.com/runix> :
96
+ - Added support for custom timeout exception. Useful to avoid interference
97
+ with other libraries using `Timeout::Error` (e.g. `Net::HTTP`)
98
+ <http://github.com/runix/system-timer/commit/d33acb3acc53d5105c68b25c3a2126fa682f12c0>
99
+ <http://github.com/runix/system-timer/commit/d8ca3452e462ea909d8e11a6091e7c30dfa3a1a8>
100
+
101
+ Copyright
102
+ =========
41
103
 
42
- Copyright:: (C) 2008 David Vollbracht & Philippe Hanrigou
104
+ Copyright:: (C) 2008-2010 David Vollbracht & Philippe Hanrigou
43
105
 
44
- == Description
106
+ Description
107
+ ===========
45
108
 
46
109
  While deploying Rails application in production our team discovered
47
110
  that some web service call would not timeout way beyond their defined
@@ -66,11 +129,12 @@ timeout.rb, just a way to wrap sensitive call to system resources.
66
129
  You can find more details on SystemTimer and how to use it
67
130
  at http://ph7spot.com/articles/system_timer
68
131
 
69
- == License
132
+ License
133
+ =======
70
134
 
71
135
  (The Ruby License)
72
136
 
73
- Copyright:: (C) 2008 David Vollbracht & Philippe Hanrigou
137
+ Copyright:: (C) 2008-2010 David Vollbracht & Philippe Hanrigou
74
138
 
75
139
  SystemTimer is copyrighted free software by David Vollbracht and Philippe Hanrigou.
76
140
  You can redistribute it and/or modify it under either the terms of the GPL
@@ -83,33 +147,33 @@ You can redistribute it and/or modify it under either the terms of the GPL
83
147
  2. You may modify your copy of the software in any way, provided that
84
148
  you do at least ONE of the following:
85
149
 
86
- a) place your modifications in the Public Domain or otherwise
87
- make them Freely Available, such as by posting said
88
- modifications to Usenet or an equivalent medium, or by allowing
89
- the author to include your modifications in the software.
150
+ a) place your modifications in the Public Domain or otherwise
151
+ make them Freely Available, such as by posting said
152
+ modifications to Usenet or an equivalent medium, or by allowing
153
+ the author to include your modifications in the software.
90
154
 
91
- b) use the modified software only within your corporation or
92
- organization.
155
+ b) use the modified software only within your corporation or
156
+ organization.
93
157
 
94
- c) rename any non-standard executables so the names do not conflict
95
- with standard executables, which must also be provided.
158
+ c) rename any non-standard executables so the names do not conflict
159
+ with standard executables, which must also be provided.
96
160
 
97
- d) make other distribution arrangements with the author.
161
+ d) make other distribution arrangements with the author.
98
162
 
99
163
  3. You may distribute the software in object code or executable
100
164
  form, provided that you do at least ONE of the following:
101
165
 
102
- a) distribute the executables and library files of the software,
103
- together with instructions (in the manual page or equivalent)
104
- on where to get the original distribution.
166
+ a) distribute the executables and library files of the software,
167
+ together with instructions (in the manual page or equivalent)
168
+ on where to get the original distribution.
105
169
 
106
- b) accompany the distribution with the machine-readable source of
107
- the software.
170
+ b) accompany the distribution with the machine-readable source of
171
+ the software.
108
172
 
109
- c) give non-standard executables non-standard names, with
110
- instructions on where to get the original software distribution.
173
+ c) give non-standard executables non-standard names, with
174
+ instructions on where to get the original software distribution.
111
175
 
112
- d) make other distribution arrangements with the author.
176
+ d) make other distribution arrangements with the author.
113
177
 
114
178
  4. You may modify and include the part of the software into any other
115
179
  software (possibly commercial). But some files in the distribution
@@ -12,6 +12,8 @@
12
12
 
13
13
  #define DISPLAY_ERRNO 1
14
14
  #define DO_NOT_DISPLAY_ERRNO 0
15
+ #define MICRO_SECONDS 1000000.0
16
+ #define MINIMUM_TIMER_INTERVAL_IN_SECONDS 0.2
15
17
 
16
18
  VALUE rb_cSystemTimer;
17
19
  sigset_t original_mask;
@@ -26,7 +28,7 @@ static void restore_original_ruby_sigalrm_handler(VALUE);
26
28
  static void restore_original_sigalrm_mask_when_blocked();
27
29
  static void restore_original_timer_interval();
28
30
  static void set_itimerval_with_minimum_1s_interval(struct itimerval *, VALUE);
29
- static void set_itimerval(struct itimerval *, int);
31
+ static void set_itimerval(struct itimerval *, double);
30
32
  static void restore_sigalrm_mask(sigset_t *previous_mask);
31
33
  static void log_debug(char*, ...);
32
34
  static void log_error(char*, int);
@@ -37,7 +39,7 @@ static VALUE install_first_timer_and_save_original_configuration(VALUE self, VAL
37
39
  struct itimerval timer_interval;
38
40
 
39
41
  if (debug_enabled) {
40
- log_debug("[install_first_timer] %d s\n", NUM2INT(seconds));
42
+ log_debug("[install_first_timer] %.2lfs\n", NUM2DBL(seconds));
41
43
  }
42
44
 
43
45
  /*
@@ -70,7 +72,7 @@ static VALUE install_first_timer_and_save_original_configuration(VALUE self, VAL
70
72
  /*
71
73
  * Save original real time interval timer and aet new real time interval timer.
72
74
  */
73
- set_itimerval(&original_timer_interval, 0);
75
+ set_itimerval(&original_timer_interval, 0.0);
74
76
  set_itimerval_with_minimum_1s_interval(&timer_interval, seconds);
75
77
  if (0 != setitimer(ITIMER_REAL, &timer_interval, &original_timer_interval)) {
76
78
  log_error("[install_first_timer] Could not install our own timer, timeout will not work", DISPLAY_ERRNO);
@@ -78,7 +80,10 @@ static VALUE install_first_timer_and_save_original_configuration(VALUE self, VAL
78
80
  restore_original_sigalrm_mask_when_blocked();
79
81
  return Qnil;
80
82
  }
81
- log_debug("[install_first_timer] Successfully installed timer (%ds)\n", timer_interval.it_value.tv_sec);
83
+ if (debug_enabled) {
84
+ log_debug("[install_first_timer] Successfully installed timer (%ds)\n",
85
+ timer_interval.it_value.tv_sec);
86
+ }
82
87
 
83
88
  /*
84
89
  * Unblock SIG_ALRM
@@ -100,7 +105,7 @@ static VALUE install_next_timer(VALUE self, VALUE seconds)
100
105
  sigset_t previous_sigalarm_mask;
101
106
 
102
107
  if (debug_enabled) {
103
- log_debug("[install_next_timer] %ds\n", NUM2INT(seconds));
108
+ log_debug("[install_next_timer] %.2lfs\n", NUM2DBL(seconds));
104
109
  }
105
110
 
106
111
  /*
@@ -122,7 +127,10 @@ static VALUE install_next_timer(VALUE self, VALUE seconds)
122
127
  restore_sigalrm_mask(&previous_sigalarm_mask);
123
128
  return Qnil;
124
129
  }
125
- log_debug("[install_next_timer] Successfully installed timer (%ds)\n", timer_interval.it_value.tv_sec);
130
+ if (debug_enabled) {
131
+ log_debug("[install_next_timer] Successfully installed timer (%ds + %dus)\n",
132
+ timer_interval.it_value.tv_sec, timer_interval.it_value.tv_usec);
133
+ }
126
134
 
127
135
  /*
128
136
  * Unblock SIG_ALRM
@@ -264,20 +272,28 @@ static void init_sigalarm_mask()
264
272
  static void set_itimerval_with_minimum_1s_interval(struct itimerval *value,
265
273
  VALUE seconds) {
266
274
 
267
- int sanitized_second_interval;
275
+ double sanitized_second_interval;
268
276
 
269
- sanitized_second_interval = NUM2INT(seconds);
270
- if (sanitized_second_interval <= 0 ) {
271
- sanitized_second_interval = 1;
277
+ sanitized_second_interval = NUM2DBL(seconds) + MINIMUM_TIMER_INTERVAL_IN_SECONDS;
278
+ if (sanitized_second_interval < MINIMUM_TIMER_INTERVAL_IN_SECONDS ) {
279
+ sanitized_second_interval = MINIMUM_TIMER_INTERVAL_IN_SECONDS;
272
280
  }
273
281
  set_itimerval(value, sanitized_second_interval);
274
282
  }
275
283
 
276
- static void set_itimerval(struct itimerval *value, int seconds) {
284
+ static void set_itimerval(struct itimerval *value, double seconds) {
285
+ if (debug_enabled) {
286
+ log_debug("[set_itimerval] %.3lfs\n", seconds);
287
+ }
277
288
  value->it_interval.tv_usec = 0;
278
289
  value->it_interval.tv_sec = 0;
279
- value->it_value.tv_usec = 0;
280
- value->it_value.tv_sec = seconds; // (long int)
290
+ value->it_value.tv_sec = (long int) (seconds);
291
+ value->it_value.tv_usec = (long int) ((seconds - value->it_value.tv_sec) \
292
+ * MICRO_SECONDS);
293
+ if (debug_enabled) {
294
+ log_debug("[set_itimerval] Set to %ds + %dus\n", value->it_value.tv_sec,
295
+ value->it_value.tv_usec);
296
+ }
281
297
  return;
282
298
  }
283
299
 
@@ -3,6 +3,7 @@
3
3
  require 'rubygems'
4
4
  require 'timeout'
5
5
  require 'forwardable'
6
+ require 'monitor'
6
7
  require File.dirname(__FILE__) + '/system_timer/thread_timer'
7
8
  require File.dirname(__FILE__) + '/system_timer/concurrent_timer_pool'
8
9
 
@@ -29,9 +30,9 @@ require File.dirname(__FILE__) + '/system_timer/concurrent_timer_pool'
29
30
  #
30
31
  module SystemTimer
31
32
 
32
- Thread.exclusive do # Avoid race conditions for mutex and pool creation
33
+ Thread.exclusive do # Avoid race conditions for monitor and pool creation
33
34
  @timer_pool = ConcurrentTimerPool.new
34
- @mutex = Mutex.new
35
+ @monitor = Monitor.new
35
36
  end
36
37
 
37
38
  class << self
@@ -40,12 +41,12 @@ module SystemTimer
40
41
  # Executes the method's block. If the block execution terminates before
41
42
  # +seconds+ seconds has passed, it returns true. If not, it terminates
42
43
  # the execution and raises a +Timeout::Error+.
43
- def timeout_after(seconds)
44
+ def timeout_after(seconds, exception_class = nil)
44
45
  new_timer = nil # just for scope
45
- @mutex.synchronize do
46
- new_timer = timer_pool.add_timer seconds
46
+ @monitor.synchronize do
47
+ new_timer = timer_pool.add_timer seconds, exception_class
47
48
  timer_interval = timer_pool.next_trigger_interval_in_seconds
48
- debug "==== Install Timer ==== at #{Time.now.to_i}, next interval: #{timer_interval}"
49
+ debug "==== Install Timer ==== at #{Time.now.to_f}, next interval: #{timer_interval}"
49
50
  if timer_pool.first_timer?
50
51
  install_first_timer_and_save_original_configuration timer_interval
51
52
  else
@@ -54,8 +55,8 @@ module SystemTimer
54
55
  end
55
56
  return yield
56
57
  ensure
57
- @mutex.synchronize do
58
- debug "==== Cleanup Timer ==== at #{Time.now.to_i}, #{new_timer} "
58
+ @monitor.synchronize do
59
+ debug "==== Cleanup Timer ==== at #{Time.now.to_f}, #{new_timer} "
59
60
  timer_pool.cancel new_timer
60
61
  timer_pool.log_registered_timers if debug_enabled?
61
62
  next_interval = timer_pool.next_trigger_interval_in_seconds
@@ -75,7 +76,7 @@ module SystemTimer
75
76
 
76
77
  def install_ruby_sigalrm_handler #:nodoc:
77
78
  @original_ruby_sigalrm_handler = trap('SIGALRM') do
78
- @mutex.synchronize do
79
+ @monitor.synchronize do
79
80
  # Triggers timers one at a time to ensure more deterministic results
80
81
  timer_pool.trigger_next_expired_timer
81
82
  end
@@ -104,4 +105,4 @@ module SystemTimer
104
105
 
105
106
  end
106
107
 
107
- require 'system_timer_native'
108
+ require 'system_timer_native'
@@ -8,14 +8,14 @@ module SystemTimer
8
8
  @timers ||= []
9
9
  end
10
10
 
11
- def register_timer(trigger_time, thread)
12
- new_timer = ThreadTimer.new(trigger_time, thread)
11
+ def register_timer(trigger_time, thread, exception_class=nil)
12
+ new_timer = ThreadTimer.new(trigger_time, thread, exception_class)
13
13
  registered_timers << new_timer
14
14
  new_timer
15
15
  end
16
16
 
17
- def add_timer(interval_in_seconds)
18
- new_timer = register_timer(Time.now.to_i + interval_in_seconds, Thread.current)
17
+ def add_timer(interval_in_seconds, exception_class=nil)
18
+ new_timer = register_timer(Time.now.to_f + interval_in_seconds, Thread.current, exception_class)
19
19
  log_registered_timers if SystemTimer.debug_enabled?
20
20
  new_timer
21
21
  end
@@ -39,11 +39,15 @@ module SystemTimer
39
39
 
40
40
  def next_trigger_interval_in_seconds
41
41
  timer = next_timer
42
- [0, (timer.trigger_time - Time.now.to_i)].max unless timer.nil?
42
+ [0, (timer.trigger_time - Time.now.to_f)].max unless timer.nil?
43
43
  end
44
44
 
45
45
  def next_expired_timer(now_in_seconds_since_epoch)
46
46
  candidate_timer = next_timer
47
+ if SystemTimer.debug_enabled?
48
+ puts "Candidate timer at #{now_in_seconds_since_epoch} : " +
49
+ candidate_timer.inspect
50
+ end
47
51
  return nil if candidate_timer.nil? ||
48
52
  candidate_timer.trigger_time > now_in_seconds_since_epoch
49
53
  candidate_timer
@@ -51,15 +55,17 @@ module SystemTimer
51
55
 
52
56
  def trigger_next_expired_timer_at(now_in_seconds_since_epoch)
53
57
  timer = next_expired_timer(now_in_seconds_since_epoch)
58
+ puts "Next expired timer : #{timer.inspect}" if SystemTimer.debug_enabled?
54
59
  return if timer.nil?
55
60
 
56
61
  cancel timer
57
62
  log_timeout_received(timer) if SystemTimer.debug_enabled?
58
- timer.thread.raise Timeout::Error.new("time's up!")
63
+ timer.thread.raise timer.exception_class.new("time's up!")
59
64
  end
60
65
 
61
66
  def trigger_next_expired_timer
62
- trigger_next_expired_timer_at Time.now.to_i
67
+ puts "Trigger next expired timer" if SystemTimer.debug_enabled?
68
+ trigger_next_expired_timer_at Time.now.to_f
63
69
  end
64
70
 
65
71
  def log_timeout_received(thread_timer) #:nodoc:
@@ -74,10 +80,10 @@ module SystemTimer
74
80
 
75
81
  def log_registered_timers #:nodoc:
76
82
  puts <<-EOS
77
- Registered Timers: #{registered_timers.collect do |t| t.to_s end}
83
+ Registered Timers: #{registered_timers.map {|t| t.to_s}.join("\n ")}
78
84
  EOS
79
85
  end
80
86
 
81
87
  end
82
88
 
83
- end
89
+ end
@@ -6,15 +6,16 @@ module SystemTimer
6
6
  # from a Ruby signal handler and Ruby signals are always delivered to
7
7
  # main thread.
8
8
  class ThreadTimer
9
- attr_reader :trigger_time, :thread
9
+ attr_reader :trigger_time, :thread, :exception_class
10
10
 
11
- def initialize(trigger_time, thread)
11
+ def initialize(trigger_time, thread, exception_class = nil)
12
12
  @trigger_time = trigger_time
13
13
  @thread = thread
14
+ @exception_class = exception_class || Timeout::Error
14
15
  end
15
16
 
16
17
  def to_s
17
- "<ThreadTimer :time => #{trigger_time}, :thread => #{thread}>"
18
+ "<ThreadTimer :time => #{trigger_time}, :thread => #{thread}, :exception_class => #{exception_class}>"
18
19
  end
19
20
 
20
21
  end
@@ -25,7 +25,7 @@ unit_tests do
25
25
  now = Time.now
26
26
  Time.stubs(:now).returns(now)
27
27
 
28
- pool.expects(:register_timer).with(now.to_i + 15, :the_current_thread)
28
+ pool.expects(:register_timer).with(now.to_f + 15, :the_current_thread, nil)
29
29
  pool.add_timer 15
30
30
  end
31
31
 
@@ -123,7 +123,7 @@ unit_tests do
123
123
  pool = SystemTimer::ConcurrentTimerPool.new
124
124
  now = Time.now
125
125
  Time.stubs(:now).returns(now)
126
- next_timer = SystemTimer::ThreadTimer.new((now.to_i + 7), stub_everything)
126
+ next_timer = SystemTimer::ThreadTimer.new((now.to_f + 7), stub_everything)
127
127
  pool.expects(:next_timer).returns(next_timer)
128
128
  assert_equal 7, pool.next_trigger_interval_in_seconds
129
129
  end
@@ -132,7 +132,7 @@ unit_tests do
132
132
  pool = SystemTimer::ConcurrentTimerPool.new
133
133
  now = Time.now
134
134
  Time.stubs(:now).returns(now)
135
- next_timer = SystemTimer::ThreadTimer.new now.to_i, stub_everything
135
+ next_timer = SystemTimer::ThreadTimer.new now.to_f, stub_everything
136
136
  pool.expects(:next_timer).returns(next_timer)
137
137
  assert_equal 0, pool.next_trigger_interval_in_seconds
138
138
  end
@@ -141,7 +141,7 @@ unit_tests do
141
141
  pool = SystemTimer::ConcurrentTimerPool.new
142
142
  now = Time.now
143
143
  Time.stubs(:now).returns(now)
144
- next_timer = SystemTimer::ThreadTimer.new((now.to_i - 3), stub_everything)
144
+ next_timer = SystemTimer::ThreadTimer.new((now.to_f - 3), stub_everything)
145
145
  pool.expects(:next_timer).returns(next_timer)
146
146
  assert_equal 0, pool.next_trigger_interval_in_seconds
147
147
  end
@@ -217,12 +217,21 @@ unit_tests do
217
217
 
218
218
  test "trigger_next_expired_timer_at logs timeout a registered timer has expired" +
219
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
220
 
224
- pool.expects(:log_timeout_received).with(the_timer)
225
- pool.trigger_next_expired_timer_at 24
221
+ original_stdout = $stdout
222
+ begin
223
+ stdout = StringIO.new
224
+ $stdout = stdout
225
+
226
+ pool = SystemTimer::ConcurrentTimerPool.new
227
+ the_timer = pool.register_timer 24, stub_everything
228
+ SystemTimer.stubs(:debug_enabled?).returns(true)
229
+
230
+ pool.expects(:log_timeout_received).with(the_timer)
231
+ pool.trigger_next_expired_timer_at 24
232
+ ensure
233
+ $stdout = original_stdout
234
+ end
226
235
  end
227
236
 
228
237
  test "trigger_next_expired_timer_at does not logs timeoout when SystemTimer " +
@@ -238,13 +247,21 @@ unit_tests do
238
247
 
239
248
  test "trigger_next_expired_timer_at does not logs timeout no registered timer " +
240
249
  "has expired and SystemTimer debug mode is enabled " do
250
+
251
+ original_stdout = $stdout
252
+ begin
253
+ stdout = StringIO.new
254
+ $stdout = stdout
241
255
 
242
- pool = SystemTimer::ConcurrentTimerPool.new
243
- the_timer = pool.register_timer 24, stub_everything
244
- SystemTimer.stubs(:debug_enabled?).returns(true)
256
+ pool = SystemTimer::ConcurrentTimerPool.new
257
+ the_timer = pool.register_timer 24, stub_everything
258
+ SystemTimer.stubs(:debug_enabled?).returns(true)
245
259
 
246
- pool.expects(:log_timeout_received).never
247
- pool.trigger_next_expired_timer_at 23
260
+ pool.expects(:log_timeout_received).never
261
+ pool.trigger_next_expired_timer_at 23
262
+ ensure
263
+ $stdout = original_stdout
264
+ end
248
265
  end
249
266
 
250
267
  test "trigger_next_expired_timer is a shorcut method calling " +
@@ -254,7 +271,7 @@ unit_tests do
254
271
  pool = SystemTimer::ConcurrentTimerPool.new
255
272
  Time.stubs(:now).returns(now)
256
273
 
257
- pool.expects(:trigger_next_expired_timer_at).with(now.to_i)
274
+ pool.expects(:trigger_next_expired_timer_at).with(now.to_f)
258
275
  pool.trigger_next_expired_timer
259
276
  end
260
277
 
@@ -13,7 +13,7 @@ unit_tests do
13
13
  end
14
14
 
15
15
  test "to_s retruns a human friendly description of the timer" do
16
- assert_match /<ThreadTimer :time => 24, :thread => #<Thread(.*)>>/,
16
+ assert_match /<ThreadTimer :time => 24, :thread => #<Thread(.*)>, :exception_class => Timeout::Error>/,
17
17
  SystemTimer::ThreadTimer.new(24, Thread.current).to_s
18
18
  end
19
19
 
@@ -50,7 +50,21 @@ functional_tests do
50
50
  SystemTimer.timeout_after(1) {sleep 5}
51
51
  end
52
52
  end
53
-
53
+
54
+ test "timeout_after timeout can be a fraction of a second" do
55
+ assert_raises(Timeout::Error) do
56
+ SystemTimer.timeout_after(0.2) {sleep 3}
57
+ end
58
+ end
59
+
60
+
61
+ test "timeout_after raises a custom timeout when block takes too long and a custom exception class is provided" do
62
+ ACustomException = Class.new(Exception)
63
+ assert_raises(ACustomException) do
64
+ SystemTimer.timeout_after(1, ACustomException) {sleep 5}
65
+ end
66
+ end
67
+
54
68
  test "timeout_after does not raises Timeout Error if block completes in time" do
55
69
  SystemTimer.timeout_after(5) {sleep 1}
56
70
  end
@@ -154,18 +168,28 @@ functional_tests do
154
168
  end
155
169
 
156
170
  test "can set multiple serial timers" do
157
- 10.times do
158
- assert_timeout_within(3) do
159
- SystemTimer.timeout(3) do
171
+ 10.times do |i|
172
+ print(i) & STDOUT.flush
173
+ assert_timeout_within(1) do
174
+ SystemTimer.timeout(1) do
175
+ sleep 60
176
+ end
177
+ end
178
+ end
179
+ end
180
+
181
+ test "can set multiple serial timers with fractional timeout" do
182
+ 10.times do |i|
183
+ print(i) & STDOUT.flush
184
+ assert_timeout_within(0.5) do
185
+ SystemTimer.timeout(0.5) do
160
186
  sleep 60
161
187
  end
162
188
  end
163
189
  end
164
190
  end
165
191
 
166
- test "timeout work when setting concurrent timers, the first one " +
167
- "expiring before the second one" do
168
-
192
+ test "timeout work when setting concurrent timers, the first one expiring before the second one" do
169
193
  first_thread = Thread.new do
170
194
  assert_timeout_within(3) do
171
195
  SystemTimer.timeout(3) do
@@ -184,9 +208,8 @@ functional_tests do
184
208
  second_thread.join
185
209
  end
186
210
 
187
- test "timeout work when setting concurrent timers, the second one " +
188
- "expiring before the first one" do
189
-
211
+ test "timeout work when setting concurrent timers, the second one expiring before the first one" do
212
+
190
213
  first_thread = Thread.new do
191
214
  assert_timeout_within(10) do
192
215
  SystemTimer.timeout(10) do
@@ -205,8 +228,7 @@ functional_tests do
205
228
  second_thread.join
206
229
  end
207
230
 
208
- test "timeout work when setting concurrent timers with the exact" +
209
- "same timeout" do
231
+ test "timeout work when setting concurrent timers with the exact same timeout" do
210
232
 
211
233
  first_thread = Thread.new do
212
234
  assert_timeout_within(2) do
@@ -230,7 +252,7 @@ functional_tests do
230
252
  all_threads = []
231
253
 
232
254
  10.times do
233
- a_timeout = [1, (rand(10)).to_i].max
255
+ a_timeout = [1, (rand(10)).to_f].max
234
256
  all_threads << Thread.new do
235
257
  assert_timeout_within(a_timeout, 10) do
236
258
  SystemTimer.timeout(a_timeout) do
@@ -257,4 +279,4 @@ functional_tests do
257
279
  "Timed out after #{elapsed} seconds, expected #{expected_timeout_in_seconds}"
258
280
  end
259
281
 
260
- end
282
+ end
@@ -9,18 +9,30 @@ unit_tests do
9
9
  SystemTimer.stubs(:install_next_timer)
10
10
  SystemTimer.stubs(:restore_original_configuration)
11
11
 
12
- pool.expects(:add_timer).with(5).returns(stub_everything)
12
+ pool.expects(:add_timer).with(5, nil).returns(stub_everything)
13
13
  SystemTimer.timeout_after(5) {}
14
14
  end
15
15
 
16
+ test "timeout_after registers a new timer with a custom timeout exception in the timer pool" do
17
+ MyCustomException = Class.new(Exception)
18
+ pool = stub_everything
19
+ Thread.stubs(:current).returns(:the_current_thread)
20
+ SystemTimer.stubs(:timer_pool).returns(pool)
21
+ SystemTimer.stubs(:install_next_timer)
22
+ SystemTimer.stubs(:restore_original_configuration)
23
+
24
+ pool.expects(:add_timer).with(5, MyCustomException).returns(stub_everything)
25
+ SystemTimer.timeout_after(5, MyCustomException) {}
26
+ end
27
+
16
28
  test "timeout_after installs a system timer saving the previous " +
17
29
  "configuration when there is only one timer" do
18
30
 
19
31
  now = Time.now
20
32
  Time.stubs(:now).returns(now)
21
33
  SystemTimer.stubs(:restore_original_configuration)
22
-
23
- SystemTimer.expects(:install_first_timer_and_save_original_configuration).with(24)
34
+ SystemTimer.expects(:install_first_timer_and_save_original_configuration) \
35
+ .with {|value| value.between?(23.99, 24.01) }
24
36
  SystemTimer.timeout_after(24) {}
25
37
  end
26
38
 
@@ -29,11 +41,12 @@ unit_tests do
29
41
 
30
42
  now = Time.now
31
43
  Time.stubs(:now).returns(now)
32
- SystemTimer.timer_pool.register_timer now.to_i + 100, :a_thread
44
+ SystemTimer.timer_pool.register_timer now.to_f + 100, :a_thread
33
45
  SystemTimer.stubs(:restore_original_configuration)
34
46
  SystemTimer.stubs(:install_next_timer)
35
47
 
36
- SystemTimer.expects(:install_next_timer).with(24)
48
+ SystemTimer.expects(:install_next_timer) \
49
+ .with {|value| value.between?(23.99, 24.01) }
37
50
  SystemTimer.timeout_after(24) {}
38
51
  end
39
52
 
@@ -42,11 +55,12 @@ unit_tests do
42
55
 
43
56
  now = Time.now
44
57
  Time.stubs(:now).returns(now)
45
- SystemTimer.timer_pool.register_timer now.to_i + 24, :a_thread
58
+ SystemTimer.timer_pool.register_timer now.to_f + 24, :a_thread
46
59
  SystemTimer.stubs(:restore_original_configuration)
47
60
  SystemTimer.stubs(:install_next_timer)
48
61
 
49
- SystemTimer.expects(:install_next_timer).with(24)
62
+ SystemTimer.expects(:install_next_timer) \
63
+ .with {|value| value.between?(23.99, 24.01) }
50
64
  SystemTimer.timeout_after(100) {}
51
65
  end
52
66
 
@@ -93,4 +107,4 @@ unit_tests do
93
107
  end
94
108
  end
95
109
 
96
- end
110
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: SystemTimer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.3
4
+ version: "1.2"
5
5
  platform: ruby
6
6
  authors:
7
7
  - Philippe Hanrigou
@@ -10,7 +10,7 @@ autorequire: system_timer
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2009-10-29 00:00:00 -07:00
13
+ date: 2010-02-25 00:00:00 -08:00
14
14
  default_executable:
15
15
  dependencies: []
16
16