timeout-interrupt 0.1.1 → 0.2.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.
data/README.md CHANGED
@@ -7,9 +7,62 @@ It uses POSIX's alarm and traps ALRM-signals.
7
7
 
8
8
  Known limitations bacause of alarm and ALRM are, that you can not use alarm or trap ALRM.
9
9
 
10
+ Scopes
11
+ ======
12
+
13
+ If you need scopes with inner and outer time outs, you should know:
14
+
15
+ The first timed out Timeout will be raised:
16
+
17
+ include TimeoutInterrupt
18
+ timeout(1) { # Will be raised
19
+ timeout(10) { sleep 2 } # Will not be raised
20
+ }
21
+
22
+ If you want to know, which was raised, you need custom exceptions:
23
+
24
+ class CustomErrorWillBeRaised <Exception
25
+ end
26
+ class CustomErrorNotRaise <Exception
27
+ end
28
+ include TimeoutInterrupt
29
+ timeout( 1, CustomErrorWillBeRaised) { # Will be raised again
30
+ timeout( 10, CustomErrorNotRaise) { sleep 2 } # Will not be raised
31
+ }
32
+
33
+ Problems
34
+ ========
35
+
36
+ Memory-Leaks or no clean up
37
+ ---------------------------
38
+
10
39
  Do not forget, syscall can have allocated memory.
11
40
  If you interrupt a call, which can not free his allocations, you will have a memory leak.
12
- So, use it only, if your process did not live any longer or if you call something, which never allocate mem
41
+ If it opens a file, reads it and closes it and while it reads, a time out occurs, the file will not be closed.
42
+
43
+ So, use it only, if your process did not live any longer or if you call something, which never allocate mem or opens a file.
44
+
45
+ Every time, a process dies, all his memory will be freed and every file will be closed, so let your process die and you should be safe.
46
+
47
+ Exception-handling
48
+ ------------------
49
+
50
+ Timeouts can break your exception-handling! You should not handling exception while you wait for a timeout:
51
+
52
+ include TimeoutInterrupt
53
+ timeout(1) {
54
+ begin
55
+ transaction_begin
56
+ do_something
57
+ ensure
58
+ clean_up
59
+ transaction_end
60
+ end
61
+ }
62
+
63
+ Same happens, if clean\_up will raise an exception.
64
+
65
+ And same problem you have with ruby's `Timeout.timeout`.
13
66
 
14
67
  Copyleft
15
68
  =========
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.1
1
+ 0.2.0
@@ -7,43 +7,67 @@ module FFI
7
7
  end
8
8
  end
9
9
 
10
- module TimeoutInterrupt
11
- def self.timeouts
12
- @timeouts ||= {}
10
+ module TimeoutInterruptSingleton
11
+ class <<self
12
+ def timeouts thread = nil
13
+ @timeouts ||= Hash.new {|h,k| h[k] = [] }
14
+ thread = Thread.current if thread.kind_of? Thread
15
+ thread ? @timeouts[thread] : @timeouts
16
+ end
17
+
18
+ def alarm_trap sig
19
+ key, (at, bt, exception) = self.timeouts.min_by {|key,(at,bt,ex)| at }
20
+ return if Time.now < at
21
+ raise exception, 'execution expired', bt
22
+ end
23
+
24
+ def setup
25
+ if timeouts.empty?
26
+ Signal.trap( 'ALRM') {}
27
+ FFI::LibC.alarm 0
28
+ else
29
+ key, (at, bt) = timeouts.min_by {|key,(at,bt)| at }
30
+ secs = (at - Time.now)
31
+ alarm_trap 14 if 0 > secs
32
+ Signal.trap 'ALRM', &method( :alarm_trap)
33
+ FFI::LibC.alarm secs.to_i+1
34
+ end
35
+ end
36
+
37
+ def timeout seconds = nil, exception = nil
38
+ return setup if seconds.nil?
39
+ seconds = seconds.to_i
40
+ exception ||= TimeoutInterrupt::Error
41
+ raise exception, "Timeout must be longer than '0' seconds." unless 0 < seconds
42
+ unless block_given?
43
+ return lambda {|&e|
44
+ raise exception, "Expect a lambda." unless e
45
+ timeout seconds, exception, &e
46
+ }
47
+ end
48
+ at = Time.now + seconds
49
+ key, bt = Random.rand( 2**64-1), Kernel.caller
50
+ begin
51
+ self.timeouts[key] = [at, bt, exception]
52
+ setup
53
+ yield
54
+ ensure
55
+ self.timeouts.delete key
56
+ setup
57
+ end
58
+ end
13
59
  end
