foreground 0.0.4 → 0.1.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.
- data/README.md +1 -1
- data/features/start_stop.feature +1 -1
- data/features/steps/sample_daemon_steps.rb +1 -1
- data/features/watch_daemon.feature +14 -0
- data/lib/foreground.rb +21 -1
- data/lib/foreground/cli.rb +16 -1
- data/lib/foreground/daemon.rb +27 -5
- data/lib/foreground/signal_handlers.rb +1 -1
- data/lib/foreground/version.rb +1 -1
- data/spec/foreground/cli_spec.rb +8 -2
- data/spec/foreground/daemon_spec.rb +57 -5
- data/spec/foreground_spec.rb +34 -0
- metadata +9 -5
data/README.md
CHANGED
@@ -27,7 +27,7 @@ Install it via RubyGems:
|
|
27
27
|
|
28
28
|
Wrap your daemon command inside your launchd.plist:
|
29
29
|
|
30
|
-
foreground --pid_file /tmp/foreground_sample_daemon.pid foreground_sample_daemon
|
30
|
+
foreground --pid_file /tmp/foreground_sample_daemon.pid --command "foreground_sample_daemon --with arguments"
|
31
31
|
|
32
32
|
## Limitations
|
33
33
|
|
data/features/start_stop.feature
CHANGED
@@ -40,7 +40,7 @@ end
|
|
40
40
|
|
41
41
|
When /^I run the sample daemon via foreground$/ do
|
42
42
|
@foreground = fork do
|
43
|
-
exec('foreground', '--pid_file', '/tmp/foreground_sample_daemon.pid', 'foreground_sample_daemon')
|
43
|
+
exec('foreground', '--pid_file', '/tmp/foreground_sample_daemon.pid', '--command', 'foreground_sample_daemon')
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
@@ -0,0 +1,14 @@
|
|
1
|
+
Feature: Watch daemon
|
2
|
+
|
3
|
+
In order to notify launchd about a terminated daemon
|
4
|
+
As a funky user
|
5
|
+
I want foreground to terminate as well
|
6
|
+
|
7
|
+
Scenario: Terminate when daemon goes down
|
8
|
+
Given I run the sample daemon via foreground
|
9
|
+
Then foreground should run
|
10
|
+
And the sample daemon should run
|
11
|
+
Given I run `sleep 2`
|
12
|
+
When I kill the sample daemon
|
13
|
+
Then foreground should not run
|
14
|
+
And the sample daemon should not run
|
data/lib/foreground.rb
CHANGED
@@ -1,8 +1,28 @@
|
|
1
|
+
require 'logger'
|
1
2
|
require 'mixlib/cli'
|
2
3
|
require 'foreground/version'
|
3
4
|
require 'foreground/daemon'
|
4
5
|
require 'foreground/cli'
|
5
6
|
|
6
7
|
module Foreground
|
7
|
-
|
8
|
+
class << self
|
9
|
+
# The global configuration based on command line options.
|
10
|
+
attr_accessor :config
|
11
|
+
end
|
12
|
+
|
13
|
+
def logger
|
14
|
+
@logger ||= create_logger
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def create_logger
|
20
|
+
STDOUT.sync = true
|
21
|
+
l = Logger.new(STDOUT)
|
22
|
+
#TODO: Test this!
|
23
|
+
l.formatter = proc do |severity, datetime, progname, msg|
|
24
|
+
"#{progname} [#{severity}]: #{msg}\n"
|
25
|
+
end
|
26
|
+
l
|
27
|
+
end
|
8
28
|
end
|
data/lib/foreground/cli.rb
CHANGED
@@ -8,6 +8,20 @@ module Foreground
|
|
8
8
|
:description => 'PID file for the daemon',
|
9
9
|
:required => true
|
10
10
|
|
11
|
+
option :command,
|
12
|
+
:short => '-c COMMAND',
|
13
|
+
:long => '--command COMMAND',
|
14
|
+
:description => 'Daemon command line',
|
15
|
+
:required => true
|
16
|
+
|
17
|
+
option :timeout,
|
18
|
+
:short => '-t SECONDS',
|
19
|
+
:long => '--timeout SECONDS',
|
20
|
+
:description => 'Timeout for the daemon to generate a valid PID file',
|
21
|
+
:required => false,
|
22
|
+
:default => 2,
|
23
|
+
:proc => Proc.new { |t| t.to_i }
|
24
|
+
|
11
25
|
class << self
|
12
26
|
def run(argv=ARGV)
|
13
27
|
new.run(argv)
|
@@ -16,7 +30,8 @@ module Foreground
|
|
16
30
|
|
17
31
|
def run(argv)
|
18
32
|
cmd = parse_options(argv)
|
19
|
-
|
33
|
+
Foreground.config = config
|
34
|
+
Daemon.run(config[:command], config[:pid_file])
|
20
35
|
end
|
21
36
|
end
|
22
37
|
end
|
data/lib/foreground/daemon.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'foreground'
|
2
2
|
|
3
3
|
module Foreground
|
4
|
+
class DaemonError < StandardError; end
|
5
|
+
|
4
6
|
class Daemon
|
5
7
|
@daemon = nil
|
6
8
|
|
@@ -34,15 +36,35 @@ module Foreground
|
|
34
36
|
Process.kill(signal, pid)
|
35
37
|
end
|
36
38
|
|
39
|
+
#TODO: Add scenario for this stuff!
|
37
40
|
def pid
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
+
return @pid unless @pid.nil?
|
42
|
+
elapsed_time = 0
|
43
|
+
sleep_time = 0.1
|
44
|
+
begin
|
45
|
+
break if @pid = read_pid
|
46
|
+
rescue
|
47
|
+
raise unless elapsed_time < Foreground.config[:timeout]
|
48
|
+
elapsed_time += sleep_time
|
49
|
+
end while sleep(sleep_time)
|
50
|
+
@pid
|
41
51
|
end
|
42
52
|
|
43
53
|
def watch
|
44
|
-
|
45
|
-
|
54
|
+
watch_interval = 10
|
55
|
+
begin
|
56
|
+
kill(0)
|
57
|
+
rescue Errno::ESRCH
|
58
|
+
raise DaemonError, "No process with PID #{pid} found."
|
59
|
+
end while sleep(watch_interval)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def read_pid
|
65
|
+
pid = File.read(@pid_file).to_i
|
66
|
+
raise DaemonError, "PID not readable from #{@pid_file}" unless pid > 0
|
67
|
+
pid
|
46
68
|
end
|
47
69
|
end
|
48
70
|
end
|
data/lib/foreground/version.rb
CHANGED
data/spec/foreground/cli_spec.rb
CHANGED
@@ -4,7 +4,8 @@ module Foreground
|
|
4
4
|
describe CLI do
|
5
5
|
before do
|
6
6
|
Daemon.stub(:run)
|
7
|
-
@
|
7
|
+
@cmd = 'foreground_sample_daemon --with "some arguments"'
|
8
|
+
@argv = ['--pid_file', '/tmp/foreground_sample_daemon.pid', '--command', @cmd]
|
8
9
|
@cli = CLI.new
|
9
10
|
end
|
10
11
|
|
@@ -23,8 +24,13 @@ module Foreground
|
|
23
24
|
@cli.run(@argv)
|
24
25
|
end
|
25
26
|
|
27
|
+
it 'should make the config globally available' do
|
28
|
+
@cli.run(@argv)
|
29
|
+
Foreground.config.should eql(@cli.config)
|
30
|
+
end
|
31
|
+
|
26
32
|
it 'should run the daemon' do
|
27
|
-
Daemon.should_receive(:run).with(
|
33
|
+
Daemon.should_receive(:run).with(@cmd, '/tmp/foreground_sample_daemon.pid')
|
28
34
|
@cli.run(@argv)
|
29
35
|
end
|
30
36
|
end
|
@@ -4,7 +4,8 @@ module Foreground
|
|
4
4
|
describe Daemon do
|
5
5
|
before do
|
6
6
|
Process.stub(:kill)
|
7
|
-
|
7
|
+
#@cmd = ['some_daemon', '--with', 'arguments']
|
8
|
+
@cmd = 'some_daemon --with "many arguments"'
|
8
9
|
@pid_file = 'some_daemon.pid'
|
9
10
|
@pid = 42
|
10
11
|
@args = [@cmd, @pid_file]
|
@@ -34,12 +35,31 @@ module Foreground
|
|
34
35
|
|
35
36
|
describe '#run' do
|
36
37
|
it 'should run and watch the daemon' do
|
37
|
-
@daemon.should_receive(:system).with(
|
38
|
+
@daemon.should_receive(:system).with(@cmd).ordered
|
38
39
|
@daemon.should_receive(:watch).ordered
|
39
40
|
@daemon.run
|
40
41
|
end
|
41
42
|
end
|
42
43
|
|
44
|
+
describe '#watch' do
|
45
|
+
before do
|
46
|
+
@daemon = Daemon.new(@cmd, @pid_file)
|
47
|
+
@daemon.stub(:pid).and_return(@pid)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should sleep if the daemon is still alive' do
|
51
|
+
@daemon.should_receive(:sleep).with(10).and_return(nil)
|
52
|
+
Process.should_receive(:kill).with(0, @pid).and_return(1)
|
53
|
+
@daemon.watch
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'should raise error if the daemon is not alive' do
|
57
|
+
@daemon.should_not_receive(:sleep)
|
58
|
+
Process.should_receive(:kill).with(0, @pid).and_raise(Errno::ESRCH)
|
59
|
+
lambda { @daemon.watch }.should raise_error(DaemonError, /no process with pid #{@pid} found/i)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
43
63
|
describe '#kill' do
|
44
64
|
it 'should send the daemon a SIGTERM' do
|
45
65
|
@daemon.stub(:pid).and_return(42)
|
@@ -48,10 +68,42 @@ module Foreground
|
|
48
68
|
end
|
49
69
|
end
|
50
70
|
|
71
|
+
describe '#read_pid' do
|
72
|
+
context 'with existing PID file' do
|
73
|
+
it 'should return the PID' do
|
74
|
+
File.should_receive(:read).with(@pid_file).and_return("#{@pid}\n")
|
75
|
+
@daemon.send(:read_pid).should eql(@pid)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context 'with unreadable PID' do
|
80
|
+
it 'should raise error' do
|
81
|
+
File.should_receive(:read).with(@pid_file).and_return('fortytwo')
|
82
|
+
lambda { @daemon.send(:read_pid) }.should raise_error(DaemonError, /pid not readable from #{@pid_file}/i)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
51
87
|
describe '#pid' do
|
52
|
-
|
53
|
-
|
54
|
-
|
88
|
+
context 'with readable PID' do
|
89
|
+
it 'should cache and return the PID' do
|
90
|
+
@daemon.should_receive(:read_pid).once.and_return(@pid)
|
91
|
+
2.times { @daemon.pid.should eql(@pid) }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'with unreadable PID' do
|
96
|
+
it 'should rescue and retry after sleep' do
|
97
|
+
@daemon.should_receive(:read_pid).and_raise(StandardError)
|
98
|
+
@daemon.should_receive(:sleep).with(0.1).and_return(nil)
|
99
|
+
@daemon.pid
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'should reraise last exception after 2 seconds' do
|
103
|
+
@daemon.stub(:read_pid).and_raise(Errno::ENOENT)
|
104
|
+
@daemon.should_receive(:sleep).with(0.1).exactly(20).times.and_return(true)
|
105
|
+
lambda { @daemon.pid }.should raise_error(Errno::ENOENT)
|
106
|
+
end
|
55
107
|
end
|
56
108
|
end
|
57
109
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Foreground
|
4
|
+
class Foo
|
5
|
+
include Foreground
|
6
|
+
end
|
7
|
+
|
8
|
+
describe '#logger' do
|
9
|
+
before do
|
10
|
+
@logger = double('logger')
|
11
|
+
@logger.stub(:formatter=)
|
12
|
+
Logger.stub(:new).and_return(@logger)
|
13
|
+
@foo = Foo.new
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should initialize a new logger once' do
|
17
|
+
Logger.should_receive(:new).once.with(STDOUT)
|
18
|
+
2.times { @foo.logger }
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should return the logger' do
|
22
|
+
@foo.logger.should eql(@logger)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should flush to stdout' do
|
26
|
+
original_sync_state = STDOUT.sync
|
27
|
+
STDOUT.sync = false
|
28
|
+
STDOUT.sync.should be_false
|
29
|
+
@foo.logger
|
30
|
+
STDOUT.sync.should be_true
|
31
|
+
STDOUT.sync = original_sync_state
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: foreground
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-09-
|
12
|
+
date: 2012-09-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: mixlib-cli
|
@@ -144,6 +144,7 @@ files:
|
|
144
144
|
- features/start_stop.feature
|
145
145
|
- features/steps/sample_daemon_steps.rb
|
146
146
|
- features/support/env.rb
|
147
|
+
- features/watch_daemon.feature
|
147
148
|
- foreground.gemspec
|
148
149
|
- lib/foreground.rb
|
149
150
|
- lib/foreground/cli.rb
|
@@ -153,6 +154,7 @@ files:
|
|
153
154
|
- script/bootstrap
|
154
155
|
- spec/foreground/cli_spec.rb
|
155
156
|
- spec/foreground/daemon_spec.rb
|
157
|
+
- spec/foreground_spec.rb
|
156
158
|
- spec/spec_helper.rb
|
157
159
|
homepage: https://github.com/bjoernalbers/foreground
|
158
160
|
licenses: []
|
@@ -168,7 +170,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
168
170
|
version: '0'
|
169
171
|
segments:
|
170
172
|
- 0
|
171
|
-
hash:
|
173
|
+
hash: 1710979522724105195
|
172
174
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
173
175
|
none: false
|
174
176
|
requirements:
|
@@ -177,18 +179,20 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
177
179
|
version: '0'
|
178
180
|
segments:
|
179
181
|
- 0
|
180
|
-
hash:
|
182
|
+
hash: 1710979522724105195
|
181
183
|
requirements: []
|
182
184
|
rubyforge_project:
|
183
185
|
rubygems_version: 1.8.24
|
184
186
|
signing_key:
|
185
187
|
specification_version: 3
|
186
|
-
summary: foreground-0.0
|
188
|
+
summary: foreground-0.1.0
|
187
189
|
test_files:
|
188
190
|
- features/sample_daemon.feature
|
189
191
|
- features/start_stop.feature
|
190
192
|
- features/steps/sample_daemon_steps.rb
|
191
193
|
- features/support/env.rb
|
194
|
+
- features/watch_daemon.feature
|
192
195
|
- spec/foreground/cli_spec.rb
|
193
196
|
- spec/foreground/daemon_spec.rb
|
197
|
+
- spec/foreground_spec.rb
|
194
198
|
- spec/spec_helper.rb
|