extreme_timeout 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +23 -16
- data/ext/extreme_timeout/extconf.rb +1 -0
- data/ext/extreme_timeout/extreme_timeout.c +57 -2
- data/extreme_timeout.gemspec +1 -1
- data/spec/extreme_timeout_spec.rb +37 -5
- data/spec/spec_helper.rb +3 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 76d98ba4c245814afabec6e1f5fc5a23d3eee686
|
4
|
+
data.tar.gz: 4ce0b834aa9ee9bf52cc087208850cda2a3bee85
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7545df380d0d7c3f41b367e4f0019e8f3b224ddae253c6b7158029f61a8e4c336184873038bfdac71d4b15266ca4b7daa10c7ce3c39dbf084b078a9ee9714976
|
7
|
+
data.tar.gz: 31f5f53757e9b8b5129f41ca60f222d1db8b437be343113172bd1b64e137c878947e7cc73924068fa49cd3a5e8c11c87081422a5bf14b29b1dfbd4cd685c6827
|
data/README.md
CHANGED
@@ -3,32 +3,39 @@
|
|
3
3
|
Do it in 40 seconds, or die.
|
4
4
|
|
5
5
|
require 'extreme_timeout'
|
6
|
-
ExtremeTimeout::timeout(40) do
|
6
|
+
ExtremeTimeout::timeout(40, 42) do
|
7
7
|
do_something
|
8
8
|
end
|
9
9
|
|
10
|
-
|
10
|
+
If `do_something` doesn't return in 40 seconds, ExtremeTimeout forcibly
|
11
|
+
terminates the interpreter with exitcode 42.
|
11
12
|
|
12
|
-
|
13
|
+
## Why do I need this?
|
13
14
|
|
14
|
-
|
15
|
+
Let us assume that we want to set a timeout for some work. You might write a
|
16
|
+
code like this:
|
15
17
|
|
16
|
-
|
18
|
+
require 'timeout'
|
19
|
+
Timeout::timeout(40) do
|
20
|
+
do_something
|
21
|
+
end
|
17
22
|
|
18
|
-
|
23
|
+
It is okay in the normal case. But sometimes you have to get along with a
|
24
|
+
C-extension that holds the GVL for a long time. In this case, the code above
|
25
|
+
doesn't work as intended. The `timeout` library uses a ruby level thread to
|
26
|
+
accomplish its task, and there is no chance to get a control to that thread
|
27
|
+
under the situation that some C-extension is working with holding the GVL.
|
19
28
|
|
20
|
-
|
29
|
+
## Solution
|
21
30
|
|
22
|
-
|
31
|
+
Run a native thread that is not controlled by the interpreter. Since it is not
|
32
|
+
controlled by the interpreter, it can work freely without considering the GVL.
|
33
|
+
`ExtremeTimeout` creates a new native thread and sleeps for some seconds and
|
34
|
+
terminates the process.
|
23
35
|
|
24
36
|
## Usage
|
25
37
|
|
26
|
-
|
27
|
-
|
28
|
-
## Contributing
|
38
|
+
You can use this almost same as `timeout` library. Instead of the exception
|
39
|
+
class, you should specify the exitcode.
|
29
40
|
|
30
|
-
1
|
31
|
-
2. Create your feature branch (`git checkout -b my-new-feature`)
|
32
|
-
3. Commit your changes (`git commit -am 'Add some feature'`)
|
33
|
-
4. Push to the branch (`git push origin my-new-feature`)
|
34
|
-
5. Create new Pull Request
|
41
|
+
ExtremeTimeout::timeout(timeouse_sec, exitcode=1, &block)
|
@@ -3,19 +3,73 @@
|
|
3
3
|
#include <stdio.h>
|
4
4
|
#include <stdlib.h>
|
5
5
|
#include <unistd.h>
|
6
|
+
#include <signal.h>
|
7
|
+
#if defined(HAVE_BACKTRACE) && !defined(__APPLE__)
|
8
|
+
# include <execinfo.h>
|
9
|
+
#endif
|
10
|
+
|
11
|
+
static pthread_mutex_t exitcode_mutex = PTHREAD_MUTEX_INITIALIZER;
|
12
|
+
static int exitcode;
|
6
13
|
|
7
14
|
struct wait_args {
|
8
15
|
unsigned int timeout_sec;
|
9
16
|
int exitcode;
|
17
|
+
pthread_t running_thread;
|
10
18
|
};
|
11
19
|
|
20
|
+
static void
|
21
|
+
stacktrace_dumper(int signum)
|
22
|
+
{
|
23
|
+
#define MAX_TRACES 1024
|
24
|
+
static void *trace[MAX_TRACES];
|
25
|
+
int n;
|
26
|
+
VALUE backtrace_arr;
|
27
|
+
|
28
|
+
backtrace_arr = rb_make_backtrace();
|
29
|
+
fprintf(stderr,
|
30
|
+
"-- Ruby level backtrace --------------------------------------\n");
|
31
|
+
rb_io_puts(1, &backtrace_arr, rb_stderr);
|
32
|
+
fprintf(stderr, "\n");
|
33
|
+
|
34
|
+
fprintf(stderr,
|
35
|
+
"-- C level backtrace -----------------------------------------\n");
|
36
|
+
#if defined(HAVE_BACKTRACE) && !defined(__APPLE__)
|
37
|
+
n = backtrace(trace, MAX_TRACES);
|
38
|
+
backtrace_symbols_fd(trace, n, STDERR_FILENO);
|
39
|
+
#elif defined(HAVE_BACKTRACE) && defined(__APPLE__)
|
40
|
+
fprintf(stderr,
|
41
|
+
"C level backtrace is unavailable due to the bug in the OSX's environment.\nSee r39301 and r39808 of CRuby for the details.\n");
|
42
|
+
#else
|
43
|
+
fprintf(stderr,
|
44
|
+
"C level backtrace is unavailable because backtrace(3) is unavailable.\n")
|
45
|
+
#endif
|
46
|
+
exit(exitcode);
|
47
|
+
}
|
48
|
+
|
49
|
+
static void
|
50
|
+
set_stacktrace_dumper(void)
|
51
|
+
{
|
52
|
+
struct sigaction sa;
|
53
|
+
sigfillset(&sa.sa_mask);
|
54
|
+
sa.sa_handler = stacktrace_dumper;
|
55
|
+
sigaction(SIGCONT, &sa, NULL);
|
56
|
+
}
|
57
|
+
|
12
58
|
void *
|
13
59
|
sleep_thread_main(void *_arg)
|
14
60
|
{
|
15
61
|
struct wait_args *arg = _arg;
|
16
62
|
sleep(arg->timeout_sec);
|
17
|
-
fprintf(stderr, "Process exits(ExtremeTimeout::timeout)");
|
18
|
-
|
63
|
+
fprintf(stderr, "Process exits(ExtremeTimeout::timeout)\n");
|
64
|
+
fflush(stderr);
|
65
|
+
|
66
|
+
pthread_mutex_lock(&exitcode_mutex);
|
67
|
+
exitcode = arg->exitcode;
|
68
|
+
set_stacktrace_dumper();
|
69
|
+
if (pthread_kill(arg->running_thread, SIGCONT) == 0) {
|
70
|
+
pthread_join(arg->running_thread, NULL);
|
71
|
+
}
|
72
|
+
return NULL;
|
19
73
|
}
|
20
74
|
|
21
75
|
VALUE
|
@@ -49,6 +103,7 @@ timeout(int argc, VALUE *argv, VALUE self)
|
|
49
103
|
|
50
104
|
arg.timeout_sec = timeout_sec;
|
51
105
|
arg.exitcode = exitcode;
|
106
|
+
arg.running_thread = pthread_self();
|
52
107
|
if (pthread_create(&thread, NULL, sleep_thread_main, &arg) != 0) {
|
53
108
|
rb_raise(rb_eRuntimeError, "pthread_create was failed");
|
54
109
|
}
|
data/extreme_timeout.gemspec
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
require 'extreme_timeout'
|
3
2
|
|
4
3
|
describe ExtremeTimeout do
|
5
4
|
describe '.timeout' do
|
@@ -32,14 +31,47 @@ describe ExtremeTimeout do
|
|
32
31
|
expect { ExtremeTimeout::timeout(3) }.to raise_error(ArgumentError)
|
33
32
|
end
|
34
33
|
|
34
|
+
def redirectio_fork(&block)
|
35
|
+
stdout_r, stdout_w = IO.pipe
|
36
|
+
stderr_r, stderr_w = IO.pipe
|
37
|
+
pid = fork do
|
38
|
+
$stdout.reopen(stdout_w)
|
39
|
+
$stderr.reopen(stderr_w)
|
40
|
+
block.call
|
41
|
+
end
|
42
|
+
stdout_w.close
|
43
|
+
stderr_w.close
|
44
|
+
return pid, stdout_r, stderr_r
|
45
|
+
end
|
46
|
+
|
35
47
|
it 'exits the process when timeout' do
|
36
|
-
|
37
|
-
|
48
|
+
pid, = redirectio_fork do
|
49
|
+
ExtremeTimeout::timeout(1) { sleep }
|
50
|
+
end
|
51
|
+
Timeout.timeout(3) do
|
52
|
+
pid, status = Process.waitpid2(pid)
|
53
|
+
expect(status.exitstatus).to eq(1)
|
54
|
+
end
|
38
55
|
end
|
39
56
|
|
40
57
|
it 'exits the process with the exit code specified when timeout' do
|
41
|
-
|
42
|
-
|
58
|
+
pid, = redirectio_fork do
|
59
|
+
ExtremeTimeout::timeout(1, 5) { sleep }
|
60
|
+
end
|
61
|
+
Timeout.timeout(3) do
|
62
|
+
pid, status = Process.waitpid2(pid)
|
63
|
+
expect(status.exitstatus).to eq(5)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'outputs timeout message to stderr' do
|
68
|
+
pid, stdout, stderr = redirectio_fork do
|
69
|
+
ExtremeTimeout::timeout(1) { sleep }
|
70
|
+
end
|
71
|
+
Timeout.timeout(3) do
|
72
|
+
Process.waitpid(pid)
|
73
|
+
expect(stderr.read).to start_with("Process exits(ExtremeTimeout::timeout)\n")
|
74
|
+
end
|
43
75
|
end
|
44
76
|
end
|
45
77
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -4,6 +4,9 @@
|
|
4
4
|
# loaded once.
|
5
5
|
#
|
6
6
|
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
require 'extreme_timeout'
|
8
|
+
require 'timeout'
|
9
|
+
|
7
10
|
ext = File.expand_path('../../ext', __FILE__)
|
8
11
|
$LOAD_PATH.unshift(ext) unless $LOAD_PATH.include?(ext)
|
9
12
|
RSpec.configure do |config|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: extreme_timeout
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Masaya SUZUKI
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-10
|
11
|
+
date: 2013-12-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|