60
+ end
14
61
 
15
- def self.alarm_trap sig
16
- key, (at, bt) = TimeoutInterrupt.timeouts.min_by {|key,(at,bt)| at }
17
- return if Time.now < at
18
- raise Timeout::Error, 'execution expired', bt
62
+ module TimeoutInterrupt
63
+ class Error < Timeout::Error
19
64
  end
20
65
 
21
- def self.setup_timeout
22
- if TimeoutInterrupt.timeouts.empty?
23
- Signal.trap( 'ALRM') {}
24
- FFI::LibC.alarm 0
25
- else
26
- key, (at, bt) = TimeoutInterrupt.timeouts.min_by {|key,(at,bt)| at }
27
- secs = (at - Time.now).to_i+1
28
- TimeoutInterrupt.alarm_trap if 1 > secs
29
- Signal.trap 'ALRM', &TimeoutInterrupt.method( :alarm_trap)
30
- FFI::LibC.alarm secs
31
- end
66
+ def self.timeout seconds = nil, exception = nil, &e
67
+ TimeoutInterruptSingleton.timeout seconds, exception, &e
32
68
  end
33
69
 
34
- def self.timeout seconds
35
- seconds = seconds.to_i
36
- raise Timeout::Error, "Timeout must be longer than '0' seconds." unless 0 < seconds
37
- return lambda {|&e| self.timeout seconds, &e } unless block_given?
38
- at = Time.now + seconds
39
- key, bt = Random.rand( 2**64-1), Kernel.caller
40
- begin
41
- TimeoutInterrupt.timeouts[key] = [at, bt]
42
- TimeoutInterrupt.setup_timeout
43
- yield
44
- ensure
45
- TimeoutInterrupt.timeouts.delete key
46
- TimeoutInterrupt.setup_timeout
47
- end
70
+ def timeout seconds = nil, exception = nil, &e
71
+ TimeoutInterruptSingleton.timeout seconds, exception, &e
48
72
  end
49
73
  end
@@ -11,70 +11,145 @@ class TestRubyTimeoutInterrupt < Test::Unit::TestCase
11
11
  end
12
12
 
13
13
  def assert_no_defined_timeout_yet
14
- assert TimeoutInterrupt.timeouts.empty?, "For testing, no timeout should be defined, yet!"
14
+ assert TimeoutInterruptSingleton.timeouts.empty?, "For testing, no timeout should be defined, yet!"
15
15
  end
16
16
 
17
- should "not interrupt a long blocking call with the old Timeout" do
18
- time = Benchmark.realtime do
19
- begin
20
- TimeoutInterrupt.timeout(5) do
21
- Timeout.timeout(1) do
22
- blocking
23
- assert false, "Should be unreachable!"
17
+ def print_timeouts pre
18
+ puts "#{pre}: < #{TimeoutInterruptSingleton.timeouts.map {|k,(a,_b,_e)| "#{k.inspect}: #{a.strftime '%H:%M:%S'} (#{a-Time.now})" }.join ', '} >"
19
+ end
20
+
21
+ # For testing raising scoped Timeout.
22
+ class TimeoutError < Exception
23
+ end
24
+ # For testing raising scoped TimeoutInterrupt.
25
+ class TimeoutInterruptError < Exception
26
+ end
27
+
28
+ context "Long really blocking calls" do
29
+ should "not be interrupted by the old Timeout" do
30
+ time = Benchmark.realtime do
31
+ assert_nothing_raised TimeoutError, "Unexpected time out. Your Ruby implementation can time out with old Timeout? You need not TimeoutInterrupt. But it is ok. You can ignore this Error. :)" do
32
+ assert_raise TimeoutInterruptError, "Ohoh. TimeoutInterrupt should be raised." do
33
+ TimeoutInterrupt.timeout 5, TimeoutInterruptError do
34
+ Timeout.timeout 1, TimeoutError do
35
+ blocking
36
+ assert false, "Should be unreachable!"
37
+ end
38
+ end
24
39
  end
