runaway 1.0.1 → 2.0.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 +4 -4
- data/README.md +3 -12
- data/lib/runaway.rb +4 -35
- data/runaway.gemspec +3 -3
- data/spec/runaway_spec.rb +1 -25
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8140c09b38d805e869821a9e25fcc4807de4931f
|
4
|
+
data.tar.gz: 44fe561135e1fb3b8abb21094f20fc2435ecdea4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d46f84a99dd4e7e84ccab8779c67e67264bd6bb4e35ce56d3418d0115d0e9617001faabe1224b5ed26455c7d3e14777fb8982b487c95e64579b780ccedc82180
|
7
|
+
data.tar.gz: 8dd7da8f9d6f8fc92c886d68d4b2c6be7bb8f785140ef82f215d47751465100baaa009a69d2542d1529b2a14af2de258d55ed2bf5b738dccc51f917ddff6e0e6
|
data/README.md
CHANGED
@@ -1,18 +1,9 @@
|
|
1
1
|
# runaway
|
2
2
|
|
3
|
-
Spin off work into child processes and terminate it on time
|
3
|
+
Spin off work into child processes and terminate it on time. If you need the process to be done
|
4
|
+
within a certain time period (and if it takes longer it probably is hanging):
|
4
5
|
|
5
|
-
|
6
|
-
and can respond to Unix signals):
|
7
|
-
|
8
|
-
Runaway.spin do
|
9
|
-
ActiveRAMGobbler.load_many_things(num_things: 10_000_000)
|
10
|
-
end
|
11
|
-
|
12
|
-
If you need the process to be done within a certain time period (and if it takes longer
|
13
|
-
it probably is hanging):
|
14
|
-
|
15
|
-
Runaway.spin(must_quite_within: 15) do # ensures termination within 15 seconds
|
6
|
+
Runaway.spin(must_quit_within: 15) do # ensures termination within 15 seconds
|
16
7
|
`/bin/proprietary_render_server/bin/render --put-server-on-fire=yes`
|
17
8
|
end
|
18
9
|
|
data/lib/runaway.rb
CHANGED
@@ -1,14 +1,10 @@
|
|
1
|
-
require 'securerandom'
|
2
|
-
|
3
1
|
module Runaway
|
4
|
-
VERSION = '
|
2
|
+
VERSION = '2.0.0'
|
5
3
|
|
6
4
|
UncleanExit = Class.new(StandardError)
|
7
5
|
Child = Class.new(StandardError)
|
8
|
-
HeartbeatTimeout = Class.new(Child)
|
9
6
|
RuntimeExceeded = Class.new(Child)
|
10
7
|
|
11
|
-
DEFAULT_HEARTBEAT_INTERVAL = 2
|
12
8
|
TERM = 'TERM'.freeze
|
13
9
|
KILL = 'KILL'.freeze
|
14
10
|
USR2 = 'USR2'.freeze
|
@@ -18,20 +14,12 @@ module Runaway
|
|
18
14
|
# Acts as a substitute for a Logger
|
19
15
|
module NullLogger; def self.warn(*); end; end
|
20
16
|
|
21
|
-
def self.spin(must_quit_within: INF,
|
22
|
-
logger: NullLogger, &block_to_run_in_child)
|
23
|
-
cookie = SecureRandom.hex(1)
|
24
|
-
r, w = IO.pipe
|
17
|
+
def self.spin(must_quit_within: INF, logger: NullLogger, &block_to_run_in_child)
|
25
18
|
child_pid = fork do
|
26
|
-
r.close_read
|
27
19
|
# Remove anything that was there from the parent
|
28
|
-
[
|
29
|
-
|
30
|
-
# When the parent asks us for a heartbeat, send the cookie back
|
31
|
-
trap(USR2) { w.write(cookie); w.flush }
|
20
|
+
[TERM, KILL].each { |reset_sig| trap(reset_sig, DEFAULT) }
|
32
21
|
block_to_run_in_child.call
|
33
22
|
end
|
34
|
-
w.close_write
|
35
23
|
|
36
24
|
started_at = Time.now
|
37
25
|
|
@@ -49,10 +37,9 @@ module Runaway
|
|
49
37
|
(Process.kill(sig, child_pid) rescue Errno::ESRCH) if !has_quit
|
50
38
|
}
|
51
39
|
|
52
|
-
last_heartbeat_sent = started_at
|
53
40
|
begin
|
54
41
|
loop do
|
55
|
-
sleep
|
42
|
+
sleep 1
|
56
43
|
|
57
44
|
break if has_quit
|
58
45
|
|
@@ -62,22 +49,6 @@ module Runaway
|
|
62
49
|
raise RuntimeExceeded.new('%d did not terminate after %d secs (limited to %d secs)' % [
|
63
50
|
child_pid, running_for, must_quit_within])
|
64
51
|
end
|
65
|
-
|
66
|
-
# Then check if it is time to poke it with a heartbeat
|
67
|
-
at = Time.now
|
68
|
-
next if (at - last_heartbeat_sent) < heartbeat_interval
|
69
|
-
last_heartbeat_sent = at
|
70
|
-
|
71
|
-
# Then send it the USR2 as a "ping", and expect a "pong" in
|
72
|
-
# the form of a pipe write. If the pipe is still not readable
|
73
|
-
# after a certain time, we assume the process has hung.
|
74
|
-
Process.kill(USR2, child_pid)
|
75
|
-
select_timeout = (heartbeat_interval * 2)
|
76
|
-
ready_read = IO.select([r], [], [], select_timeout)
|
77
|
-
if ready_read.nil?
|
78
|
-
raise HeartbeatTimeout.new('%d did not reply to heartbeat after %d secs' % [child_pid, select_timeout])
|
79
|
-
end
|
80
|
-
r.read(cookie.bytesize)
|
81
52
|
end
|
82
53
|
rescue Runaway => terminating_error
|
83
54
|
logger.error "Terminating %d - %s: %s" % [child_pid, terminating_error.class, terminating_error.message]
|
@@ -97,7 +68,5 @@ module Runaway
|
|
97
68
|
raise unclean_exit_error if unclean_exit_error
|
98
69
|
|
99
70
|
:done
|
100
|
-
ensure
|
101
|
-
r.close_read
|
102
71
|
end
|
103
72
|
end
|
data/runaway.gemspec
CHANGED
@@ -2,16 +2,16 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: runaway
|
5
|
+
# stub: runaway 2.0.0 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "runaway"
|
9
|
-
s.version = "
|
9
|
+
s.version = "2.0.0"
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
12
|
s.require_paths = ["lib"]
|
13
13
|
s.authors = ["Julik Tarkhanov"]
|
14
|
-
s.date = "2016-
|
14
|
+
s.date = "2016-04-07"
|
15
15
|
s.description = "Spin off blocks in child processes and make sure they terminate on time"
|
16
16
|
s.email = "me@julik.nl"
|
17
17
|
s.extra_rdoc_files = [
|
data/spec/runaway_spec.rb
CHANGED
@@ -5,7 +5,7 @@ describe "Runaway" do
|
|
5
5
|
|
6
6
|
it 'supports all the options' do
|
7
7
|
require 'logger'
|
8
|
-
Runaway.spin(must_quit_within: 2,
|
8
|
+
Runaway.spin(must_quit_within: 2, logger: Logger.new($stdout)) {} # just do nothing
|
9
9
|
end
|
10
10
|
|
11
11
|
context 'with a process that quits cleanly' do
|
@@ -42,28 +42,4 @@ describe "Runaway" do
|
|
42
42
|
}
|
43
43
|
end
|
44
44
|
end
|
45
|
-
|
46
|
-
context 'when a process terminates before the first heartbeat has to be dispatched' do
|
47
|
-
it 'just returns :done' do
|
48
|
-
t = Time.now
|
49
|
-
return_token = Runaway.spin(heartbeat_interval: 5) { sleep 0.1 }
|
50
|
-
expect(return_token).to eq(:done)
|
51
|
-
delta = Time.now - t
|
52
|
-
expect(delta).to be < 2
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
context 'when a process stops responding to heartbeats' do
|
57
|
-
it 'kills it quickly and raises an error' do
|
58
|
-
t = Time.now
|
59
|
-
expect {
|
60
|
-
# Delete, then override the USR2 trap so that heartbeats do not get handled at all
|
61
|
-
Runaway.spin(heartbeat_interval: 0.8) { trap('USR2', 'DEFAULT'); trap('USR2') {}; sleep 45 }
|
62
|
-
}.to raise_error {|err|
|
63
|
-
expect(err).to be_kind_of(Runaway::HeartbeatTimeout)
|
64
|
-
expect(err.message).to match(/\d+ did not reply to heartbeat after \d+ secs/)
|
65
|
-
expect(Time.now - t).to be < 5 # should really ahve killed it fast
|
66
|
-
}
|
67
|
-
end
|
68
|
-
end
|
69
45
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: runaway
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Julik Tarkhanov
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-04-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|