daemon-spawn-tanin 0.4.3

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/.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