25
40
  end
26
- rescue Timeout::Error
27
- :ok
28
41
  end
42
+ assert 3 < time, "Did timeout!"
29
43
  end
30
- assert 3 < time, "Did timeout!"
31
- end
32
44
 
33
- should "interrupt a long blocking call with the new TimeoutInterrupt" do
34
- time = Benchmark.realtime do
35
- begin
36
- TimeoutInterrupt.timeout(1) do
37
- blocking
38
- assert false, "Should be unreachable!"
45
+ should "be interrupted by the new TimeoutInterrupt" do
46
+ time = Benchmark.realtime do
47
+ assert_raise TimeoutInterrupt::Error, "It should be timed out, why it did not raise TimeoutInterrupt::Error?" do
48
+ TimeoutInterrupt.timeout 1 do
49
+ blocking
50
+ assert false, "Should be unreachable!"
51
+ end
39
52
  end
40
- rescue Timeout::Error
41
- :ok
42
53
  end
54
+ assert 3 > time, "Did not interrupt."
43
55
  end
44
- assert 3 > time, "Did not interrupt."
45
56
  end
46
57
 
47
- should "interrupt scoped timeout, but not outer timeout" do
58
+ should "interrupt scoped timeout, but not time out the outer timeout" do
48
59
  assert_no_defined_timeout_yet
49
- begin
50
- TimeoutInterrupt.timeout(10) do
51
- TimeoutInterrupt.timeout(1) do
52
- Kernel.sleep 2
60
+ assert_raise TimeoutInterruptError, "It should be timed out, why it did not raise TimeoutInterruptError?" do
61
+ assert_nothing_raised Timeout::Error, "Oh, outer timeout was timed out. Your machine must be slow, or there is a bug" do
62
+ TimeoutInterrupt.timeout 10 do
63
+ TimeoutInterrupt.timeout 1, TimeoutInterruptError do
64
+ Kernel.sleep 2
65
+ end
66
+ assert false, "Should be unreachable!"
53
67
  end
54
- assert false, "Should be unreachable!"
55
68
  end
56
- rescue Timeout::Error
57
- :ok
58
69
  end
59
- assert TimeoutInterrupt.timeouts.empty?, "There are timeouts defined, yet!"
70
+ assert TimeoutInterruptSingleton.timeouts.empty?, "There are timeouts defined, yet!"
60
71
  end
61
72
 
62
73
  should "clear timeouts, if not timed out, too." do
63
74
  assert_no_defined_timeout_yet
64
75
  TimeoutInterrupt.timeout(10) {}
65
- assert TimeoutInterrupt.timeouts.empty?, "There are timeouts defined, yet!"
76
+ assert TimeoutInterruptSingleton.timeouts.empty?, "There are timeouts defined, yet!"
66
77
  end
67
78
 
68
- should "return a Proc if now block given, but do not create a timeout." do
69
- assert_no_defined_timeout_yet
70
- assert TimeoutInterrupt.timeout(10).kind_of?( Proc), "Did not return a Proc."
79
+ class CustomException <Exception
71
80
  end
72
81
 
