timeout-interrupt 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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: