daemon-spawn 0.3.0 → 0.4.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/Rakefile +2 -3
- data/VERSION +1 -1
- data/daemon-spawn.gemspec +13 -5
- data/lib/daemon_spawn.rb +31 -16
- data/test/daemon_spawn_test.rb +85 -3
- data/test/helper.rb +26 -0
- data/test/multi_daemon_spawn_test.rb +1 -1
- data/test/servers/deaf_server.rb +28 -0
- data/test/servers/echo_server.rb +3 -2
- data/test/servers/stubborn_server.rb +27 -0
- data/test/servers/taps_server.rb +21 -0
- metadata +16 -4
data/Rakefile
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# -*- ruby -*-
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
|
-
require 'jeweler'
|
5
4
|
require './lib/daemon_spawn.rb'
|
6
5
|
|
7
6
|
begin
|
@@ -15,9 +14,9 @@ begin
|
|
15
14
|
gemspec.rubyforge_project = "daemon-spawn"
|
16
15
|
gemspec.email = "alex.vollmer@gmail.com"
|
17
16
|
gemspec.homepage = "http://github.com/alexvollmer/daemon-spawn"
|
18
|
-
gemspec.authors = ["Alex Vollmer"]
|
19
|
-
Jeweler::GemcutterTasks.new
|
17
|
+
gemspec.authors = ["Alex Vollmer", "Seamus Abshere", "Emmanual Gomez", "Seth Falcon", "Woody Peterson"]
|
20
18
|
end
|
19
|
+
Jeweler::GemcutterTasks.new
|
21
20
|
rescue LoadError
|
22
21
|
puts "Jeweler not available. Install it with: sudo gem install jeweler"
|
23
22
|
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4.0
|
data/daemon-spawn.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{daemon-spawn}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.4.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
-
s.authors = ["Alex Vollmer"]
|
12
|
-
s.date = %q{2010-05
|
11
|
+
s.authors = ["Alex Vollmer", "Seamus Abshere", "Emmanual Gomez", "Seth Falcon", "Woody Peterson"]
|
12
|
+
s.date = %q{2010-09-05}
|
13
13
|
s.description = %q{With daemon-spawn you can start, stop and restart processes that run
|
14
14
|
in the background. Processed are tracked by a simple PID file written
|
15
15
|
to disk.}
|
@@ -28,9 +28,13 @@ Gem::Specification.new do |s|
|
|
28
28
|
"daemon-spawn.gemspec",
|
29
29
|
"lib/daemon_spawn.rb",
|
30
30
|
"test/daemon_spawn_test.rb",
|
31
|
+
"test/helper.rb",
|
31
32
|
"test/multi_daemon_spawn_test.rb",
|
33
|
+
"test/servers/deaf_server.rb",
|
32
34
|
"test/servers/echo_server.rb",
|
33
|
-
"test/servers/simple_server.rb"
|
35
|
+
"test/servers/simple_server.rb",
|
36
|
+
"test/servers/stubborn_server.rb",
|
37
|
+
"test/servers/taps_server.rb"
|
34
38
|
]
|
35
39
|
s.homepage = %q{http://github.com/alexvollmer/daemon-spawn}
|
36
40
|
s.rdoc_options = ["--charset=UTF-8"]
|
@@ -40,9 +44,13 @@ Gem::Specification.new do |s|
|
|
40
44
|
s.summary = %q{Daemon launching and management made dead simple}
|
41
45
|
s.test_files = [
|
42
46
|
"test/daemon_spawn_test.rb",
|
47
|
+
"test/helper.rb",
|
43
48
|
"test/multi_daemon_spawn_test.rb",
|
49
|
+
"test/servers/deaf_server.rb",
|
44
50
|
"test/servers/echo_server.rb",
|
45
|
-
"test/servers/simple_server.rb"
|
51
|
+
"test/servers/simple_server.rb",
|
52
|
+
"test/servers/stubborn_server.rb",
|
53
|
+
"test/servers/taps_server.rb"
|
46
54
|
]
|
47
55
|
|
48
56
|
if s.respond_to? :specification_version then
|
data/lib/daemon_spawn.rb
CHANGED
@@ -3,6 +3,7 @@ require 'fileutils'
|
|
3
3
|
# Large portions of this were liberally stolen from the
|
4
4
|
# 'simple-daemon' project at http://simple-daemon.rubyforge.org/
|
5
5
|
module DaemonSpawn
|
6
|
+
VERSION = '0.3.0'
|
6
7
|
|
7
8
|
def self.usage(msg=nil) #:nodoc:
|
8
9
|
print "#{msg}, " if msg
|
@@ -10,6 +11,12 @@ module DaemonSpawn
|
|
10
11
|
puts "Where <command> is one of start, stop, restart or status"
|
11
12
|
puts "[options] are additional options passed to the underlying process"
|
12
13
|
end
|
14
|
+
|
15
|
+
def self.alive?(pid)
|
16
|
+
Process.kill 0, pid
|
17
|
+
rescue Errno::ESRCH
|
18
|
+
false
|
19
|
+
end
|
13
20
|
|
14
21
|
def self.start(daemon, args) #:nodoc:
|
15
22
|
if !File.writable?(File.dirname(daemon.log_file))
|
@@ -48,11 +55,22 @@ module DaemonSpawn
|
|
48
55
|
def self.stop(daemon) #:nodoc:
|
49
56
|
if pid = daemon.pid
|
50
57
|
FileUtils.rm(daemon.pid_file)
|
51
|
-
Process.kill(
|
58
|
+
Process.kill(daemon.signal, pid)
|
52
59
|
begin
|
53
60
|
Process.wait(pid)
|
54
61
|
rescue Errno::ECHILD
|
55
62
|
end
|
63
|
+
if ticks = daemon.timeout
|
64
|
+
while ticks > 0 and alive?(pid) do
|
65
|
+
puts "Process is still alive. #{ticks} seconds until I kill -9 it..."
|
66
|
+
sleep 1
|
67
|
+
ticks -= 1
|
68
|
+
end
|
69
|
+
if alive?(pid)
|
70
|
+
puts "Process didn't quit after timeout of #{daemon.timeout} seconds. Killing..."
|
71
|
+
Process.kill 9, pid
|
72
|
+
end
|
73
|
+
end
|
56
74
|
else
|
57
75
|
puts "PID file not found. Is the daemon started?"
|
58
76
|
end
|
@@ -65,7 +83,7 @@ module DaemonSpawn
|
|
65
83
|
end
|
66
84
|
|
67
85
|
class Base
|
68
|
-
attr_accessor :log_file, :pid_file, :sync_log, :working_dir, :app_name, :singleton, :index
|
86
|
+
attr_accessor :log_file, :pid_file, :sync_log, :working_dir, :app_name, :singleton, :index, :signal, :timeout
|
69
87
|
|
70
88
|
def initialize(opts = {})
|
71
89
|
raise 'You must specify a :working_dir' unless opts[:working_dir]
|
@@ -73,6 +91,8 @@ module DaemonSpawn
|
|
73
91
|
self.app_name = opts[:application] || classname
|
74
92
|
self.pid_file = opts[:pid_file] || File.join(working_dir, 'tmp', 'pids', app_name + extension)
|
75
93
|
self.log_file = opts[:log_file] || File.join(working_dir, 'logs', app_name + '.log')
|
94
|
+
self.signal = opts[:signal] || 'TERM'
|
95
|
+
self.timeout = opts[:timeout]
|
76
96
|
self.index = opts[:index] || 0
|
77
97
|
if self.index > 0
|
78
98
|
self.pid_file += ".#{self.index}"
|
@@ -100,12 +120,8 @@ module DaemonSpawn
|
|
100
120
|
end
|
101
121
|
|
102
122
|
def alive? #:nodoc:
|
103
|
-
if File.file?(
|
104
|
-
|
105
|
-
Process.kill(0, self.pid)
|
106
|
-
rescue Errno::ESRCH, ::Exception
|
107
|
-
false
|
108
|
-
end
|
123
|
+
if File.file?(pid_file)
|
124
|
+
DaemonSpawn.alive? pid
|
109
125
|
else
|
110
126
|
false
|
111
127
|
end
|
@@ -156,15 +172,14 @@ module DaemonSpawn
|
|
156
172
|
end
|
157
173
|
|
158
174
|
def self.start(opts, args)
|
159
|
-
|
160
|
-
if
|
161
|
-
|
162
|
-
daemons.map { |d| DaemonSpawn.start(d, args) }
|
163
|
-
else
|
164
|
-
puts "Daemons already started! PIDS: #{daemons.map {|d| d.pid}.join(', ')}"
|
175
|
+
living_daemons = find(opts).select { |d| d.alive? }
|
176
|
+
if living_daemons.any?
|
177
|
+
puts "Daemons already started! PIDS: #{living_daemons.map {|d| d.pid}.join(', ')}"
|
165
178
|
exit 1
|
179
|
+
else
|
180
|
+
build(opts).map { |d| DaemonSpawn.start(d, args) }
|
166
181
|
end
|
167
|
-
end
|
182
|
+
end
|
168
183
|
|
169
184
|
def self.stop(opts, args)
|
170
185
|
daemons = find(opts)
|
@@ -186,7 +201,7 @@ module DaemonSpawn
|
|
186
201
|
end
|
187
202
|
|
188
203
|
def self.restart(opts, args)
|
189
|
-
daemons =
|
204
|
+
daemons = build(opts)
|
190
205
|
daemons.map do |daemon|
|
191
206
|
DaemonSpawn.stop(daemon)
|
192
207
|
DaemonSpawn.start(daemon, args)
|
data/test/daemon_spawn_test.rb
CHANGED
@@ -1,12 +1,28 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "helper")
|
1
2
|
require "socket"
|
2
|
-
require "test/unit"
|
3
3
|
|
4
4
|
class DaemonSpawnTest < Test::Unit::TestCase
|
5
5
|
|
6
6
|
SERVERS = File.join(File.dirname(__FILE__), "servers")
|
7
7
|
|
8
|
+
# Try to make sure no pidfile (or process) is left over from another test.
|
9
|
+
def setup
|
10
|
+
%w{ echo_server deaf_server stubborn_server }.each do |server|
|
11
|
+
begin
|
12
|
+
Process.kill 9, possible_pid(server)
|
13
|
+
rescue Errno::ESRCH
|
14
|
+
# good, no process to kill
|
15
|
+
end
|
16
|
+
begin
|
17
|
+
File.unlink pid_file(server)
|
18
|
+
rescue Errno::ENOENT
|
19
|
+
# good, no pidfile to clear
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
8
24
|
def with_socket
|
9
|
-
socket = TCPSocket.new('
|
25
|
+
socket = TCPSocket.new('127.0.0.1', 5150)
|
10
26
|
socket.setsockopt(Socket::SOL_SOCKET,
|
11
27
|
Socket::SO_RCVTIMEO,
|
12
28
|
[1, 0].pack("l_2"))
|
@@ -31,10 +47,25 @@ class DaemonSpawnTest < Test::Unit::TestCase
|
|
31
47
|
with_socket
|
32
48
|
ensure
|
33
49
|
assert_match(//, `./echo_server.rb stop`)
|
34
|
-
assert_raises(Errno::ECONNREFUSED) { TCPSocket.new('
|
50
|
+
assert_raises(Errno::ECONNREFUSED) { TCPSocket.new('127.0.0.1', 5150) }
|
35
51
|
end
|
36
52
|
end
|
37
53
|
end
|
54
|
+
|
55
|
+
def after_daemon_dies_leaving_pid_file
|
56
|
+
Dir.chdir(SERVERS) do
|
57
|
+
`./echo_server.rb stop`
|
58
|
+
sleep 1
|
59
|
+
`./echo_server.rb start 5150`
|
60
|
+
sleep 1
|
61
|
+
leftover_pid = IO.read(pid_file('echo_server')).to_i
|
62
|
+
Process.kill 9, leftover_pid
|
63
|
+
sleep 1
|
64
|
+
assert dead?(leftover_pid)
|
65
|
+
assert File.exists?(pid_file('echo_server'))
|
66
|
+
yield leftover_pid
|
67
|
+
end
|
68
|
+
end
|
38
69
|
|
39
70
|
def test_daemon_running
|
40
71
|
while_running do |socket|
|
@@ -98,5 +129,56 @@ class DaemonSpawnTest < Test::Unit::TestCase
|
|
98
129
|
end
|
99
130
|
end
|
100
131
|
end
|
132
|
+
|
133
|
+
def test_start_after_daemon_dies_leaving_pid_file
|
134
|
+
after_daemon_dies_leaving_pid_file do |leftover_pid|
|
135
|
+
assert_match /EchoServer started/, `./echo_server.rb start 5150`
|
136
|
+
sleep 1
|
137
|
+
new_pid = IO.read(pid_file('echo_server')).to_i
|
138
|
+
assert new_pid != leftover_pid
|
139
|
+
assert alive?(new_pid)
|
140
|
+
end
|
141
|
+
end
|
101
142
|
|
143
|
+
def test_restart_after_daemon_dies_leaving_pid_file
|
144
|
+
after_daemon_dies_leaving_pid_file do |leftover_pid|
|
145
|
+
assert_match /EchoServer started/, `./echo_server.rb restart 5150`
|
146
|
+
sleep 1
|
147
|
+
new_pid = reported_pid 'echo_server'
|
148
|
+
assert new_pid != leftover_pid
|
149
|
+
assert alive?(new_pid)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def test_stop_using_custom_signal
|
154
|
+
Dir.chdir(SERVERS) do
|
155
|
+
`./deaf_server.rb start`
|
156
|
+
sleep 1
|
157
|
+
pid = reported_pid 'deaf_server'
|
158
|
+
assert alive?(pid)
|
159
|
+
Process.kill 'TERM', pid
|
160
|
+
sleep 1
|
161
|
+
assert alive?(pid)
|
162
|
+
Process.kill 'INT', pid
|
163
|
+
sleep 1
|
164
|
+
assert alive?(pid)
|
165
|
+
`./deaf_server.rb stop`
|
166
|
+
sleep 1
|
167
|
+
assert dead?(pid)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def test_kill_9_following_timeout
|
172
|
+
Dir.chdir(SERVERS) do
|
173
|
+
`./stubborn_server.rb start`
|
174
|
+
sleep 1
|
175
|
+
pid = reported_pid 'stubborn_server'
|
176
|
+
assert alive?(pid)
|
177
|
+
Process.kill 'TERM', pid
|
178
|
+
sleep 1
|
179
|
+
assert alive?(pid)
|
180
|
+
`./stubborn_server.rb stop`
|
181
|
+
assert dead?(pid)
|
182
|
+
end
|
183
|
+
end
|
102
184
|
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require "test/unit"
|
2
|
+
require "tmpdir"
|
3
|
+
|
4
|
+
class Test::Unit::TestCase
|
5
|
+
def pid_file(daemon_name)
|
6
|
+
File.join Dir.tmpdir, "#{daemon_name}.pid"
|
7
|
+
end
|
8
|
+
|
9
|
+
def possible_pid(daemon_name)
|
10
|
+
`ps x | grep ruby | grep #{daemon_name} | awk '{ print $1 }'`.to_i
|
11
|
+
end
|
12
|
+
|
13
|
+
def reported_pid(daemon_name)
|
14
|
+
IO.read(pid_file(daemon_name)).to_i
|
15
|
+
end
|
16
|
+
|
17
|
+
def alive?(pid)
|
18
|
+
Process.kill 0, pid
|
19
|
+
rescue Errno::ESRCH
|
20
|
+
false
|
21
|
+
end
|
22
|
+
|
23
|
+
def dead?(pid)
|
24
|
+
not alive? pid
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift(File.join(File.dirname(__FILE__), "..", "..", "lib"))
|
4
|
+
|
5
|
+
require "daemon_spawn"
|
6
|
+
require "tmpdir"
|
7
|
+
|
8
|
+
# A server that only quits if you send it SIGWINCH
|
9
|
+
class DeafServer < DaemonSpawn::Base
|
10
|
+
def start(args)
|
11
|
+
trap('TERM') { }
|
12
|
+
trap('INT') { }
|
13
|
+
trap('SIGWINCH') { exit 0 }
|
14
|
+
loop do
|
15
|
+
sleep 100
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def stop
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
DeafServer.spawn!(:working_dir => File.join(File.dirname(__FILE__), '..', '..'),
|
24
|
+
:log_file => File.join(Dir.tmpdir, 'deaf_server.log'),
|
25
|
+
:pid_file => File.join(Dir.tmpdir, 'deaf_server.pid'),
|
26
|
+
:sync_log => true,
|
27
|
+
:singleton => true,
|
28
|
+
:signal => 'SIGWINCH')
|
data/test/servers/echo_server.rb
CHANGED
@@ -4,6 +4,7 @@ $:.unshift(File.join(File.dirname(__FILE__), "..", "..", "lib"))
|
|
4
4
|
|
5
5
|
require "daemon_spawn"
|
6
6
|
require "socket"
|
7
|
+
require "tmpdir"
|
7
8
|
|
8
9
|
# An echo server using daemon-spawn. It starts up local TCP server
|
9
10
|
# socket and repeats each line it receives on the connection. To fire
|
@@ -48,7 +49,7 @@ class EchoServer < DaemonSpawn::Base
|
|
48
49
|
end
|
49
50
|
|
50
51
|
EchoServer.spawn!(:working_dir => File.join(File.dirname(__FILE__), '..', '..'),
|
51
|
-
:log_file => '
|
52
|
-
:pid_file => '
|
52
|
+
:log_file => File.join(Dir.tmpdir, 'echo_server.log'),
|
53
|
+
:pid_file => File.join(Dir.tmpdir, 'echo_server.pid'),
|
53
54
|
:sync_log => true,
|
54
55
|
:singleton => true)
|
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift(File.join(File.dirname(__FILE__), "..", "..", "lib"))
|
4
|
+
|
5
|
+
require "daemon_spawn"
|
6
|
+
require "tmpdir"
|
7
|
+
|
8
|
+
# A server that needs to be kill -9'ed
|
9
|
+
class StubbornServer < DaemonSpawn::Base
|
10
|
+
def start(args)
|
11
|
+
trap('TERM') { }
|
12
|
+
loop do
|
13
|
+
sleep 100
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def stop
|
18
|
+
raise "You should never get to me"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
StubbornServer.spawn!(:working_dir => File.join(File.dirname(__FILE__), '..', '..'),
|
23
|
+
:log_file => File.join(Dir.tmpdir, 'stubborn_server.log'),
|
24
|
+
:pid_file => File.join(Dir.tmpdir, 'stubborn_server.pid'),
|
25
|
+
:sync_log => true,
|
26
|
+
:singleton => true,
|
27
|
+
:timeout => 2)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift(File.join(File.dirname(__FILE__), "..", "..", "lib"))
|
4
|
+
|
5
|
+
require "daemon_spawn"
|
6
|
+
|
7
|
+
class TapsServer < DaemonSpawn::Base
|
8
|
+
def start(args)
|
9
|
+
sleep 5 # Sinatra takes its sweet time to shut down
|
10
|
+
Kernel.exec "/usr/bin/taps server mysql://dbusername:dbpassword@localhost/data1_production?encoding=utf8 username password"
|
11
|
+
end
|
12
|
+
|
13
|
+
def stop
|
14
|
+
end
|
15
|
+
end
|
16
|
+
TapsServer.spawn!(:working_dir => File.join(File.dirname(__FILE__), '..', '..'),
|
17
|
+
:log_file => File.join(Dir.tmpdir, 'taps_server.log'),
|
18
|
+
:pid_file => File.join(Dir.tmpdir, 'taps_server.pid'),
|
19
|
+
:sync_log => true,
|
20
|
+
:singleton => true,
|
21
|
+
:signal => 'INT') # Sinatra ignores TERM
|
metadata
CHANGED
@@ -1,21 +1,25 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: daemon-spawn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 15
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 4
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.4.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Alex Vollmer
|
14
|
+
- Seamus Abshere
|
15
|
+
- Emmanual Gomez
|
16
|
+
- Seth Falcon
|
17
|
+
- Woody Peterson
|
14
18
|
autorequire:
|
15
19
|
bindir: bin
|
16
20
|
cert_chain: []
|
17
21
|
|
18
|
-
date: 2010-05
|
22
|
+
date: 2010-09-05 00:00:00 -07:00
|
19
23
|
default_executable:
|
20
24
|
dependencies: []
|
21
25
|
|
@@ -41,9 +45,13 @@ files:
|
|
41
45
|
- daemon-spawn.gemspec
|
42
46
|
- lib/daemon_spawn.rb
|
43
47
|
- test/daemon_spawn_test.rb
|
48
|
+
- test/helper.rb
|
44
49
|
- test/multi_daemon_spawn_test.rb
|
50
|
+
- test/servers/deaf_server.rb
|
45
51
|
- test/servers/echo_server.rb
|
46
52
|
- test/servers/simple_server.rb
|
53
|
+
- test/servers/stubborn_server.rb
|
54
|
+
- test/servers/taps_server.rb
|
47
55
|
has_rdoc: true
|
48
56
|
homepage: http://github.com/alexvollmer/daemon-spawn
|
49
57
|
licenses: []
|
@@ -80,6 +88,10 @@ specification_version: 3
|
|
80
88
|
summary: Daemon launching and management made dead simple
|
81
89
|
test_files:
|
82
90
|
- test/daemon_spawn_test.rb
|
91
|
+
- test/helper.rb
|
83
92
|
- test/multi_daemon_spawn_test.rb
|
93
|
+
- test/servers/deaf_server.rb
|
84
94
|
- test/servers/echo_server.rb
|
85
95
|
- test/servers/simple_server.rb
|
96
|
+
- test/servers/stubborn_server.rb
|
97
|
+
- test/servers/taps_server.rb
|