73
- should "run a returned Proc with given timeout." do
74
- assert_no_defined_timeout_yet
75
- to = TimeoutInterrupt.timeout(10)
76
- called = false
77
- to.call { called = true }
78
- assert called, "Did not called."
82
+ should "raise custom exception." do
83
+ assert_raise CustomException, "Custom exceptions do not work." do
84
+ TimeoutInterrupt.timeout 1, CustomException do
85
+ sleep 2
86
+ end
87
+ end
88
+ end
89
+
90
+ context "A prepared timeout (Proc)" do
91
+ should "be returned by calling timeout without a block" do
92
+ assert_no_defined_timeout_yet
93
+ assert TimeoutInterrupt.timeout(10).kind_of?( Proc), "Did not return a Proc."
94
+ end
95
+
96
+ should "run with once given timeout" do
97
+ assert_no_defined_timeout_yet
98
+ to = TimeoutInterrupt.timeout 10
99
+ called = false
100
+ to.call { called = true }
101
+ assert called, "Did not called."
102
+ end
103
+
104
+ should "raise custom exception" do
105
+ assert_raise CustomException, "Custom exceptions do not work." do
106
+ prepared = TimeoutInterrupt.timeout 1, CustomException
107
+ prepared.call { sleep 2 }
108
+ end
109
+ end
110
+
111
+ should "not be scopeable, without manualy setup after rescue and 2 time outs at once" do
112
+ prepared = TimeoutInterrupt.timeout 1
113
+ assert_no_defined_timeout_yet
114
+ called = false
115
+ prepared.call do
116
+ assert_raise TimeoutInterrupt::Error, 'It should time out after one second, but it did not.' do
117
+ prepared.call { 2; sleep 2 }
118
+ end
119
+ called = true
120
+ end
121
+ assert called, "It's true, it should be called, also if not expected."
122
+ end
123
+
124
+ should "be scopeable, with manualy setup after rescue, also if 2 time outs at once." do
125
+ prepared = TimeoutInterrupt.timeout 1
126
+ assert_no_defined_timeout_yet
127
+ prepared.call do
128
+ assert_raise TimeoutInterrupt::Error, 'It should time out after one second, but it did not.' do
129
+ prepared.call { sleep 2 }
130
+ end
131
+ assert_raise TimeoutInterrupt::Error, 'Manualy called timeout setup did not raise.' do
132
+ TimeoutInterrupt.timeout
133
+ end
134
+ assert true, "Should never be reached."
135
+ end
136
+ end
137
+ end
138
+
139
+ class IncludeModuleTest
140
+ include TimeoutInterrupt
141
+ def please_timeout after
142
+ timeout after do
143
+ sleep after+10
144
+ end
145
+ end
146
+ end
147
+
148
+ context "Included module" do
149
+ should "provide timeout too" do
150
+ assert_raise TimeoutInterrupt::Error, "Included timeout can not be used?" do
151
+ IncludeModuleTest.new.please_timeout 2
152
+ end
153
+ end
79
154
  end
80
155
  end
