daemon-spawn-tanin 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,2 @@
1
+ doc
2
+ pkg
data/History.txt ADDED
@@ -0,0 +1,32 @@
1
+ === 0.4.2 / 2011-04-01
2
+
3
+ * Merged patches from Tilo Prütz
4
+
5
+ === 0.4.1 / 2011-03-03
6
+
7
+ * Merged patch from oruen to fix https://github.com/alexvollmer/daemon-spawn/issues/5
8
+
9
+ === 0.4.0 / 2010-09-05
10
+
11
+ * Lots of fixes from friendly folks
12
+
13
+ === 0.3.0 / 2010-04-18
14
+
15
+ * Typo fix and internal refactoring patches from Emmanuel Gomez
16
+ * Added multi-process spawning and management (inspired by Jontathan Tropper's
17
+ patches)
18
+ * Added LSB-compliance patches from Woody Peterson
19
+ * Added some actual test-coverage :-P
20
+ * Moved examples from "examples" directory to "test/servers"
21
+
22
+ === 0.2.0 / 2009-04-22
23
+
24
+ * Allow specification of args instead of ARGV so that scripts can
25
+ handle command line parsing and validation prior to spawning.
26
+
27
+ === 0.1.0 / 2009-01-26
28
+
29
+ * 1 major enhancement
30
+
31
+ * Happy Birthday!
32
+
data/Manifest.txt ADDED
@@ -0,0 +1,9 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ lib/daemon_spawn.rb
6
+ test/daemon_spawn_test.rb
7
+ test/multi_daemon_spawn_test.rb
8
+ test/servers/echo_server.rb
9
+ test/servers/simple_server.rb
data/README.rdoc 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,34 @@
1
+ # -*- coding: utf-8 -*-
2
+ # -*- ruby -*-
3
+
4
+ require 'rubygems'
5
+ require './lib/daemon_spawn.rb'
6
+ require 'rake/testtask'
7
+
8
+ begin
9
+ require 'jeweler'
10
+ Jeweler::Tasks.new do |gemspec|
11
+ gemspec.name = "daemon-spawn"
12
+ gemspec.summary = "Daemon launching and management made dead simple"
13
+ gemspec.description = %Q[With daemon-spawn you can start, stop and restart processes that run
14
+ in the background. Processed are tracked by a simple PID file written
15
+ to disk.]
16
+ gemspec.rubyforge_project = "daemon-spawn"
17
+ gemspec.email = "alex.vollmer@gmail.com"
18
+ gemspec.homepage = "http://github.com/alexvollmer/daemon-spawn"
19
+ gemspec.authors = ["Alex Vollmer", "Seamus Abshere", "Emmanual Gomez", "Seth Falcon", "Woody Peterson", "Tilo Prütz"]
20
+ end
21
+ Jeweler::GemcutterTasks.new
22
+ rescue LoadError
23
+ puts "Jeweler not available. Install it with: sudo gem install jeweler"
24
+ end
25
+
26
+ Rake::TestTask.new do |t|
27
+ t.libs << "test"
28
+ t.test_files = FileList['test/*test.rb']
29
+ t.verbose = true
30
+ end
31
+
32
+ desc "Run tests"
33
+ task :default => :test
34
+ # vim: syntax=Ruby
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.4.2
@@ -0,0 +1,66 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{daemon-spawn-tanin}
8
+ s.version = "0.4.3"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Alex Vollmer", "Seamus Abshere", "Emmanual Gomez", "Seth Falcon", "Woody Peterson", "Tilo Pr\303\274tz","Tanin Na Nakorn"]
12
+ s.date = %q{2011-08-24}
13
+ s.description = %q{With daemon-spawn you can start, stop and restart processes that run
14
+ in the background. Processed are tracked by a simple PID file written
15
+ to disk.}
16
+ s.email = %q{alex.vollmer@gmail.com}
17
+ s.extra_rdoc_files = [
18
+ "README.rdoc"
19
+ ]
20
+ s.files = [
21
+ ".autotest",
22
+ ".gitignore",
23
+ "History.txt",
24
+ "Manifest.txt",
25
+ "README.rdoc",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "daemon-spawn-tanin.gemspec",
29
+ "lib/daemon_spawn.rb",
30
+ "test/daemon_spawn_test.rb",
31
+ "test/test_helper.rb",
32
+ "test/multi_daemon_spawn_test.rb",
33
+ "test/servers/deaf_server.rb",
34
+ "test/servers/echo_server.rb",
35
+ "test/servers/simple_server.rb",
36
+ "test/servers/stubborn_server.rb",
37
+ "test/servers/taps_server.rb"
38
+ ]
39
+ s.homepage = %q{http://github.com/alexvollmer/daemon-spawn}
40
+ s.rdoc_options = ["--charset=UTF-8"]
41
+ s.require_paths = ["lib"]
42
+ s.rubyforge_project = %q{daemon-spawn}
43
+ s.rubygems_version = %q{1.3.7}
44
+ s.summary = %q{Daemon launching and management made dead simple}
45
+ s.test_files = [
46
+ "test/daemon_spawn_test.rb",
47
+ "test/test_helper.rb",
48
+ "test/multi_daemon_spawn_test.rb",
49
+ "test/servers/deaf_server.rb",
50
+ "test/servers/echo_server.rb",
51
+ "test/servers/simple_server.rb",
52
+ "test/servers/stubborn_server.rb",
53
+ "test/servers/taps_server.rb"
54
+ ]
55
+
56
+ if s.respond_to? :specification_version then
57
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
58
+ s.specification_version = 3
59
+
60
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
61
+ else
62
+ end
63
+ else
64
+ end
65
+ end
66
+
@@ -0,0 +1,213 @@
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.alive?(pid)
16
+ Process.kill 0, pid
17
+ rescue Errno::ESRCH
18
+ false
19
+ end
20
+
21
+ def self.start(daemon, args) #:nodoc:
22
+ if !File.writable?(File.dirname(daemon.log_file))
23
+ STDERR.puts "Unable to write log file to #{daemon.log_file}"
24
+ exit 1
25
+ end
26
+
27
+ if !File.writable?(File.dirname(daemon.pid_file))
28
+ STDERR.puts "Unable to write PID file to #{daemon.pid_file}"
29
+ exit 1
30
+ end
31
+
32
+ if daemon.alive? && daemon.singleton
33
+ STDERR.puts "An instance of #{daemon.app_name} is already " +
34
+ "running (PID #{daemon.pid})"
35
+ exit 0
36
+ end
37
+
38
+ fork do
39
+ Process.setsid
40
+ exit if fork
41
+ open(daemon.pid_file, 'w') { |f| f << Process.pid }
42
+ Dir.chdir daemon.working_dir
43
+ old_umask = File.umask 0000
44
+ log = File.new(daemon.log_file, "a")
45
+ File.umask old_umask
46
+ log.sync = daemon.sync_log
47
+ STDIN.reopen "/dev/null"
48
+ STDOUT.reopen log
49
+ STDERR.reopen STDOUT
50
+ trap("TERM") {daemon.stop; exit}
51
+ daemon.start(args)
52
+ end
53
+ puts "#{daemon.app_name} started."
54
+ end
55
+
56
+ def self.stop(daemon) #:nodoc:
57
+ if pid = daemon.pid
58
+ FileUtils.rm(daemon.pid_file)
59
+ Process.kill(daemon.signal, pid)
60
+ begin
61
+ Process.wait(pid)
62
+ rescue Errno::ECHILD
63
+ end
64
+ if ticks = daemon.timeout
65
+ while ticks > 0 and alive?(pid) do
66
+ puts "Process is still alive. #{ticks} seconds until I kill -9 it..."
67
+ sleep 1
68
+ ticks -= 1
69
+ end
70
+ if alive?(pid)
71
+ puts "Process didn't quit after timeout of #{daemon.timeout} seconds. Killing..."
72
+ Process.kill 9, pid
73
+ end
74
+ end
75
+ else
76
+ puts "PID file not found. Is the daemon started?"
77
+ end
78
+ rescue Errno::ESRCH
79
+ puts "PID file found, but process was not running. The daemon may have died."
80
+ end
81
+
82
+ def self.status(daemon) #:nodoc:
83
+ puts "#{daemon.app_name} is #{daemon.alive? ? "" : "NOT "}running (PID #{daemon.pid})"
84
+ end
85
+
86
+ class Base
87
+ attr_accessor :log_file, :pid_file, :sync_log, :working_dir, :app_name, :singleton, :index, :signal, :timeout
88
+
89
+ def initialize(opts = {})
90
+ raise 'You must specify a :working_dir' unless opts[:working_dir]
91
+ self.working_dir = opts[:working_dir]
92
+ self.app_name = opts[:application] || classname
93
+ self.pid_file = opts[:pid_file] || File.join(working_dir, 'tmp', 'pids', app_name + '.pid')
94
+ self.log_file = opts[:log_file] || File.join(working_dir, 'logs', app_name + '.log')
95
+ self.signal = opts[:signal] || 'TERM'
96
+ self.timeout = opts[:timeout]
97
+ self.index = opts[:index] || 0
98
+ if self.index > 0
99
+ self.pid_file += ".#{self.index}"
100
+ self.log_file += ".#{self.index}"
101
+ end
102
+ self.sync_log = opts[:sync_log]
103
+ self.singleton = opts[:singleton] || false
104
+ end
105
+
106
+ def classname #:nodoc:
107
+ self.class.to_s.split('::').last
108
+ end
109
+
110
+ # Provide your implementation. These are provided as a reminder
111
+ # only and will raise an error if invoked. When started, this
112
+ # method will be invoked with the remaining command-line arguments.
113
+ def start(args)
114
+ raise "You must implement a 'start' method in your class!"
115
+ end
116
+
117
+ # Provide your implementation. These are provided as a reminder
118
+ # only and will raise an error if invoked.
119
+ def stop
120
+ raise "You must implement a 'stop' method in your class!"
121
+ end
122
+
123
+ def alive? #:nodoc:
124
+ if File.file?(pid_file)
125
+ DaemonSpawn.alive? pid
126
+ else
127
+ false
128
+ end
129
+ end
130
+
131
+ def pid #:nodoc:
132
+ IO.read(self.pid_file).to_i rescue nil
133
+ end
134
+
135
+ def self.build(options)
136
+ count = options.delete(:processes) || 1
137
+ daemons = []
138
+ count.times do |index|
139
+ daemons << new(options.merge(:index => index))
140
+ end
141
+ daemons
142
+ end
143
+
144
+ def self.find(options)
145
+ pid_file = new(options).pid_file
146
+ basename = File.basename(pid_file).split('.').first
147
+ pid_files = Dir.glob(File.join(File.dirname(pid_file), "#{basename}.*pid*"))
148
+ pid_files.map { |f| new(options.merge(:pid_file => f)) }
149
+ end
150
+
151
+ # Invoke this method to process command-line args and dispatch
152
+ # appropriately. Valid options include the following _symbols_:
153
+ # - <tt>:working_dir</tt> -- the working directory (required)
154
+ # - <tt>:log_file</tt> -- path to the log file
155
+ # - <tt>:pid_file</tt> -- path to the pid file
156
+ # - <tt>:sync_log</tt> -- indicate whether or not to sync log IO
157
+ # - <tt>:singleton</tt> -- If set to true, only one instance is
158
+ # allowed to start
159
+ # args must begin with 'start', 'stop', 'status', or 'restart'.
160
+ # The first token will be removed and any remaining arguments
161
+ # passed to the daemon's start method.
162
+ def self.spawn!(opts = {}, args = ARGV)
163
+ case args.any? and command = args.shift
164
+ when 'start', 'stop', 'status', 'restart'
165
+ send(command, opts, args)
166
+ when '-h', '--help', 'help'
167
+ DaemonSpawn.usage
168
+ #exit
169
+ else
170
+ DaemonSpawn.usage "Invalid command"
171
+ #exit 1
172
+ end
173
+ end
174
+
175
+ def self.start(opts, args)
176
+ living_daemons = find(opts).select { |d| d.alive? }
177
+ if living_daemons.any?
178
+ puts "Daemons already started! PIDS: #{living_daemons.map {|d| d.pid}.join(', ')}"
179
+ #exit 1
180
+ else
181
+ build(opts).map { |d| DaemonSpawn.start(d, args) }
182
+ end
183
+ end
184
+
185
+ def self.stop(opts, args)
186
+ daemons = find(opts)
187
+ if daemons.empty?
188
+ puts "No PID files found. Is the daemon started?"
189
+ #exit 1
190
+ else
191
+ daemons.each { |d| DaemonSpawn.stop(d) }
192
+ end
193
+ end
194
+
195
+ def self.status(opts, args)
196
+ daemons = find(opts)
197
+ if daemons.empty?
198
+ puts 'No PIDs found'
199
+ else
200
+ daemons.each { |d| DaemonSpawn.status(d) }
201
+ end
202
+ end
203
+
204
+ def self.restart(opts, args)
205
+ daemons = build(opts)
206
+ daemons.map do |daemon|
207
+ DaemonSpawn.stop(daemon)
208
+ sleep 0.1
209
+ DaemonSpawn.start(daemon, args)
210
+ end
211
+ end
212
+ end
213
+ end
@@ -0,0 +1,211 @@
1
+ require 'test_helper'
2
+ require "socket"
3
+
4
+ class DaemonSpawnTest < Test::Unit::TestCase
5
+
6
+ SERVERS = File.join(File.dirname(__FILE__), "servers")
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 simple_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
+
24
+ def with_socket
25
+ socket = TCPSocket.new('127.0.0.1', 5150)
26
+ socket.setsockopt(Socket::SOL_SOCKET,
27
+ Socket::SO_RCVTIMEO,
28
+ [1, 0].pack("l_2"))
29
+
30
+ begin
31
+ yield(socket) if block_given?
32
+ ensure
33
+ socket.close
34
+ end
35
+ end
36
+
37
+ def echo_server(*args)
38
+ `./echo_server.rb #{args.join(' ')}`
39
+ end
40
+
41
+ def while_running(&block)
42
+ Dir.chdir(SERVERS) do
43
+ `./echo_server.rb stop`
44
+ assert_match(/EchoServer started./, `./echo_server.rb start 5150`)
45
+ sleep 1
46
+ begin
47
+ with_socket &block
48
+ ensure
49
+ assert_match(//, `./echo_server.rb stop`)
50
+ sleep 0.1
51
+ assert_raises(Errno::ECONNREFUSED) { TCPSocket.new('127.0.0.1', 5150) }
52
+ end
53
+ end
54
+ end
55
+
56
+ def after_daemon_dies_leaving_pid_file
57
+ Dir.chdir(SERVERS) do
58
+ `./echo_server.rb stop`
59
+ sleep 1
60
+ `./echo_server.rb start 5150`
61
+ sleep 1
62
+ leftover_pid = IO.read(pid_file('echo_server')).to_i
63
+ Process.kill 9, leftover_pid
64
+ sleep 1
65
+ assert dead?(leftover_pid)
66
+ assert File.exists?(pid_file('echo_server'))
67
+ yield leftover_pid
68
+ end
69
+ end
70
+
71
+ def test_daemon_running
72
+ while_running do |socket|
73
+ socket << "foobar\n"
74
+ assert_equal "foobar\n", socket.readline
75
+ end
76
+ end
77
+
78
+ def test_status_running
79
+ while_running do |socket|
80
+ assert_match(/EchoServer is running/, `./echo_server.rb status`)
81
+ end
82
+ end
83
+
84
+ def test_status_not_running
85
+ Dir.chdir(SERVERS) do
86
+ assert_match(/No PIDs found/, `./echo_server.rb status`)
87
+ end
88
+ end
89
+
90
+ def test_start_after_started
91
+ while_running do
92
+ pid = echo_server("status").match(/PID (\d+)/)[1]
93
+ assert_match(/Daemons already started! PIDS: #{pid}/,
94
+ echo_server("start"))
95
+ end
96
+ end
97
+
98
+ def test_stop_after_stopped
99
+ Dir.chdir(SERVERS) do
100
+ assert_match("No PID files found. Is the daemon started?",
101
+ `./echo_server.rb stop`)
102
+ end
103
+ end
104
+
105
+ def test_restart_after_stopped
106
+ Dir.chdir(SERVERS) do
107
+ assert_match(/EchoServer started/, `./echo_server.rb restart 5150`)
108
+ assert_equal(0, $?.exitstatus)
109
+ sleep 1
110
+ with_socket do |socket|
111
+ socket << "foobar\n"
112
+ assert_equal "foobar\n", socket.readline
113
+ end
114
+ end
115
+ end
116
+
117
+ def test_restart_after_started
118
+ Dir.chdir(SERVERS) do
119
+ assert_match(/EchoServer started/, `./echo_server.rb start 5150`)
120
+ assert_equal(0, $?.exitstatus)
121
+ sleep 1
122
+
123
+ assert_match(/EchoServer started/, `./echo_server.rb restart 5150`)
124
+ assert_equal(0, $?.exitstatus)
125
+ sleep 1
126
+
127
+ with_socket do |socket|
128
+ socket << "foobar\n"
129
+ assert_equal "foobar\n", socket.readline
130
+ end
131
+ end
132
+ end
133
+
134
+ def test_start_after_daemon_dies_leaving_pid_file
135
+ after_daemon_dies_leaving_pid_file do |leftover_pid|
136
+ assert_match /EchoServer started/, `./echo_server.rb start 5150`
137
+ sleep 1
138
+ new_pid = IO.read(pid_file('echo_server')).to_i
139
+ assert new_pid != leftover_pid
140
+ assert alive?(new_pid)
141
+ end
142
+ end
143
+
144
+ def test_restart_after_daemon_dies_leaving_pid_file
145
+ after_daemon_dies_leaving_pid_file do |leftover_pid|
146
+ assert_match /EchoServer started/, `./echo_server.rb restart 5150`
147
+ sleep 1
148
+ new_pid = reported_pid 'echo_server'
149
+ assert new_pid != leftover_pid
150
+ assert alive?(new_pid)
151
+ end
152
+ end
153
+
154
+ def test_stop_using_custom_signal
155
+ Dir.chdir(SERVERS) do
156
+ `./deaf_server.rb start`
157
+ sleep 1
158
+ pid = reported_pid 'deaf_server'
159
+ assert alive?(pid)
160
+ Process.kill 'TERM', pid
161
+ sleep 1
162
+ assert alive?(pid)
163
+ Process.kill 'INT', pid
164
+ sleep 1
165
+ assert alive?(pid)
166
+ `./deaf_server.rb stop`
167
+ sleep 1
168
+ assert dead?(pid)
169
+ end
170
+ end
171
+
172
+ def test_kill_9_following_timeout
173
+ Dir.chdir(SERVERS) do
174
+ `./stubborn_server.rb start`
175
+ sleep 1
176
+ pid = reported_pid 'stubborn_server'
177
+ assert alive?(pid)
178
+ Process.kill 'TERM', pid
179
+ sleep 1
180
+ assert alive?(pid)
181
+ `./stubborn_server.rb stop`
182
+ assert dead?(pid)
183
+ end
184
+ end
185
+
186
+ def test_umask_unchanged
187
+ Dir.chdir(SERVERS) do
188
+ old_umask = File.umask 0124
189
+ log_file = File.join(Dir.tmpdir, 'should_not_be_world_writable')
190
+ begin
191
+ `./simple_server.rb stop`
192
+ FileUtils.rm_f log_file
193
+ `./simple_server.rb start #{log_file}`
194
+ sleep 1
195
+ assert_equal 0100642, File.stat(log_file).mode
196
+ ensure
197
+ `./simple_server.rb stop`
198
+ FileUtils.rm_f log_file
199
+ File.umask old_umask
200
+ end
201
+ end
202
+ end
203
+
204
+ def test_log_file_is_world_writable
205
+ log_file = File.join(Dir.tmpdir, 'echo_server.log')
206
+ FileUtils.rm_f log_file
207
+ while_running do
208
+ assert_equal 0100666, File.stat(log_file).mode
209
+ end
210
+ end
211
+ end
@@ -0,0 +1,111 @@
1
+ require 'test_helper'
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,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')
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift(File.join(File.dirname(__FILE__), "..", "..", "lib"))
4
+
5
+ require "daemon_spawn"
6
+ require "socket"
7
+ require "tmpdir"
8
+
9
+ # An echo server using daemon-spawn. It starts up local TCP server
10
+ # socket and repeats each line it receives on the connection. To fire
11
+ # it up run:
12
+ # ./echo_server.rb start 12345
13
+ # Then connect to it using telnet to test it:
14
+ # telnet localhost 12345
15
+ # > howdy!
16
+ # howdy!
17
+ # to shut the daemon down, go back to the command-line and run:
18
+ # ./echo_server.rb stop
19
+ class EchoServer < DaemonSpawn::Base
20
+
21
+ attr_accessor :server_socket
22
+
23
+ def start(args)
24
+ port = args.empty? ? 0 : args.first.to_i
25
+ self.server_socket = TCPServer.new('127.0.0.1', port)
26
+ self.server_socket.setsockopt(Socket::SOL_SOCKET,
27
+ Socket::SO_REUSEADDR,
28
+ true)
29
+ port = self.server_socket.addr[1]
30
+ puts "EchoServer started on port #{port}"
31
+ loop do
32
+ begin
33
+ client = self.server_socket.accept
34
+ puts "Got a connection from #{client}"
35
+ while str = client.gets
36
+ client.write(str)
37
+ puts "Echoed '#{str}' to #{client}"
38
+ end
39
+ rescue Errno::ECONNRESET => e
40
+ STDERR.puts "Client reset connection"
41
+ end
42
+ end
43
+ end
44
+
45
+ def stop
46
+ puts "Stopping EchoServer..."
47
+ self.server_socket.close if self.server_socket
48
+ end
49
+ end
50
+
51
+ EchoServer.spawn!(:working_dir => File.join(File.dirname(__FILE__), '..', '..'),
52
+ :log_file => File.join(Dir.tmpdir, 'echo_server.log'),
53
+ :pid_file => File.join(Dir.tmpdir, 'echo_server.pid'),
54
+ :sync_log => true,
55
+ :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)
@@ -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
@@ -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
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: daemon-spawn-tanin
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.3
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Alex Vollmer
9
+ - Seamus Abshere
10
+ - Emmanual Gomez
11
+ - Seth Falcon
12
+ - Woody Peterson
13
+ - Tilo Prütz
14
+ - Tanin Na Nakorn
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+ date: 2011-08-24 00:00:00.000000000Z
19
+ dependencies: []
20
+ description: ! "With daemon-spawn you can start, stop and restart processes that run\n
21
+ \ in the background. Processed are tracked by a simple PID file written\n to
22
+ disk."
23
+ email: alex.vollmer@gmail.com
24
+ executables: []
25
+ extensions: []
26
+ extra_rdoc_files:
27
+ - README.rdoc
28
+ files:
29
+ - .autotest
30
+ - .gitignore
31
+ - History.txt
32
+ - Manifest.txt
33
+ - README.rdoc
34
+ - Rakefile
35
+ - VERSION
36
+ - daemon-spawn-tanin.gemspec
37
+ - lib/daemon_spawn.rb
38
+ - test/daemon_spawn_test.rb
39
+ - test/test_helper.rb
40
+ - test/multi_daemon_spawn_test.rb
41
+ - test/servers/deaf_server.rb
42
+ - test/servers/echo_server.rb
43
+ - test/servers/simple_server.rb
44
+ - test/servers/stubborn_server.rb
45
+ - test/servers/taps_server.rb
46
+ homepage: http://github.com/alexvollmer/daemon-spawn
47
+ licenses: []
48
+ post_install_message:
49
+ rdoc_options:
50
+ - --charset=UTF-8
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ! '>='
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ! '>='
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ requirements: []
66
+ rubyforge_project: daemon-spawn
67
+ rubygems_version: 1.8.10
68
+ signing_key:
69
+ specification_version: 3
70
+ summary: Daemon launching and management made dead simple
71
+ test_files:
72
+ - test/daemon_spawn_test.rb
73
+ - test/test_helper.rb
74
+ - test/multi_daemon_spawn_test.rb
75
+ - test/servers/deaf_server.rb
76
+ - test/servers/echo_server.rb
77
+ - test/servers/simple_server.rb
78
+ - test/servers/stubborn_server.rb
79
+ - test/servers/taps_server.rb