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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4a13e67993632a9392a673e855283c2861ebdd85
4
- data.tar.gz: 2560ded553a334cff47f21ef060529813c802745
3
+ metadata.gz: 76d98ba4c245814afabec6e1f5fc5a23d3eee686
4
+ data.tar.gz: 4ce0b834aa9ee9bf52cc087208850cda2a3bee85
5
5
  SHA512:
6
- metadata.gz: 7cdb2260b5d15f9f83214fbc84ec0ef69728e6772ae59b7167d3f22a289c2c39dc014af56c8e80d7a7a60238e8fb179faa33859a0b966add79a3b5a549991256
7
- data.tar.gz: 3fa034f942193b921fdd6e2a3ade11806c2e2858987cff03a72f198c23953db6fd62e9f048b130d92f8f15367a1110d934dab0e0d124e3e75650395827f6fcf7
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
- ## Installation
10
+ If `do_something` doesn't return in 40 seconds, ExtremeTimeout forcibly
11
+ terminates the interpreter with exitcode 42.
11
12
 
12
- Add this line to your application's Gemfile:
13
+ ## Why do I need this?
13
14
 
14
- gem 'extreme_timeout'
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
- And then execute:
18
+ require 'timeout'
19
+ Timeout::timeout(40) do
20
+ do_something
21
+ end
17
22
 
18
- $ bundle
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
- Or install it yourself as:
29
+ ## Solution
21
30
 
22
- $ gem install extreme_timeout
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
- TODO: Write usage instructions here
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. Fork it
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)
@@ -1,2 +1,3 @@
1
1
  require 'mkmf'
2
+ have_func('backtrace', 'execinfo.h')
2
3
  create_makefile('extreme_timeout')
@@ -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
- exit(arg->exitcode);
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
  }
@@ -1,7 +1,7 @@
1
1
  # coding: utf-8
2
2
  Gem::Specification.new do |spec|
3
3
  spec.name = "extreme_timeout"
4
- spec.version = "0.2.1"
4
+ spec.version = "0.3.0"
5
5
  spec.authors = ["Masaya SUZUKI"]
6
6
  spec.email = ["draftcode@gmail.com"]
7
7
  spec.description = "Timeout from the outside of the GVL"
@@ -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
- pending "This test will terminate the whole rspec process"
37
- ExtremeTimeout::timeout(1) { sleep }
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
- pending "This test will terminate the whole rspec process"
42
- ExtremeTimeout::timeout(1, 5) { sleep }
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
@@ -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.2.1
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-17 00:00:00.000000000 Z
11
+ date: 2013-12-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler