codders-SystemTimer 1.2.3.1

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,205 @@
1
+ Synopsis
2
+ ========
3
+
4
+ System Timer, a timer based on underlying `SIGALRM` system timers, is a
5
+ solution to Ruby processes which hang beyond the time limit when accessing
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.
8
+
9
+ More background on:
10
+
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)
13
+
14
+ Usage
15
+ =====
16
+
17
+ require 'system_timer'
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
25
+
26
+ Timeouts as Floats
27
+ ------------------
28
+
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.
31
+
32
+ SystemTimer.timeout_after(0.5) do
33
+ # timeout after 500ms
34
+ end
35
+
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 'system_timer'
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
+ ============
68
+
69
+ SystemTimer only works on UNIX platforms (Mac OS X, Linux, Solaris, BSD, ...).
70
+ You can install the gem on Microsoft Windows, but you will only get
71
+ a convenience shell wrapping a simple call to timeout.rb under the cover.
72
+
73
+ Install
74
+ =======
75
+
76
+ sudo gem install SystemTimer
77
+
78
+ Authors
79
+ =======
80
+
81
+ * David Vollbracht <http://davidvollbracht.com>
82
+ * Philippe Hanrigou <http://ph7spot.com>
83
+
84
+ Contributors
85
+ ============
86
+
87
+ * Dmytro Shteflyuk <http://kpumuk.info/> :
88
+ - Changed from using Mutex to Monitor. Evidently Mutex causes thread
89
+ join errors when Ruby is compiled with -disable-pthreads
90
+ <https://github.com/kpumuk/system-micro-timer/commit/fe28f4dcf7d4126e53b7c642c5ec35fe8bc1e081>
91
+ - First tentative to support float timeouts
92
+ <https://github.com/kpumuk/system-micro-timer/commit/57fff73849aad7c94f8b9234352b7288d1314d21>
93
+
94
+ * runix <http://github.com/runix> :
95
+ - Added support for custom timeout exception. Useful to avoid interference
96
+ with other libraries using `Timeout::Error` (e.g. `Net::HTTP`)
97
+ <https://github.com/runix/system-timer/commit/d33acb3acc53d5105c68b25c3a2126fa682f12c0>
98
+ <https://github.com/runix/system-timer/commit/d8ca3452e462ea909d8e11a6091e7c30dfa3a1a8>
99
+
100
+ * Jesse Storimer <http://jstorimer.com>
101
+ - Explicit required_ruby_version = '~> 1.8.7' in gem spec.
102
+ <https://github.com/jstorimer/system-timer/commit/ec08b4d2173ffd635065a1680c8f8b4fbf6691fd>
103
+
104
+ * James Tucker <http://blog.ra66i.org>
105
+ - Fix for RubyGems 1.6, which will not require "thread"
106
+ <https://github.com/raggi/system-timer/commit/f6dd9535e3f1141f319fe7919b8347dd0e40560c>
107
+ <https://github.com/raggi/system-timer/commit/b13ff12bc7392b1aa2fe7911e305a3e8f215efd2>
108
+
109
+ Copyright
110
+ =========
111
+
112
+ Copyright:: (C) 2008-2010 David Vollbracht & Philippe Hanrigou
113
+
114
+ Description
115
+ ===========
116
+
117
+ While deploying Rails application in production our team discovered
118
+ that some web service call would not timeout way beyond their defined
119
+ limit, progressively freezing our Mongrel cluster until we restarted
120
+ the servers. A closer analysis revealed that the native thread in charge of
121
+ of the web service call was never scheduled, "stucked" on the service
122
+ call. As it turn out the timeout library bundled with Ruby 1.8 (MRI)
123
+ relies on green-threads to perform its magic... so the magic had no chance
124
+ to happen in this scenario.
125
+
126
+ Based on an original idea by Kurtis Seebaldt <http://kseebaldt.blogspot.com>,
127
+ David Vollbracht and Philippe Hanrigou pair programmed an alternative
128
+ implementation based on system timers (the +SIGALRM+ POSIX signal):
129
+ This design guarantees proper timeout behavior even when crossing-boundaries and accessing
130
+ system/external resources. Special care has been taken to interfere as little as
131
+ possible with other processes that might also rely on +SIGALRM+,
132
+ in particular MySQL.
133
+
134
+ This implementation is not intended to be drop-in replacement to
135
+ timeout.rb, just a way to wrap sensitive call to system resources.
136
+
137
+ You can find more details on SystemTimer and how to use it
138
+ at http://ph7spot.com/articles/system_timer
139
+
140
+ License
141
+ =======
142
+
143
+ (The Ruby License)
144
+
145
+ Copyright:: (C) 2008-2010 David Vollbracht & Philippe Hanrigou
146
+
147
+ SystemTimer is copyrighted free software by David Vollbracht and Philippe Hanrigou.
148
+ You can redistribute it and/or modify it under either the terms of the GPL
149
+ (see COPYING file), or the conditions below:
150
+
151
+ 1. You may make and give away verbatim copies of the source form of the
152
+ software without restriction, provided that you duplicate all of the
153
+ original copyright notices and associated disclaimers.
154
+
155
+ 2. You may modify your copy of the software in any way, provided that
156
+ you do at least ONE of the following:
157
+
158
+ a) place your modifications in the Public Domain or otherwise
159
+ make them Freely Available, such as by posting said
160
+ modifications to Usenet or an equivalent medium, or by allowing
161
+ the author to include your modifications in the software.
162
+
163
+ b) use the modified software only within your corporation or
164
+ organization.
165
+
166
+ c) rename any non-standard executables so the names do not conflict
167
+ with standard executables, which must also be provided.
168
+
169
+ d) make other distribution arrangements with the author.
170
+
171
+ 3. You may distribute the software in object code or executable
172
+ form, provided that you do at least ONE of the following:
173
+
174
+ a) distribute the executables and library files of the software,
175
+ together with instructions (in the manual page or equivalent)
176
+ on where to get the original distribution.
177
+
178
+ b) accompany the distribution with the machine-readable source of
179
+ the software.
180
+
181
+ c) give non-standard executables non-standard names, with
182
+ instructions on where to get the original software distribution.
183
+
184
+ d) make other distribution arrangements with the author.
185
+
186
+ 4. You may modify and include the part of the software into any other
187
+ software (possibly commercial). But some files in the distribution
188
+ are not written by the author, so that they are not under this terms.
189
+
190
+ They are gc.c(partly), utils.c(partly), regex.[ch], st.[ch] and some
191
+ files under the ./missing directory. See each file for the copying
192
+ condition.
193
+
194
+ 5. The scripts and library files supplied as input to or produced as
195
+ output from the software do not automatically fall under the
196
+ copyright of the software, but belong to whomever generated them,
197
+ and may be sold commercially, and may be aggregated with this
198
+ software.
199
+
200
+ 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
201
+ IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
202
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
203
+ PURPOSE.
204
+
205
+
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require 'mkmf'
3
+ create_makefile("system_timer_native")
@@ -0,0 +1,326 @@
1
+ /*
2
+ * SystemTimer native implementation relying on ITIMER_REAL
3
+ *
4
+ * Copyright 2008 David Vollbracht & Philippe Hanrigou
5
+ */
6
+
7
+ #include "ruby.h"
8
+ #include "rubysig.h"
9
+ #include <signal.h>
10
+ #include <errno.h>
11
+ #include <stdarg.h>
12
+
13
+ #define DISPLAY_ERRNO 1
14
+ #define DO_NOT_DISPLAY_ERRNO 0
15
+ #define MICRO_SECONDS 1000000.0
16
+ #define MINIMUM_TIMER_INTERVAL_IN_SECONDS 0.2
17
+ #define RT_SIGNAL_ID 10
18
+
19
+ VALUE rb_cSystemTimer;
20
+
21
+ // Ignore most of this for Rubinius
22
+ #ifndef RUBINIUS
23
+
24
+ sigset_t original_mask;
25
+ sigset_t sigalarm_mask;
26
+ struct sigaction original_signal_handler;
27
+ struct itimerval original_timer_interval;
28
+ static int debug_enabled = 0;
29
+
30
+ static void clear_pending_sigalrm_for_ruby_threads();
31
+ static void install_ruby_sigalrm_handler(VALUE);
32
+ static void restore_original_ruby_sigalrm_handler(VALUE);
33
+ static void restore_original_sigalrm_mask_when_blocked();
34
+ static void restore_original_timer_interval();
35
+ static void set_itimerval_with_minimum_1s_interval(struct itimerval *, VALUE);
36
+ static void set_itimerval(struct itimerval *, double);
37
+ static void restore_sigalrm_mask(sigset_t *previous_mask);
38
+ static void log_debug(char*, ...);
39
+ static void log_error(char*, int);
40
+
41
+
42
+ static VALUE install_first_timer_and_save_original_configuration(VALUE self, VALUE seconds)
43
+ {
44
+ struct itimerval timer_interval;
45
+
46
+ if (debug_enabled) {
47
+ log_debug("[install_first_timer] %.2lfs\n", NUM2DBL(seconds));
48
+ }
49
+
50
+ /*
51
+ * Block SIG_ALRM for safe processing of SIG_ALRM configuration and save mask.
52
+ */
53
+ if (0 != sigprocmask(SIG_BLOCK, &sigalarm_mask, &original_mask)) {
54
+ log_error("[install_first_timer] Could not block SIG_ALRM\n", DISPLAY_ERRNO);
55
+ return Qnil;
56
+ }
57
+ clear_pending_sigalrm_for_ruby_threads();
58
+ log_debug("[install_first_timer] Successfully blocked SIG_ALRM at O.S. level\n");
59
+
60
+ /*
61
+ * Save previous signal handler.
62
+ */
63
+ log_debug("[install_first_timer] Saving original system handler\n");
64
+ original_signal_handler.sa_handler = NULL;
65
+ if (0 != sigaction(SIGALRM, NULL, &original_signal_handler)) {
66
+ log_error("[install_first_timer] Could not save existing handler for SIG_ALRM\n", DISPLAY_ERRNO);
67
+ restore_original_sigalrm_mask_when_blocked();
68
+ return Qnil;
69
+ }
70
+ log_debug("[install_first_timer] Successfully saved existing SIG_ALRM handler\n");
71
+
72
+ /*
73
+ * Install Ruby Level SIG_ALRM handler
74
+ */
75
+ install_ruby_sigalrm_handler(self);
76
+
77
+ /*
78
+ * Save original real time interval timer and aet new real time interval timer.
79
+ */
80
+ set_itimerval(&original_timer_interval, 0.0);
81
+ set_itimerval_with_minimum_1s_interval(&timer_interval, seconds);
82
+ if (0 != setitimer(ITIMER_REAL, &timer_interval, &original_timer_interval)) {
83
+ log_error("[install_first_timer] Could not install our own timer, timeout will not work", DISPLAY_ERRNO);
84
+ restore_original_ruby_sigalrm_handler(self);
85
+ restore_original_sigalrm_mask_when_blocked();
86
+ return Qnil;
87
+ }
88
+ if (debug_enabled) {
89
+ log_debug("[install_first_timer] Successfully installed timer (%ds)\n",
90
+ timer_interval.it_value.tv_sec);
91
+ }
92
+
93
+ /*
94
+ * Unblock SIG_ALRM
95
+ */
96
+ if (0 != sigprocmask(SIG_UNBLOCK, &sigalarm_mask, NULL)) {
97
+ log_error("[install_first_timer] Could not unblock SIG_ALRM, timeout will not work", DISPLAY_ERRNO);
98
+ restore_original_timer_interval();
99
+ restore_original_ruby_sigalrm_handler(self);
100
+ restore_original_sigalrm_mask_when_blocked();
101
+ }
102
+ log_debug("[install_first_timer] Successfully unblocked SIG_ALRM.\n");
103
+
104
+ return Qnil;
105
+ }
106
+
107
+ static VALUE install_next_timer(VALUE self, VALUE seconds)
108
+ {
109
+ struct itimerval timer_interval;
110
+ sigset_t previous_sigalarm_mask;
111
+
112
+ if (debug_enabled) {
113
+ log_debug("[install_next_timer] %.2lfs\n", NUM2DBL(seconds));
114
+ }
115
+
116
+ /*
117
+ * Block SIG_ALRM for safe processing of SIG_ALRM configuration and save mask.
118
+ */
119
+ if (0 != sigprocmask(SIG_BLOCK, &sigalarm_mask, &previous_sigalarm_mask)) {
120
+ log_error("[install_next_timer] Could not block SIG_ALRM\n", DISPLAY_ERRNO);
121
+ return Qnil;
122
+ }
123
+ clear_pending_sigalrm_for_ruby_threads();
124
+ log_debug("[install_next_timer] Successfully blocked SIG_ALRM at O.S. level\n");
125
+
126
+ /*
127
+ * Set new real time interval timer.
128
+ */
129
+ set_itimerval_with_minimum_1s_interval(&timer_interval, seconds);
130
+ if (0 != setitimer(ITIMER_REAL, &timer_interval, NULL)) {
131
+ log_error("[install_next_timer] Could not install our own timer, timeout will not work", DISPLAY_ERRNO);
132
+ restore_sigalrm_mask(&previous_sigalarm_mask);
133
+ return Qnil;
134
+ }
135
+ if (debug_enabled) {
136
+ log_debug("[install_next_timer] Successfully installed timer (%ds + %dus)\n",
137
+ timer_interval.it_value.tv_sec, timer_interval.it_value.tv_usec);
138
+ }
139
+
140
+ /*
141
+ * Unblock SIG_ALRM
142
+ */
143
+ if (0 != sigprocmask(SIG_UNBLOCK, &sigalarm_mask, NULL)) {
144
+ log_error("[install_next_timer] Could not unblock SIG_ALRM, timeout will not work", DISPLAY_ERRNO);
145
+ restore_sigalrm_mask(&previous_sigalarm_mask);
146
+ }
147
+ log_debug("[install_next_timer] Successfully unblocked SIG_ALRM.\n");
148
+
149
+ return Qnil;
150
+ }
151
+
152
+ static VALUE restore_original_configuration(VALUE self)
153
+ {
154
+ /*
155
+ * Block SIG_ALRM for safe processing of SIG_ALRM configuration.
156
+ */
157
+ if (0 != sigprocmask(SIG_BLOCK, &sigalarm_mask, NULL)) {
158
+ log_error("restore_original_configuration: Could not block SIG_ALRM", errno);
159
+ }
160
+ clear_pending_sigalrm_for_ruby_threads();
161
+ log_debug("[restore_original_configuration] Blocked SIG_ALRM\n");
162
+
163
+ /*
164
+ * Install Ruby Level SIG_ALRM handler
165
+ */
166
+ restore_original_ruby_sigalrm_handler(self);
167
+
168
+ if (original_signal_handler.sa_handler == NULL) {
169
+ log_error("[restore_original_configuration] Previous SIG_ALRM handler not initialized!", DO_NOT_DISPLAY_ERRNO);
170
+ } else if (0 == sigaction(SIGALRM, &original_signal_handler, NULL)) {
171
+ log_debug("[restore_original_configuration] Successfully restored previous handler for SIG_ALRM\n");
172
+ } else {
173
+ log_error("[restore_original_configuration] Could not restore previous handler for SIG_ALRM", DISPLAY_ERRNO);
174
+ }
175
+ original_signal_handler.sa_handler = NULL;
176
+
177
+ restore_original_timer_interval();
178
+ restore_original_sigalrm_mask_when_blocked();
179
+ }
180
+
181
+ /*
182
+ * Restore original timer the way it was originally set. **WARNING** Breaks original timer semantics
183
+ *
184
+ * Not bothering to calculate how much time is left or if the timer already expired
185
+ * based on when the original timer was set and how much time is passed, just resetting
186
+ * the original timer as is for the sake of simplicity.
187
+ *
188
+ */
189
+ static void restore_original_timer_interval() {
190
+ if (0 != setitimer(ITIMER_REAL, &original_timer_interval, NULL)) {
191
+ log_error("[restore_original_configuration] Could not restore original timer", DISPLAY_ERRNO);
192
+ }
193
+ log_debug("[restore_original_configuration] Successfully restored original timer\n");
194
+ }
195
+
196
+ static void restore_sigalrm_mask(sigset_t *previous_mask)
197
+ {
198
+ if (!sigismember(previous_mask, SIGALRM)) {
199
+ sigprocmask(SIG_UNBLOCK, &sigalarm_mask, NULL);
200
+ log_debug("[restore_sigalrm_mask] Unblocked SIG_ALRM\n");
201
+ } else {
202
+ log_debug("[restore_sigalrm_mask] No Need to unblock SIG_ALRM\n");
203
+ }
204
+ }
205
+
206
+ static void restore_original_sigalrm_mask_when_blocked()
207
+ {
208
+ restore_sigalrm_mask(&original_mask);
209
+ }
210
+
211
+ static void install_ruby_sigalrm_handler(VALUE self) {
212
+ rb_thread_critical = 1;
213
+ rb_funcall(self, rb_intern("install_ruby_sigalrm_handler"), 0);
214
+ rb_thread_critical = 0;
215
+ }
216
+
217
+ static void restore_original_ruby_sigalrm_handler(VALUE self) {
218
+ rb_thread_critical = 1;
219
+ rb_funcall(self, rb_intern("restore_original_ruby_sigalrm_handler"), 0);
220
+ rb_thread_critical = 0;
221
+ }
222
+
223
+
224
+ static VALUE debug_enabled_p(VALUE self) {
225
+ return debug_enabled ? Qtrue : Qfalse;
226
+ }
227
+
228
+ static VALUE enable_debug(VALUE self) {
229
+ debug_enabled = 1;
230
+ return Qnil;
231
+ }
232
+
233
+ static VALUE disable_debug(VALUE self) {
234
+ debug_enabled = 0;
235
+ return Qnil;
236
+ }
237
+
238
+ static void log_debug(char* message, ...)
239
+ {
240
+ va_list argp;
241
+
242
+ if (0 != debug_enabled) {
243
+ va_start(argp, message);
244
+ vfprintf(stdout, message, argp);
245
+ va_end(argp);
246
+ }
247
+ return;
248
+ }
249
+
250
+ static void log_error(char* message, int display_errno)
251
+ {
252
+ fprintf(stderr, "%s: %s\n", message, display_errno ? strerror(errno) : "");
253
+ return;
254
+ }
255
+
256
+ /*
257
+ * The intent is to clear SIG_ALRM signals at the Ruby level (green threads),
258
+ * eventually triggering existing SIG_ALRM handler as a courtesy.
259
+ *
260
+ * As we cannot access trap_pending_list outside of signal.c our best fallback option
261
+ * is to trigger all pending signals at the Ruby level (potentially triggering
262
+ * green thread scheduling).
263
+ */
264
+ static void clear_pending_sigalrm_for_ruby_threads()
265
+ {
266
+ CHECK_INTS;
267
+ log_debug("[native] Successfully triggered all pending signals at Green Thread level\n");
268
+ }
269
+
270
+ static void init_sigalarm_mask()
271
+ {
272
+ sigemptyset(&sigalarm_mask);
273
+ sigaddset(&sigalarm_mask, SIGRTMIN + RT_SIGNAL_ID);
274
+ return;
275
+ }
276
+
277
+ static void set_itimerval_with_minimum_1s_interval(struct itimerval *value,
278
+ VALUE seconds) {
279
+
280
+ double sanitized_second_interval;
281
+
282
+ sanitized_second_interval = NUM2DBL(seconds) + MINIMUM_TIMER_INTERVAL_IN_SECONDS;
283
+ if (sanitized_second_interval < MINIMUM_TIMER_INTERVAL_IN_SECONDS ) {
284
+ sanitized_second_interval = MINIMUM_TIMER_INTERVAL_IN_SECONDS;
285
+ }
286
+ set_itimerval(value, sanitized_second_interval);
287
+ }
288
+
289
+ static void set_itimerval(struct itimerval *value, double seconds) {
290
+ if (debug_enabled) {
291
+ log_debug("[set_itimerval] %.3lfs\n", seconds);
292
+ }
293
+ value->it_interval.tv_usec = 0;
294
+ value->it_interval.tv_sec = 0;
295
+ value->it_value.tv_sec = (long int) (seconds);
296
+ value->it_value.tv_usec = (long int) ((seconds - value->it_value.tv_sec) \
297
+ * MICRO_SECONDS);
298
+ if (debug_enabled) {
299
+ log_debug("[set_itimerval] Set to %ds + %dus\n", value->it_value.tv_sec,
300
+ value->it_value.tv_usec);
301
+ }
302
+ return;
303
+ }
304
+
305
+
306
+ void Init_system_timer_native()
307
+ {
308
+ init_sigalarm_mask();
309
+ rb_cSystemTimer = rb_define_module("SystemTimer");
310
+ rb_define_singleton_method(rb_cSystemTimer, "install_first_timer_and_save_original_configuration", install_first_timer_and_save_original_configuration, 1);
311
+ rb_define_singleton_method(rb_cSystemTimer, "install_next_timer", install_next_timer, 1);
312
+ rb_define_singleton_method(rb_cSystemTimer, "restore_original_configuration", restore_original_configuration, 0);
313
+ rb_define_singleton_method(rb_cSystemTimer, "debug_enabled?", debug_enabled_p, 0);
314
+ rb_define_singleton_method(rb_cSystemTimer, "enable_debug", enable_debug, 0);
315
+ rb_define_singleton_method(rb_cSystemTimer, "disable_debug", disable_debug, 0);
316
+ }
317
+
318
+ #else
319
+
320
+ // Exists just to make things happy
321
+ void Init_system_timer_native()
322
+ {
323
+ rb_cSystemTimer = rb_define_module("SystemTimer");
324
+ }
325
+
326
+ #endif