restartable 0.1.3 → 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.
- checksums.yaml +8 -8
- data/.gitignore +1 -0
- data/.travis.yml +10 -0
- data/Gemfile +3 -0
- data/README.markdown +2 -0
- data/bin/restartable +1 -1
- data/features/restarting.feature +30 -0
- data/features/step_definitions/restartable_steps.rb +67 -0
- data/lib/restartable.rb +53 -61
- data/restartable.gemspec +3 -1
- metadata +37 -3
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
OGZhZTQzNTI5MDQxODAwNjA5YmM1NGYxMDhkODU3ZTczMWQ2ZGFhOQ==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
MTAwNzEyYWVjZjA2ODM4MWVhMTc3NGRjOTcwYTIwZWI0ZjQ0ZjdmMQ==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
YTE4NjVlMTJiNjhiMzU1NzQ4ZjY3ZWJiOWQ4MjI5YzIxMTVlNGNmMDAwMTdh
|
10
|
+
NDI3YjIxYmYwMTlhYWQyMTcyYTU1ZWU1OWY1YTlkMmU2YTRhOGU2ZmViY2Nj
|
11
|
+
ZDJhZWFiNWFlYjdiZjI1MTM4OTAxNDJjM2MxNzA3MzZlZWM0Nzg=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
YzQ2Zjg5YzhiNTA4ZTBkNTRjNjUwNDEwYzQ5ZjQxM2FiMzA1MzJjOTA1YzYw
|
14
|
+
MWQ1YTIyNWMzOWQzNTY5ZjJiYzI2YWUxMzc2NDFhZDg2ZWEwYTFhZWRmYmFi
|
15
|
+
OGVmMmYxOGYyODZkZTAyNzM5NTVmN2MxMGViNjk5NWViMmM3M2I=
|
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.markdown
CHANGED
data/bin/restartable
CHANGED
@@ -0,0 +1,30 @@
|
|
1
|
+
Feature: Restarting
|
2
|
+
|
3
|
+
Scenario Outline: Restarting and terminating
|
4
|
+
Given I have invoked restartable with `<code>`
|
5
|
+
|
6
|
+
When I have waited for 1 second
|
7
|
+
Then I should see "^C to restart, double ^C to stop" in stderr
|
8
|
+
And I should see "Hello world!" in stdout
|
9
|
+
And there should be an inner process
|
10
|
+
When I interrupt restartable
|
11
|
+
Then I should see "Killing children…" and "Waiting ^C 0.5 second than restart…" in stderr
|
12
|
+
And inner process should terminate
|
13
|
+
|
14
|
+
When I have waited for 1 second
|
15
|
+
Then I should see "^C to restart, double ^C to stop" in stderr
|
16
|
+
And I should see "Hello world!" in stdout
|
17
|
+
And there should be inner process
|
18
|
+
When I interrupt restartable twice
|
19
|
+
Then I should see "Killing children…" and "Don't restart!" in last 3 lines of stderr
|
20
|
+
And inner process should terminate
|
21
|
+
And restartable should finish
|
22
|
+
|
23
|
+
Examples:
|
24
|
+
| code |
|
25
|
+
| $stdout.puts "Hello world!" |
|
26
|
+
| $stdout.puts "Hello world!"; sleep 30 |
|
27
|
+
| exec 'echo "Hello world!"; sleep 30' |
|
28
|
+
| system 'echo "Hello world!"; sleep 30' |
|
29
|
+
| fork{ $stdout.puts "Hello world!"; sleep 30 } |
|
30
|
+
| fork{ fork{ fork{ $stdout.puts "Hello world!"; sleep 30 } } } |
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'restartable'
|
2
|
+
|
3
|
+
Given(/^I have invoked restartable with `(.*?)`$/) do |command|
|
4
|
+
@stdout = IO.pipe
|
5
|
+
@stderr = IO.pipe
|
6
|
+
|
7
|
+
@pid = fork do
|
8
|
+
Process.setpgrp
|
9
|
+
|
10
|
+
@stdout[0].close
|
11
|
+
STDOUT.reopen(@stdout[1])
|
12
|
+
|
13
|
+
@stderr[0].close
|
14
|
+
STDERR.reopen(@stderr[1])
|
15
|
+
|
16
|
+
Restartable.new do
|
17
|
+
Signal.trap('INT', 'EXIT')
|
18
|
+
eval(command)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
@stdout[1].close
|
23
|
+
@stderr[1].close
|
24
|
+
end
|
25
|
+
|
26
|
+
When(/^I have waited for (\d+) second$/) do |seconds|
|
27
|
+
sleep seconds.to_i
|
28
|
+
end
|
29
|
+
|
30
|
+
Then(/^I should see "(.*?)" in stdout$/) do |string|
|
31
|
+
Timeout::timeout(5) do
|
32
|
+
@stdout[0].gets.should include(string)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
Then(/^I should see "(.*?)" in (?:last (\d+) lines of )?stderr$/) do |arg, line_count|
|
37
|
+
Timeout::timeout(5) do
|
38
|
+
strings = arg.split(/".*?"/)
|
39
|
+
line_count = line_count ? line_count.to_i : strings.length
|
40
|
+
got = line_count.times.map{ @stderr[0].gets }.join
|
41
|
+
strings.each do |string|
|
42
|
+
got.should include(string)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
When(/^I interrupt restartable$/) do
|
48
|
+
Process.kill('INT', -@pid)
|
49
|
+
end
|
50
|
+
|
51
|
+
When(/^I interrupt restartable twice$/) do
|
52
|
+
Process.kill('INT', -@pid)
|
53
|
+
sleep 0.1
|
54
|
+
Process.kill('INT', -@pid)
|
55
|
+
end
|
56
|
+
|
57
|
+
Then(/^there should be an inner process$/) do
|
58
|
+
Sys::ProcTable.ps.any?{ |pe| pe.ppid == @cpid }
|
59
|
+
end
|
60
|
+
|
61
|
+
Then(/^inner process should terminate$/) do
|
62
|
+
Sys::ProcTable.ps.none?{ |pe| pe.ppid == @cpid }
|
63
|
+
end
|
64
|
+
|
65
|
+
Then(/^restartable should finish$/) do
|
66
|
+
Process.wait(@pid)
|
67
|
+
end
|
data/lib/restartable.rb
CHANGED
@@ -9,7 +9,7 @@ class Restartable
|
|
9
9
|
Gem.loaded_specs['restartable'].version.to_s rescue 'DEV'
|
10
10
|
end
|
11
11
|
|
12
|
-
def initialize(options, &block)
|
12
|
+
def initialize(options = {}, &block)
|
13
13
|
@options, @block = options, block
|
14
14
|
run!
|
15
15
|
end
|
@@ -17,94 +17,86 @@ class Restartable
|
|
17
17
|
private
|
18
18
|
|
19
19
|
def run!
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
20
|
+
Signal.trap('INT') do
|
21
|
+
interrupt!
|
22
|
+
if @cpid
|
23
|
+
Process.kill('INT', -@cpid) rescue nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
Signal.trap('TERM') do
|
27
|
+
terminate!
|
28
28
|
end
|
29
|
-
sender.close
|
30
|
-
|
31
|
-
Signal.trap('INT', 'IGNORE')
|
32
|
-
synced_trap('TERM'){ terminate! }
|
33
29
|
|
34
|
-
|
35
|
-
|
36
|
-
|
30
|
+
until @stop
|
31
|
+
@interrupted = nil
|
32
|
+
$stderr << "^C to restart, double ^C to stop\n".green
|
33
|
+
@cpid = fork do
|
34
|
+
Process.setpgrp
|
35
|
+
Signal.trap('INT', 'DEFAULT')
|
36
|
+
Signal.trap('TERM', 'DEFAULT')
|
37
|
+
@block.call
|
38
|
+
end
|
39
|
+
sleep 0.1 until @interrupted
|
40
|
+
kill_children!
|
41
|
+
unless @stop
|
42
|
+
$stderr << "Waiting ^C 0.5 second than restart…\n".yellow.bold
|
43
|
+
sleep 0.5
|
37
44
|
end
|
38
45
|
end
|
39
|
-
cycle
|
40
46
|
end
|
41
47
|
|
42
48
|
def interrupt!
|
43
|
-
|
44
|
-
@
|
45
|
-
|
46
|
-
thread.raise SignalException.new('INT')
|
47
|
-
end
|
49
|
+
if @interrupted
|
50
|
+
@stop = true
|
51
|
+
$stderr << "Don't restart!\n".red.bold
|
48
52
|
else
|
49
|
-
|
53
|
+
@interrupted = true
|
50
54
|
end
|
51
55
|
end
|
52
56
|
|
53
57
|
def terminate!
|
54
|
-
no_restart!
|
55
58
|
interrupt!
|
56
|
-
|
57
|
-
|
58
|
-
def no_restart!
|
59
|
-
@stop = true
|
60
|
-
puts 'Don\'t restart!'.red.bold
|
61
|
-
end
|
62
|
-
|
63
|
-
def synced_trap(signal, &block)
|
64
|
-
Signal.trap(signal) do
|
65
|
-
Thread.new do
|
66
|
-
@mutex.synchronize(&block)
|
67
|
-
end
|
68
|
-
end
|
59
|
+
interrupt!
|
69
60
|
end
|
70
61
|
|
71
62
|
WAIT_SIGNALS = [[5, 'INT'], [3, 'INT'], [1, 'INT'], [3, 'TERM'], [5, 'KILL']]
|
72
63
|
|
73
|
-
def cycle
|
74
|
-
until @stop
|
75
|
-
@interrupted = false
|
76
|
-
puts '^C to restart, double ^C to stop'.green
|
77
|
-
begin
|
78
|
-
@block.call
|
79
|
-
loop{ sleep } # wait ^C even if block finishes
|
80
|
-
rescue SignalException
|
81
|
-
kill_children!
|
82
|
-
end
|
83
|
-
unless @stop
|
84
|
-
puts 'Waiting ^C 0.5 second than restart…'.yellow.bold
|
85
|
-
sleep 0.5
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
64
|
def kill_children!
|
91
|
-
|
92
|
-
|
65
|
+
until (pids = children_pids).empty?
|
66
|
+
$stderr << "Killing children…\n".yellow.bold
|
93
67
|
ripper = Thread.new do
|
94
68
|
WAIT_SIGNALS.each do |time, signal|
|
95
69
|
sleep time
|
96
|
-
|
97
|
-
|
98
|
-
|
70
|
+
$stderr << "…SIG#{signal}…\n".yellow
|
71
|
+
Process.kill('INT', -@cpid)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
Process.waitall
|
75
|
+
pids.each do |pid|
|
76
|
+
begin
|
77
|
+
loop do
|
78
|
+
Process.kill(0, pid)
|
79
|
+
sleep 1
|
99
80
|
end
|
81
|
+
rescue Errno::ESRCH
|
100
82
|
end
|
101
83
|
end
|
102
|
-
children_pids.each(&Process.method(:wait))
|
103
84
|
ripper.terminate
|
104
85
|
end
|
105
86
|
end
|
106
87
|
|
107
88
|
def children_pids
|
108
|
-
Sys::ProcTable.ps.select
|
89
|
+
Sys::ProcTable.ps.select do |pe|
|
90
|
+
@cpid == case
|
91
|
+
when pe.respond_to?(:pgid)
|
92
|
+
pe.pgid
|
93
|
+
when pe.respond_to?(:pgrp)
|
94
|
+
pe.pgrp
|
95
|
+
when pe.respond_to?(:ppid)
|
96
|
+
pe.ppid
|
97
|
+
else
|
98
|
+
raise 'Can\'t find process group id'
|
99
|
+
end
|
100
|
+
end.map(&:pid)
|
109
101
|
end
|
110
102
|
end
|
data/restartable.gemspec
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = 'restartable'
|
5
|
-
s.version = '0.
|
5
|
+
s.version = '0.2.0'
|
6
6
|
s.summary = %q{Run code, Ctrl-C to restart, once more Ctrl-C to stop}
|
7
7
|
s.homepage = "http://github.com/toy/#{s.name}"
|
8
8
|
s.authors = ['Ivan Kuchin']
|
@@ -17,4 +17,6 @@ Gem::Specification.new do |s|
|
|
17
17
|
|
18
18
|
s.add_dependency 'colored', '~> 1.2'
|
19
19
|
s.add_dependency 'sys-proctable', '~> 0.9.3'
|
20
|
+
s.add_development_dependency 'cucumber'
|
21
|
+
s.add_development_dependency 'rspec'
|
20
22
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: restartable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ivan Kuchin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-08-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: colored
|
@@ -38,6 +38,34 @@ dependencies:
|
|
38
38
|
- - ~>
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: 0.9.3
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: cucumber
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ! '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
41
69
|
description:
|
42
70
|
email:
|
43
71
|
executables:
|
@@ -46,10 +74,14 @@ extensions: []
|
|
46
74
|
extra_rdoc_files: []
|
47
75
|
files:
|
48
76
|
- .gitignore
|
77
|
+
- .travis.yml
|
78
|
+
- Gemfile
|
49
79
|
- LICENSE.txt
|
50
80
|
- README.markdown
|
51
81
|
- TODO
|
52
82
|
- bin/restartable
|
83
|
+
- features/restarting.feature
|
84
|
+
- features/step_definitions/restartable_steps.rb
|
53
85
|
- lib/restartable.rb
|
54
86
|
- restartable.gemspec
|
55
87
|
homepage: http://github.com/toy/restartable
|
@@ -76,4 +108,6 @@ rubygems_version: 2.0.3
|
|
76
108
|
signing_key:
|
77
109
|
specification_version: 4
|
78
110
|
summary: Run code, Ctrl-C to restart, once more Ctrl-C to stop
|
79
|
-
test_files:
|
111
|
+
test_files:
|
112
|
+
- features/restarting.feature
|
113
|
+
- features/step_definitions/restartable_steps.rb
|