ghazel-SystemTimer 1.2.1.1

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