seamusabshere-daemon-spawn 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +16 -0
- data/.gitignore +2 -0
- data/History.txt +20 -0
- data/Manifest.txt +9 -0
- data/README.txt +107 -0
- data/Rakefile +18 -0
- data/VERSION +1 -0
- data/daemon-spawn.gemspec +42 -0
- data/lib/daemon_spawn.rb +197 -0
- data/test/daemon_spawn_test.rb +102 -0
- data/test/multi_daemon_spawn_test.rb +111 -0
- data/test/servers/echo_server.rb +54 -0
- data/test/servers/simple_server.rb +34 -0
- metadata +77 -0
data/.autotest
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
Autotest.add_hook(:initialize) do |at|
|
2
|
+
at.clear_mappings
|
3
|
+
|
4
|
+
at.add_mapping(/.*flymake/) do |f, _|
|
5
|
+
[]
|
6
|
+
end
|
7
|
+
|
8
|
+
at.add_mapping(%r[lib/daemon-spawn.rb]) do |f, _|
|
9
|
+
at.files_matching /^test\/.*_test\.rb$/
|
10
|
+
end
|
11
|
+
|
12
|
+
at.add_mapping(/^test\/.*_test\.rb$/) do |filename, _|
|
13
|
+
filename
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
data/.gitignore
ADDED
data/History.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
=== 0.3.0 / 2010-04-18
|
2
|
+
|
3
|
+
* Typo fix and internal refactoring patches from Emmanuel Gomez
|
4
|
+
* Added multi-process spawning and management (inspired by Jontathan Tropper's
|
5
|
+
patches)
|
6
|
+
* Added LSB-compliance patches from Woody Peterson
|
7
|
+
* Added some actual test-coverage :-P
|
8
|
+
* Moved examples from "examples" directory to "test/servers"
|
9
|
+
|
10
|
+
=== 0.2.0 / 2009-04-22
|
11
|
+
|
12
|
+
* Allow specification of args instead of ARGV so that scripts can
|
13
|
+
handle command line parsing and validation prior to spawning.
|
14
|
+
|
15
|
+
=== 0.1.0 / 2009-01-26
|
16
|
+
|
17
|
+
* 1 major enhancement
|
18
|
+
|
19
|
+
* Happy Birthday!
|
20
|
+
|
data/Manifest.txt
ADDED
data/README.txt
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
= daemon-spawn
|
2
|
+
|
3
|
+
* http://github.com/alexvollmer/daemon-spawn
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
Daemon launching and management made dead simple.
|
8
|
+
|
9
|
+
With daemon-spawn you can start, stop and restart processes that run
|
10
|
+
in the background. Processed are tracked by a simple PID file written
|
11
|
+
to disk.
|
12
|
+
|
13
|
+
In addition, you can choose to either execute ruby in your daemonized
|
14
|
+
process or 'exec' another process altogether (handy for wrapping other
|
15
|
+
services).
|
16
|
+
|
17
|
+
== SYNOPSIS:
|
18
|
+
|
19
|
+
=== WRITING A DAEMON:
|
20
|
+
|
21
|
+
To create a new spawner, write a class that extends <tt>DaemonSpawn::Base</tt>
|
22
|
+
and provides +start+ and +stop+ methods. For example:
|
23
|
+
|
24
|
+
class MyServer < DaemonSpawn::Base
|
25
|
+
|
26
|
+
def start(args)
|
27
|
+
# process command-line args
|
28
|
+
# start your bad self
|
29
|
+
end
|
30
|
+
|
31
|
+
def stop
|
32
|
+
# stop your bad self
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
MyServer.spawn!(:log_file => '/var/log/echo_server.log',
|
37
|
+
:pid_file => '/var/run/echo_server.pid',
|
38
|
+
:sync_log => true,
|
39
|
+
:working_dir => File.dirname(__FILE__))
|
40
|
+
|
41
|
+
If you need command-line parameters, any arguments passed after one of
|
42
|
+
the commands (start, stop, status or restart) will be passed to the
|
43
|
+
+start+ method.
|
44
|
+
|
45
|
+
The <tt>spawn!</tt> method takes a hash of symbolized keys. At a minimum you
|
46
|
+
_must_ specify the <tt>:working_dir</tt> option. You can also override
|
47
|
+
the default locations for the log and PID files.
|
48
|
+
|
49
|
+
If you pass a <tt>:processes</tt> option to the <tt>spawn!</tt>,
|
50
|
+
daemon spawn will start that number of processes.
|
51
|
+
|
52
|
+
See the <tt>test/servers</tt> directory for working examples.
|
53
|
+
|
54
|
+
=== RUNNING A DAEMON:
|
55
|
+
|
56
|
+
Let's say that you have the example script listed above in
|
57
|
+
<tt>bin/my_server</tt>. Here are the commands for starting, querying
|
58
|
+
the status, restarting and stopping the daemon:
|
59
|
+
|
60
|
+
bin/my_server start
|
61
|
+
bin/my_server status
|
62
|
+
bin/my_server restart
|
63
|
+
bin/my_server stop
|
64
|
+
|
65
|
+
Note that if any additional arguments are passed to either
|
66
|
+
<tt>start</tt> or <tt>restart</tt> those will be passed to the +start+
|
67
|
+
method of an instance of your daemon class.
|
68
|
+
|
69
|
+
|
70
|
+
== REQUIREMENTS:
|
71
|
+
|
72
|
+
None!
|
73
|
+
|
74
|
+
== CONTRIBUTIONS:
|
75
|
+
|
76
|
+
Feel free to fork this project and send me pull requests with any
|
77
|
+
changes that you have. Please note that I won't accept any patches
|
78
|
+
with significant formatting changes or ones without tests.
|
79
|
+
|
80
|
+
== INSTALL:
|
81
|
+
|
82
|
+
* sudo gem install daemon-spawn
|
83
|
+
|
84
|
+
== LICENSE:
|
85
|
+
|
86
|
+
(The MIT License)
|
87
|
+
|
88
|
+
Copyright (c) 2009 Evri, Inc.
|
89
|
+
|
90
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
91
|
+
a copy of this software and associated documentation files (the
|
92
|
+
'Software'), to deal in the Software without restriction, including
|
93
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
94
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
95
|
+
permit persons to whom the Software is furnished to do so, subject to
|
96
|
+
the following conditions:
|
97
|
+
|
98
|
+
The above copyright notice and this permission notice shall be
|
99
|
+
included in all copies or substantial portions of the Software.
|
100
|
+
|
101
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
102
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
103
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
104
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
105
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
106
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
107
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'jeweler'
|
5
|
+
Jeweler::Tasks.new do |gemspec|
|
6
|
+
gemspec.name = "seamusabshere-daemon-spawn"
|
7
|
+
gemspec.summary = "A simple, flexible daemon management library. (re-released by Seamus Abshere)"
|
8
|
+
gemspec.description = %{gem 0.3.0 re-release... originally at http://github.com/alexvollmer/daemon-spawn}
|
9
|
+
gemspec.email = "seamus@abshere.net"
|
10
|
+
gemspec.homepage = "http://github.com/seamusabshere/daemon-spawn"
|
11
|
+
gemspec.authors = ["Alex Vollmer"]
|
12
|
+
end
|
13
|
+
Jeweler::GemcutterTasks.new
|
14
|
+
rescue LoadError
|
15
|
+
puts "Jeweler not available. Install it with: gem install jeweler"
|
16
|
+
end
|
17
|
+
|
18
|
+
# vim: syntax=Ruby
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.1
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{daemon-spawn}
|
5
|
+
s.version = "0.2.0"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Alex Vollmer"]
|
9
|
+
s.date = %q{2009-09-21}
|
10
|
+
s.description = %q{Daemon launching and management made dead simple.
|
11
|
+
|
12
|
+
With daemon-spawn you can start, stop and restart processes that run
|
13
|
+
in the background. Processed are tracked by a simple PID file written
|
14
|
+
to disk.
|
15
|
+
|
16
|
+
In addition, you can choose to either execute ruby in your daemonized
|
17
|
+
process or 'exec' another process altogether (handy for wrapping other
|
18
|
+
services).}
|
19
|
+
s.email = ["alex.vollmer@gmail.com"]
|
20
|
+
s.extra_rdoc_files = ["History.txt", "Manifest.txt", "README.txt"]
|
21
|
+
s.files = ["History.txt", "Manifest.txt", "README.txt", "Rakefile", "lib/daemon-spawn.rb", "test/test_daemon-spawn.rb", "examples/echo_server.rb"]
|
22
|
+
s.homepage = %q{http://github.com/alexvollmer/daemon-spawn}
|
23
|
+
s.rdoc_options = ["--main", "README.txt"]
|
24
|
+
s.require_paths = ["lib"]
|
25
|
+
s.rubyforge_project = %q{daemon-spawn}
|
26
|
+
s.rubygems_version = %q{1.3.5}
|
27
|
+
s.summary = %q{Daemon launching and management made dead simple}
|
28
|
+
s.test_files = ["test/test_daemon-spawn.rb"]
|
29
|
+
|
30
|
+
if s.respond_to? :specification_version then
|
31
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
32
|
+
s.specification_version = 3
|
33
|
+
|
34
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
35
|
+
s.add_development_dependency(%q<hoe>, [">= 2.3.3"])
|
36
|
+
else
|
37
|
+
s.add_dependency(%q<hoe>, [">= 2.3.3"])
|
38
|
+
end
|
39
|
+
else
|
40
|
+
s.add_dependency(%q<hoe>, [">= 2.3.3"])
|
41
|
+
end
|
42
|
+
end
|
data/lib/daemon_spawn.rb
ADDED
@@ -0,0 +1,197 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
# Large portions of this were liberally stolen from the
|
4
|
+
# 'simple-daemon' project at http://simple-daemon.rubyforge.org/
|
5
|
+
module DaemonSpawn
|
6
|
+
VERSION = '0.3.0'
|
7
|
+
|
8
|
+
def self.usage(msg=nil) #:nodoc:
|
9
|
+
print "#{msg}, " if msg
|
10
|
+
puts "usage: #{$0} <command> [options]"
|
11
|
+
puts "Where <command> is one of start, stop, restart or status"
|
12
|
+
puts "[options] are additional options passed to the underlying process"
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.start(daemon, args) #:nodoc:
|
16
|
+
if !File.writable?(File.dirname(daemon.log_file))
|
17
|
+
STDERR.puts "Unable to write log file to #{daemon.log_file}"
|
18
|
+
exit 1
|
19
|
+
end
|
20
|
+
|
21
|
+
if !File.writable?(File.dirname(daemon.pid_file))
|
22
|
+
STDERR.puts "Unable to write PID file to #{daemon.pid_file}"
|
23
|
+
exit 1
|
24
|
+
end
|
25
|
+
|
26
|
+
if daemon.alive? && daemon.singleton
|
27
|
+
STDERR.puts "An instance of #{daemon.app_name} is already " +
|
28
|
+
"running (PID #{daemon.pid})"
|
29
|
+
exit 0
|
30
|
+
end
|
31
|
+
|
32
|
+
fork do
|
33
|
+
Process.setsid
|
34
|
+
exit if fork
|
35
|
+
open(daemon.pid_file, 'w') { |f| f << Process.pid }
|
36
|
+
Dir.chdir daemon.working_dir
|
37
|
+
File.umask 0000
|
38
|
+
log = File.new(daemon.log_file, "a")
|
39
|
+
log.sync = daemon.sync_log
|
40
|
+
STDIN.reopen "/dev/null"
|
41
|
+
STDOUT.reopen log
|
42
|
+
STDERR.reopen STDOUT
|
43
|
+
trap("TERM") {daemon.stop; exit}
|
44
|
+
daemon.start(args)
|
45
|
+
end
|
46
|
+
puts "#{daemon.app_name} started."
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.stop(daemon) #:nodoc:
|
50
|
+
if pid = daemon.pid
|
51
|
+
FileUtils.rm(daemon.pid_file)
|
52
|
+
Process.kill("TERM", pid)
|
53
|
+
begin
|
54
|
+
Process.wait(pid)
|
55
|
+
rescue Errno::ECHILD
|
56
|
+
end
|
57
|
+
else
|
58
|
+
puts "PID file not found. Is the daemon started?"
|
59
|
+
end
|
60
|
+
rescue Errno::ESRCH
|
61
|
+
puts "PID file found, but process was not running. The daemon may have died."
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.status(daemon) #:nodoc:
|
65
|
+
puts "#{daemon.app_name} is #{daemon.alive? ? "" : "NOT "}running (PID #{daemon.pid})"
|
66
|
+
end
|
67
|
+
|
68
|
+
class Base
|
69
|
+
attr_accessor :log_file, :pid_file, :sync_log, :working_dir, :app_name, :singleton, :index
|
70
|
+
|
71
|
+
def initialize(opts = {})
|
72
|
+
raise 'You must specify a :working_dir' unless opts[:working_dir]
|
73
|
+
self.working_dir = opts[:working_dir]
|
74
|
+
self.app_name = opts[:application] || classname
|
75
|
+
self.pid_file = opts[:pid_file] || File.join(working_dir, 'tmp', 'pids', app_name + extension)
|
76
|
+
self.log_file = opts[:log_file] || File.join(working_dir, 'logs', app_name + '.log')
|
77
|
+
self.index = opts[:index] || 0
|
78
|
+
if self.index > 0
|
79
|
+
self.pid_file += ".#{self.index}"
|
80
|
+
self.log_file += ".#{self.index}"
|
81
|
+
end
|
82
|
+
self.sync_log = opts[:sync_log]
|
83
|
+
self.singleton = opts[:singleton] || false
|
84
|
+
end
|
85
|
+
|
86
|
+
def classname #:nodoc:
|
87
|
+
self.class.to_s.split('::').last
|
88
|
+
end
|
89
|
+
|
90
|
+
# Provide your implementation. These are provided as a reminder
|
91
|
+
# only and will raise an error if invoked. When started, this
|
92
|
+
# method will be invoked with the remaining command-line arguments.
|
93
|
+
def start(args)
|
94
|
+
raise "You must implement a 'start' method in your class!"
|
95
|
+
end
|
96
|
+
|
97
|
+
# Provide your implementation. These are provided as a reminder
|
98
|
+
# only and will raise an error if invoked.
|
99
|
+
def stop
|
100
|
+
raise "You must implement a 'stop' method in your class!"
|
101
|
+
end
|
102
|
+
|
103
|
+
def alive? #:nodoc:
|
104
|
+
if File.file?(self.pid_file)
|
105
|
+
begin
|
106
|
+
Process.kill(0, self.pid)
|
107
|
+
rescue Errno::ESRCH, ::Exception
|
108
|
+
false
|
109
|
+
end
|
110
|
+
else
|
111
|
+
false
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def pid #:nodoc:
|
116
|
+
IO.read(self.pid_file).to_i rescue nil
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.build(options)
|
120
|
+
count = options.delete(:processes) || 1
|
121
|
+
daemons = []
|
122
|
+
count.times do |index|
|
123
|
+
daemons << new(options.merge(:index => index))
|
124
|
+
end
|
125
|
+
daemons
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.find(options)
|
129
|
+
pid_file = new(options).pid_file
|
130
|
+
basename = File.basename(pid_file).split('.').first
|
131
|
+
pid_files = Dir.glob(File.join(File.dirname(pid_file), "#{basename}.*pid*"))
|
132
|
+
pid_files.map { |f| new(options.merge(:pid_file => f)) }
|
133
|
+
end
|
134
|
+
|
135
|
+
# Invoke this method to process command-line args and dispatch
|
136
|
+
# appropriately. Valid options include the following _symbols_:
|
137
|
+
# - <tt>:working_dir</tt> -- the working directory (required)
|
138
|
+
# - <tt>:log_file</tt> -- path to the log file
|
139
|
+
# - <tt>:pid_file</tt> -- path to the pid file
|
140
|
+
# - <tt>:sync_log</tt> -- indicate whether or not to sync log IO
|
141
|
+
# - <tt>:singleton</tt> -- If set to true, only one instance is
|
142
|
+
# allowed to start
|
143
|
+
# args must begin with 'start', 'stop', 'status', or 'restart'.
|
144
|
+
# The first token will be removed and any remaining arguments
|
145
|
+
# passed to the daemon's start method.
|
146
|
+
def self.spawn!(opts = {}, args = ARGV)
|
147
|
+
case args.any? and command = args.shift
|
148
|
+
when 'start', 'stop', 'status', 'restart'
|
149
|
+
send(command, opts, args)
|
150
|
+
when '-h', '--help', 'help'
|
151
|
+
DaemonSpawn.usage
|
152
|
+
exit
|
153
|
+
else
|
154
|
+
DaemonSpawn.usage "Invalid command"
|
155
|
+
exit 1
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def self.start(opts, args)
|
160
|
+
daemons = find(opts)
|
161
|
+
if daemons.empty?
|
162
|
+
daemons = build(opts)
|
163
|
+
daemons.map { |d| DaemonSpawn.start(d, args) }
|
164
|
+
else
|
165
|
+
puts "Daemons already started! PIDS: #{daemons.map {|d| d.pid}.join(', ')}"
|
166
|
+
exit 1
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def self.stop(opts, args)
|
171
|
+
daemons = find(opts)
|
172
|
+
if daemons.empty?
|
173
|
+
puts "No PID files found. Is the daemon started?"
|
174
|
+
exit 1
|
175
|
+
else
|
176
|
+
daemons.each { |d| DaemonSpawn.stop(d) }
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def self.status(opts, args)
|
181
|
+
daemons = find(opts)
|
182
|
+
if daemons.empty?
|
183
|
+
puts 'No PIDs found'
|
184
|
+
else
|
185
|
+
daemons.each { |d| DaemonSpawn.status(d) }
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def self.restart(opts, args)
|
190
|
+
daemons = find(opts)
|
191
|
+
daemons.map do |daemon|
|
192
|
+
DaemonSpawn.stop(daemon)
|
193
|
+
DaemonSpawn.start(daemon, args)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require "socket"
|
2
|
+
require "test/unit"
|
3
|
+
|
4
|
+
class DaemonSpawnTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
SERVERS = File.join(File.dirname(__FILE__), "servers")
|
7
|
+
|
8
|
+
def with_socket
|
9
|
+
socket = TCPSocket.new('localhost', 5150)
|
10
|
+
socket.setsockopt(Socket::SOL_SOCKET,
|
11
|
+
Socket::SO_RCVTIMEO,
|
12
|
+
[1, 0].pack("l_2"))
|
13
|
+
|
14
|
+
begin
|
15
|
+
yield(socket) if block_given?
|
16
|
+
ensure
|
17
|
+
socket.close
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def echo_server(*args)
|
22
|
+
`./echo_server.rb #{args.join(' ')}`
|
23
|
+
end
|
24
|
+
|
25
|
+
def while_running
|
26
|
+
Dir.chdir(SERVERS) do
|
27
|
+
`./echo_server.rb stop`
|
28
|
+
assert_match(/EchoServer started./, `./echo_server.rb start 5150`)
|
29
|
+
sleep 1
|
30
|
+
begin
|
31
|
+
with_socket
|
32
|
+
ensure
|
33
|
+
assert_match(//, `./echo_server.rb stop`)
|
34
|
+
assert_raises(Errno::ECONNREFUSED) { TCPSocket.new('localhost', 5150) }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_daemon_running
|
40
|
+
while_running do |socket|
|
41
|
+
socket << "foobar\n"
|
42
|
+
assert_equal "foobar\n", socket.readline
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_status_running
|
47
|
+
while_running do |socket|
|
48
|
+
assert_match(/EchoServer is running/, `./echo_server.rb status`)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_status_not_running
|
53
|
+
Dir.chdir(SERVERS) do
|
54
|
+
assert_match(/No PIDs found/, `./echo_server.rb status`)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_start_after_started
|
59
|
+
while_running do
|
60
|
+
pid = echo_server("status").match(/PID (\d+)/)[1]
|
61
|
+
assert_match(/Daemons already started! PIDS: #{pid}/,
|
62
|
+
echo_server("start"))
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_stop_after_stopped
|
67
|
+
Dir.chdir(SERVERS) do
|
68
|
+
assert_match("No PID files found. Is the daemon started?",
|
69
|
+
`./echo_server.rb stop`)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_restart_after_stopped
|
74
|
+
Dir.chdir(SERVERS) do
|
75
|
+
assert_match(/EchoServer started/, `./echo_server.rb restart 5150`)
|
76
|
+
assert_equal(0, $?.exitstatus)
|
77
|
+
sleep 1
|
78
|
+
with_socket do |socket|
|
79
|
+
socket << "foobar\n"
|
80
|
+
assert_equal "foobar\n", socket.readline
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_restart_after_started
|
86
|
+
Dir.chdir(SERVERS) do
|
87
|
+
assert_match(/EchoServer started/, `./echo_server.rb start 5150`)
|
88
|
+
assert_equal(0, $?.exitstatus)
|
89
|
+
sleep 1
|
90
|
+
|
91
|
+
assert_match(/EchoServer started/, `./echo_server.rb restart 5150`)
|
92
|
+
assert_equal(0, $?.exitstatus)
|
93
|
+
sleep 1
|
94
|
+
|
95
|
+
with_socket do |socket|
|
96
|
+
socket << "foobar\n"
|
97
|
+
assert_equal "foobar\n", socket.readline
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require "test/unit"
|
2
|
+
require "tempfile"
|
3
|
+
|
4
|
+
class MultiDaemonSpawnTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
SERVERS = File.join(File.dirname(__FILE__), "servers")
|
7
|
+
|
8
|
+
def setup
|
9
|
+
@tmpfile = Tempfile.new("multi_daemon_spawn_test")
|
10
|
+
end
|
11
|
+
|
12
|
+
def tear_down
|
13
|
+
@tmpfile.delete
|
14
|
+
end
|
15
|
+
|
16
|
+
def simple_server(*args)
|
17
|
+
`./simple_server.rb #{args.join(" ")}`
|
18
|
+
end
|
19
|
+
|
20
|
+
def current_pids
|
21
|
+
regexp = /SimpleServer is running \(PID (\d+)\)/
|
22
|
+
pids = simple_server("status").split("\n").map do |line|
|
23
|
+
if m = regexp.match(line)
|
24
|
+
m[1]
|
25
|
+
else
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
end.compact
|
29
|
+
end
|
30
|
+
|
31
|
+
def while_running
|
32
|
+
Dir.chdir(SERVERS) do
|
33
|
+
simple_server "stop"
|
34
|
+
simple_server "start", @tmpfile.path
|
35
|
+
sleep 1
|
36
|
+
begin
|
37
|
+
yield if block_given?
|
38
|
+
ensure
|
39
|
+
simple_server "stop"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_start_multiple
|
45
|
+
while_running do
|
46
|
+
lines = open(@tmpfile.path).readlines
|
47
|
+
assert_equal 2, lines.size
|
48
|
+
assert lines.member?("SimpleServer (0) started\n")
|
49
|
+
assert lines.member?("SimpleServer (1) started\n")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_status_multiple
|
54
|
+
while_running do
|
55
|
+
lines = simple_server("status").split("\n")
|
56
|
+
lines.each do |line|
|
57
|
+
assert_match /SimpleServer is running/, line
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_stop_multiple
|
63
|
+
while_running
|
64
|
+
Dir.chdir(SERVERS) do
|
65
|
+
assert_match /No PIDs found/, simple_server("status")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_restart_multiple
|
70
|
+
while_running do
|
71
|
+
pids = current_pids
|
72
|
+
simple_server "restart"
|
73
|
+
new_pids = current_pids
|
74
|
+
assert_not_equal pids.sort, new_pids.sort
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_status_with_one_dead_process
|
79
|
+
while_running do
|
80
|
+
pids = current_pids
|
81
|
+
Process.kill(9, pids[0].to_i)
|
82
|
+
|
83
|
+
lines = simple_server("status").split("\n")
|
84
|
+
assert_equal 2, lines.size
|
85
|
+
assert lines.member?("SimpleServer is NOT running (PID #{pids[0]})")
|
86
|
+
assert lines.member?("SimpleServer is running (PID #{pids[1]})")
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_restart_with_one_dead_process
|
91
|
+
while_running do
|
92
|
+
pids = current_pids
|
93
|
+
Process.kill(9, pids[0].to_i)
|
94
|
+
|
95
|
+
lines = simple_server("restart").split("\n")
|
96
|
+
assert lines.member?("PID file found, but process was not running. The daemon may have died."), lines.inspect
|
97
|
+
assert_equal 2, lines.select { |l| l == "SimpleServer started." }.size
|
98
|
+
|
99
|
+
new_pids = current_pids
|
100
|
+
assert_not_equal new_pids, pids
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_start_after_started
|
105
|
+
while_running do
|
106
|
+
pids = current_pids
|
107
|
+
assert_match(/Daemons already started! PIDS: #{pids.join(', ')}/,
|
108
|
+
simple_server("start"))
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift(File.join(File.dirname(__FILE__), "..", "..", "lib"))
|
4
|
+
|
5
|
+
require "daemon_spawn"
|
6
|
+
require "socket"
|
7
|
+
|
8
|
+
# An echo server using daemon-spawn. It starts up local TCP server
|
9
|
+
# socket and repeats each line it receives on the connection. To fire
|
10
|
+
# it up run:
|
11
|
+
# ./echo_server.rb start 12345
|
12
|
+
# Then connect to it using telnet to test it:
|
13
|
+
# telnet localhost 12345
|
14
|
+
# > howdy!
|
15
|
+
# howdy!
|
16
|
+
# to shut the daemon down, go back to the command-line and run:
|
17
|
+
# ./echo_server.rb stop
|
18
|
+
class EchoServer < DaemonSpawn::Base
|
19
|
+
|
20
|
+
attr_accessor :server_socket
|
21
|
+
|
22
|
+
def start(args)
|
23
|
+
port = args.empty? ? 0 : args.first.to_i
|
24
|
+
self.server_socket = TCPServer.new('127.0.0.1', port)
|
25
|
+
self.server_socket.setsockopt(Socket::SOL_SOCKET,
|
26
|
+
Socket::SO_REUSEADDR,
|
27
|
+
true)
|
28
|
+
port = self.server_socket.addr[1]
|
29
|
+
puts "EchoServer started on port #{port}"
|
30
|
+
loop do
|
31
|
+
begin
|
32
|
+
client = self.server_socket.accept
|
33
|
+
puts "Got a connection from #{client}"
|
34
|
+
while str = client.gets
|
35
|
+
client.write(str)
|
36
|
+
puts "Echoed '#{str}' to #{client}"
|
37
|
+
end
|
38
|
+
rescue Errno::ECONNRESET => e
|
39
|
+
STDERR.puts "Client reset connection"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def stop
|
45
|
+
puts "Stopping EchoServer..."
|
46
|
+
self.server_socket.close if self.server_socket
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
EchoServer.spawn!(:working_dir => File.join(File.dirname(__FILE__), '..', '..'),
|
51
|
+
:log_file => '/tmp/echo_server.log',
|
52
|
+
:pid_file => '/tmp/echo_server.pid',
|
53
|
+
:sync_log => true,
|
54
|
+
:singleton => true)
|
@@ -0,0 +1,34 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift(File.join(File.dirname(__FILE__), "..", "..", "lib"))
|
4
|
+
|
5
|
+
require "daemon_spawn"
|
6
|
+
|
7
|
+
class SimpleServer < DaemonSpawn::Base
|
8
|
+
|
9
|
+
attr_accessor :outfile
|
10
|
+
|
11
|
+
def start(args)
|
12
|
+
abort "USAGE: phrase_server.rb LOGFILE" if args.empty?
|
13
|
+
@outfile = args.first
|
14
|
+
self.puts "SimpleServer (#{self.index}) started"
|
15
|
+
while true # keep running like a real daemon
|
16
|
+
sleep 5
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def puts(str)
|
21
|
+
open(@outfile, "a") { |f| f.puts str }
|
22
|
+
end
|
23
|
+
|
24
|
+
def stop
|
25
|
+
self.puts "SimpleServer (#{self.index}) stopped"
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
SimpleServer.spawn!(:working_dir => File.join(File.dirname(__FILE__), '..', '..'),
|
31
|
+
:log_file => '/tmp/simple_server.log',
|
32
|
+
:pid_file => '/tmp/simple_server.pid',
|
33
|
+
:sync_log => true,
|
34
|
+
:processes => 2)
|
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: seamusabshere-daemon-spawn
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 2
|
8
|
+
- 1
|
9
|
+
version: 0.2.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Alex Vollmer
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-05-20 00:00:00 -04:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: gem 0.3.0 re-release... originally at http://github.com/alexvollmer/daemon-spawn
|
22
|
+
email: seamus@abshere.net
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files:
|
28
|
+
- README.txt
|
29
|
+
files:
|
30
|
+
- .autotest
|
31
|
+
- .gitignore
|
32
|
+
- History.txt
|
33
|
+
- Manifest.txt
|
34
|
+
- README.txt
|
35
|
+
- Rakefile
|
36
|
+
- VERSION
|
37
|
+
- daemon-spawn.gemspec
|
38
|
+
- lib/daemon_spawn.rb
|
39
|
+
- test/daemon_spawn_test.rb
|
40
|
+
- test/multi_daemon_spawn_test.rb
|
41
|
+
- test/servers/echo_server.rb
|
42
|
+
- test/servers/simple_server.rb
|
43
|
+
has_rdoc: true
|
44
|
+
homepage: http://github.com/seamusabshere/daemon-spawn
|
45
|
+
licenses: []
|
46
|
+
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options:
|
49
|
+
- --charset=UTF-8
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
segments:
|
57
|
+
- 0
|
58
|
+
version: "0"
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
segments:
|
64
|
+
- 0
|
65
|
+
version: "0"
|
66
|
+
requirements: []
|
67
|
+
|
68
|
+
rubyforge_project:
|
69
|
+
rubygems_version: 1.3.6
|
70
|
+
signing_key:
|
71
|
+
specification_version: 3
|
72
|
+
summary: A simple, flexible daemon management library. (re-released by Seamus Abshere)
|
73
|
+
test_files:
|
74
|
+
- test/daemon_spawn_test.rb
|
75
|
+
- test/multi_daemon_spawn_test.rb
|
76
|
+
- test/servers/echo_server.rb
|
77
|
+
- test/servers/simple_server.rb
|