extreme_timeout 0.2.1 → 0.3.0
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.
- 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
|