safe_timeout 0.0.3 → 0.0.4

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