@@ -0,0 +1,68 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "timeout-interrupt"
8
+ s.version = "0.2.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Denis Knauf"]
12
+ s.date = "2013-03-07"
13
+ s.description = "Timeout-lib, which interrupts everything, also systemcalls. It uses libc-alarm."
14
+ s.email = "Denis.Knauf@gmail.com"
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.md"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ "Gemfile",
22
+ "Gemfile.lock",
23
+ "LICENSE.txt",
24
+ "README.md",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "lib/timeout_interrupt.rb",
28
+ "test/helper.rb",
29
+ "test/test_ruby-timeout-interrupt.rb",
30
+ "timeout-interrupt.gemspec"
31
+ ]
32
+ s.homepage = "http://github.com/DenisKnauf/ruby-timeout-interrupt"
33
+ s.licenses = ["AGPLv3"]
34
+ s.require_paths = ["lib"]
35
+ s.rubygems_version = "1.8.11"
36
+ s.summary = "\"Interrupts systemcalls too.\""
37
+
38
+ if s.respond_to? :specification_version then
39
+ s.specification_version = 3
40
+
41
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
42
+ s.add_runtime_dependency(%q<ffi-libc>, [">= 0"])
43
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
44
+ s.add_development_dependency(%q<yard>, [">= 0"])
45
+ s.add_development_dependency(%q<rdoc>, [">= 0"])
46
+ s.add_development_dependency(%q<bundler>, [">= 0"])
47
+ s.add_development_dependency(%q<jeweler>, [">= 0"])
48
+ s.add_development_dependency(%q<simplecov>, [">= 0"])
49
+ else
50
+ s.add_dependency(%q<ffi-libc>, [">= 0"])
51
+ s.add_dependency(%q<shoulda>, [">= 0"])
52
+ s.add_dependency(%q<yard>, [">= 0"])
53
+ s.add_dependency(%q<rdoc>, [">= 0"])
54
+ s.add_dependency(%q<bundler>, [">= 0"])
55
+ s.add_dependency(%q<jeweler>, [">= 0"])
56
+ s.add_dependency(%q<simplecov>, [">= 0"])
57
+ end
58
+ else
59
+ s.add_dependency(%q<ffi-libc>, [">= 0"])
60
+ s.add_dependency(%q<shoulda>, [">= 0"])
61
+ s.add_dependency(%q<yard>, [">= 0"])
62
+ s.add_dependency(%q<rdoc>, [">= 0"])
63
+ s.add_dependency(%q<bundler>, [">= 0"])
64
+ s.add_dependency(%q<jeweler>, [">= 0"])
65
+ s.add_dependency(%q<simplecov>, [">= 0"])
66
+ end
67
+ end
68
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: timeout-interrupt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-03-06 00:00:00.000000000 Z
12
+ date: 2013-03-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: ffi-libc
16
- requirement: &72349180 !ruby/object:Gem::Requirement
16
+ requirement: &79897400 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *72349180
24
+ version_requirements: *79897400
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: shoulda
27
- requirement: &72348250 !ruby/object:Gem::Requirement
27
+ requirement: &79918180 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *72348250
35
+ version_requirements: *79918180
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: yard
38
- requirement: &72367400 !ruby/object:Gem::Requirement
38
+ requirement: &79914220 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *72367400
46
+ version_requirements: *79914220
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: rdoc
49
- requirement: &72364150 !ruby/object:Gem::Requirement
49
+ requirement: &79910520 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *72364150
57
+ version_requirements: *79910520
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: bundler
60
- requirement: &72360610 !ruby/object:Gem::Requirement
60
+ requirement: &79907200 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: '0'
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *72360610
68
+ version_requirements: *79907200
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: jeweler
71
- requirement: &72357200 !ruby/object:Gem::Requirement
71
+ requirement: &79905270 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ! '>='
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: '0'
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *72357200
79
+ version_requirements: *79905270
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: simplecov
82
- requirement: &72355990 !ruby/object:Gem::Requirement
82
+ requirement: &79904230 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ! '>='
@@ -87,7 +87,7 @@ dependencies:
87
87
  version: '0'
88
88
  type: :development
89
89
  prerelease: false
90
- version_requirements: *72355990
90
+ version_requirements: *79904230
91
91
  description: Timeout-lib, which interrupts everything, also systemcalls. It uses libc-alarm.
92
92
  email: Denis.Knauf@gmail.com
93
93
  executables: []
@@ -106,6 +106,7 @@ files:
106
106
  - lib/timeout_interrupt.rb
107
107
  - test/helper.rb
108
108
  - test/test_ruby-timeout-interrupt.rb
109
+ - timeout-interrupt.gemspec
109
110
  homepage: http://github.com/DenisKnauf/ruby-timeout-interrupt
110
111
  licenses:
111
112
  - AGPLv3
@@ -121,7 +122,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
121
122
  version: '0'
122
123
  segments:
123
124
  - 0
124
- hash: -502487251
125
+ hash: -567688307
125
126
  required_rubygems_version: !ruby/object:Gem::Requirement
126
127
  none: false
127
128
  requirements: