safe_timeout 0.0.3 → 0.0.4

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: 2a1dc91fe9838d18748dfa219507d52abfbbc072
4
- data.tar.gz: 2415cf891a23e2cb38a608c489ab7fa9e18be384
3
+ metadata.gz: d1f06d3a6a1b3d03875ee2501d1bfa1ceefcc676
4
+ data.tar.gz: 7d5a7e5273989529d22bddb49927ae091866ba96
5
5
  SHA512:
6
- metadata.gz: 00e4442f99e7143a0fbef4caf51c6f023578b13def7d0c058c393577c049e4aca26cf4874c8311e3869f9390b2d6b7650dbfd7533e8dab0144b2e1645b337966
7
- data.tar.gz: 007b456918328c1b87edf436538af4bf1cb62fec547b5dd00751409aee6aaba24555523f01ab12ba118e02afb7046b8ed29d1b817f776be1254e07c865b5f326
6
+ metadata.gz: 48ef6022f8aecfb0e69c348adec09d589c3dba1929896ce843685f45123bc6d43605f66823ee3271b2a73b5d70852b159f37906925160e8559f930f0fedd9b75
7
+ data.tar.gz: 14e77cc5ca9f6c1f24459fda1e35186697af31a75814658f32591bc999231de8d4c85f78d6cf8f1755a0e58341eaa3097d9f0f8b6c9e68f5f31f8bdeb0b6c35b
data/bin/safe_timeout ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require 'safe_timeout'
3
+ SafeTimeout::InterruptingChildProcess.new(*ARGV).wait_for_timeout
@@ -1,43 +1,25 @@
1
1
  module SafeTimeout
2
2
  class InterruptingChildProcess
3
3
 
4
- def initialize(options={})
5
- @expiration = Time.now.to_f + options.fetch(:timeout)
6
- @on_timeout = options.fetch(:on_timeout)
7
- end
4
+ def initialize(ppid, expiration)
5
+ @ppid = ppid.to_i
6
+ @expiration = expiration.to_f
8
7
 
9
- def start(&block)
10
- Signal.trap("TRAP", &@on_timeout)
11
- @child_pid = Kernel.fork { wait_for_expiration }
12
- yield
13
- ensure
14
- begin
15
- stop
16
- rescue Errno::ESRCH
17
- end
8
+ abort "Invalid pid to monitor: #{@ppid}" if @ppid == 0
9
+ abort "Invalid expiration: #{@expiration}" if @expiration == 0.0
18
10
  end
19
11
 
20
- def stop
21
- # Tell that child to stop interrupting!
22
- Process.kill("HUP", @child_pid)
12
+ def notify_parent_of_expiration
13
+ SafeTimeout.send_signal('TRAP', @ppid)
23
14
  end
24
15
 
25
- def wait_for_expiration
26
- Signal.trap("HUP") { exit 0 }
16
+ def wait_for_timeout
17
+ Signal.trap('HUP') { exit 0 }
18
+
19
+ sleep [@expiration - Time.now.to_f, 0.1].max
27
20
 
28
- # If the parent dies unexpectedly and the child is never told to
29
- # stop, it becomes an orphan and is given to the init process (1)
30
- # or worse yet it becomes a zombie with parent 0. In either case,
31
- # stop interrupting!
32
- while Process.ppid > 1
33
- sleep 0.1
34
- if Time.now.to_f > @expiration
35
- Process.kill("TRAP", Process.ppid)
36
- return
37
- end
38
- end
21
+ notify_parent_of_expiration
39
22
  end
40
23
 
41
24
  end
42
-
43
25
  end
@@ -0,0 +1,32 @@
1
+ module SafeTimeout
2
+ class Spawner
3
+
4
+ def initialize(options={})
5
+ @expiration = Time.now.to_f + options.fetch(:timeout)
6
+ @on_timeout = options.fetch(:on_timeout)
7
+ end
8
+
9
+ def start(&block)
10
+ original = Signal.trap('TRAP', &@on_timeout) || 'DEFAULT'
11
+ spawn_interrupter
12
+ yield
13
+ ensure
14
+ Signal.trap('TRAP', original)
15
+ stop
16
+ end
17
+
18
+ def stop
19
+ # Tell that child to stop interrupting!
20
+ SafeTimeout.send_signal('HUP', @child_pid)
21
+ end
22
+
23
+ def spawn_interrupter
24
+ # Create a light-weight child process to notify this process if it is
25
+ # taking too long
26
+ bin = Gem.bin_path('safe_timeout', 'safe_timeout')
27
+ @child_pid = Process.spawn(bin, $$.to_s, @expiration.to_s)
28
+ Process.detach(@child_pid)
29
+ end
30
+
31
+ end
32
+ end
@@ -1,3 +1,3 @@
1
1
  module SafeTimeout
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
data/lib/safe_timeout.rb CHANGED
@@ -1,15 +1,23 @@
1
- # Ruby's Timeout is broken and highly dangerous. To avoid this risk we
2
- # instead use a child process to handle the timeout. We fork it and let
3
- # it issue a SIGINT (Ctrl-C) to the parent if the timeout is reached.
1
+ #
2
+ # Ruby's Timeout is broken and highly dangerous. To avoid this risk we instead
3
+ # use a child process to handle the timeout. We fork it and let it issue a SIGINT
4
+ # (Ctrl-C) to the parent if the timeout is reached.
5
+ #
4
6
  module SafeTimeout
5
7
 
6
8
  autoload :InterruptingChildProcess, 'safe_timeout/interrupting_child_process'
9
+ autoload :Spawner, 'safe_timeout/spawner'
7
10
 
8
11
  def self.timeout(sec, klass=nil, &block)
9
- InterruptingChildProcess.new(
12
+ Spawner.new(
10
13
  :timeout => sec,
11
14
  :on_timeout => lambda { |_| raise(klass || Timeout::Error) }
12
15
  ).start(&block)
13
16
  end
14
17
 
18
+ def self.send_signal(signal, pid)
19
+ Process.kill(signal, pid) if pid
20
+ rescue Errno::ESRCH
21
+ end
22
+
15
23
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: safe_timeout
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - David McCullars
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-09-17 00:00:00.000000000 Z
11
+ date: 2015-09-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -56,7 +56,8 @@ description: A safer alternative to Ruby's Timeout that uses unix processes inst
56
56
  of threads.
57
57
  email:
58
58
  - david.mccullars@gmail.com
59
- executables: []
59
+ executables:
60
+ - safe_timeout
60
61
  extensions: []
61
62
  extra_rdoc_files: []
62
63
  files:
@@ -66,9 +67,11 @@ files:
66
67
  - LICENSE.txt
67
68
  - README.md
68
69
  - Rakefile
70
+ - bin/safe_timeout
69
71
  - circle.yml
70
72
  - lib/safe_timeout.rb
71
73
  - lib/safe_timeout/interrupting_child_process.rb
74
+ - lib/safe_timeout/spawner.rb
72
75
  - lib/safe_timeout/version.rb
73
76
  - safe_timeout.gemspec
74
77
  - spec/lib/safe_timeout_spec.rb