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 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