process-daemon 0.3.0 → 0.5.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.
- checksums.yaml +4 -4
- data/lib/process/daemon.rb +26 -10
- data/lib/process/daemon/controller.rb +89 -80
- data/lib/process/daemon/priviledges.rb +0 -4
- data/lib/process/daemon/version.rb +1 -1
- data/process-daemon.gemspec +4 -2
- data/test/test_daemon.rb +20 -0
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7db4156d137b48f2986d06e6dfd5f1b5150d8afb
|
4
|
+
data.tar.gz: fec53d11a1bea87a073f68f9829edbfbafc2826c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4e3b5741338cba187cba99156ee755812a8ada36ab4022cda60a870e19f87e2d373942a67567d387d00301e25becb86180cdf05726291a3976a8f320309eb7ee
|
7
|
+
data.tar.gz: da7769da5b93b2edd49541fcdb47615f6e2709765ebe38879ea87459aa899b4ba62be208a9c8c335f6606572b691fbd3ce35ebe8b4326705060c69c4efa8b367
|
data/lib/process/daemon.rb
CHANGED
@@ -130,30 +130,46 @@ module Process
|
|
130
130
|
Process.kill(0, :INT)
|
131
131
|
end
|
132
132
|
|
133
|
-
|
133
|
+
def run
|
134
|
+
startup
|
135
|
+
|
136
|
+
trap("INT") do
|
137
|
+
shutdown
|
138
|
+
end
|
139
|
+
end
|
134
140
|
|
141
|
+
# A shared instance of the daemon.
|
135
142
|
def self.instance
|
136
143
|
@instance ||= self.new
|
137
144
|
end
|
138
145
|
|
139
|
-
#
|
140
|
-
def self.
|
141
|
-
Controller.
|
146
|
+
# The process controller, responsible for managing the daemon process start, stop, restart, etc.
|
147
|
+
def self.controller(options = {})
|
148
|
+
@controller ||= Controller.new(instance, options)
|
149
|
+
end
|
150
|
+
|
151
|
+
# The main entry point for daemonized scripts.
|
152
|
+
def self.daemonize(*args)
|
153
|
+
# Wish Ruby 2.0 kwargs were backported to 1.9.3... oh well:
|
154
|
+
options = (args.last === Hash) ? args.pop : {}
|
155
|
+
argv = (args.last === Array) ? args.pop : ARGV
|
156
|
+
|
157
|
+
controller(options).daemonize(argv)
|
142
158
|
end
|
143
159
|
|
144
|
-
# Start the daemon instance
|
160
|
+
# Start the daemon instance.
|
145
161
|
def self.start
|
146
|
-
|
162
|
+
controller.start
|
147
163
|
end
|
148
164
|
|
149
|
-
# Stop the daemon instance
|
165
|
+
# Stop the daemon instance.
|
150
166
|
def self.stop
|
151
|
-
|
167
|
+
controller.stop
|
152
168
|
end
|
153
169
|
|
154
|
-
#
|
170
|
+
# Check if the daemon is runnning or not.
|
155
171
|
def self.status
|
156
|
-
|
172
|
+
controller.status
|
157
173
|
end
|
158
174
|
end
|
159
175
|
end
|
@@ -25,75 +25,63 @@ module Process
|
|
25
25
|
# Daemon startup timeout
|
26
26
|
TIMEOUT = 5
|
27
27
|
|
28
|
-
# This module contains functionality related to starting and stopping the daemon, and code for processing command line input.
|
29
|
-
|
28
|
+
# This module contains functionality related to starting and stopping the @daemon, and code for processing command line input.
|
29
|
+
class Controller
|
30
|
+
# `options[:output]` specifies where to write textual output describing what is going on.
|
31
|
+
def initialize(daemon, options = {})
|
32
|
+
@daemon = daemon
|
33
|
+
|
34
|
+
@output = options[:output] || $stdout
|
35
|
+
end
|
36
|
+
|
30
37
|
# This function is called from the daemon executable. It processes ARGV and checks whether the user is asking for `start`, `stop`, `restart`, `status`.
|
31
|
-
def
|
38
|
+
def daemonize(argv = ARGV)
|
32
39
|
case argv.shift.to_sym
|
33
40
|
when :start
|
34
|
-
start
|
35
|
-
status
|
41
|
+
start
|
42
|
+
status
|
36
43
|
when :stop
|
37
|
-
stop
|
38
|
-
status
|
39
|
-
ProcessFile.cleanup(daemon)
|
44
|
+
stop
|
45
|
+
status
|
46
|
+
ProcessFile.cleanup(@daemon)
|
40
47
|
when :restart
|
41
|
-
stop
|
42
|
-
ProcessFile.cleanup(daemon)
|
43
|
-
start
|
44
|
-
status
|
48
|
+
stop
|
49
|
+
ProcessFile.cleanup(@daemon)
|
50
|
+
start
|
51
|
+
status
|
45
52
|
when :status
|
46
|
-
status
|
53
|
+
status
|
47
54
|
else
|
48
|
-
puts "Invalid command. Please specify start, restart, stop or status."
|
49
|
-
exit 1
|
55
|
+
@stderr.puts Rainbow("Invalid command. Please specify start, restart, stop or status.").red
|
50
56
|
end
|
51
57
|
end
|
52
|
-
|
53
|
-
#
|
54
|
-
def
|
55
|
-
|
56
|
-
|
57
|
-
case ProcessFile.status(daemon)
|
58
|
-
when :running
|
59
|
-
$stderr.puts Rainbow("Daemon already running!").color(:blue)
|
60
|
-
return
|
61
|
-
when :stopped
|
62
|
-
# We are good to go...
|
63
|
-
else
|
64
|
-
$stderr.puts Rainbow("Daemon in unknown state! Will clear previous state and continue.").color(:red)
|
65
|
-
status(daemon)
|
66
|
-
ProcessFile.clear(daemon)
|
67
|
-
end
|
68
|
-
|
69
|
-
daemon.prefork
|
70
|
-
daemon.mark_log
|
58
|
+
|
59
|
+
# Fork a child process, detatch it and run the daemon code.
|
60
|
+
def spawn
|
61
|
+
@daemon.prefork
|
62
|
+
@daemon.mark_log
|
71
63
|
|
72
64
|
fork do
|
73
65
|
Process.setsid
|
74
66
|
exit if fork
|
75
67
|
|
76
|
-
ProcessFile.store(daemon, Process.pid)
|
68
|
+
ProcessFile.store(@daemon, Process.pid)
|
77
69
|
|
78
70
|
File.umask 0000
|
79
|
-
Dir.chdir daemon.working_directory
|
71
|
+
Dir.chdir @daemon.working_directory
|
80
72
|
|
81
73
|
$stdin.reopen "/dev/null"
|
82
|
-
$stdout.reopen daemon.log_file_path, "a"
|
74
|
+
$stdout.reopen @daemon.log_file_path, "a"
|
83
75
|
$stdout.sync = true
|
84
|
-
|
76
|
+
|
85
77
|
$stderr.reopen $stdout
|
86
78
|
$stderr.sync = true
|
87
79
|
|
88
80
|
begin
|
89
|
-
daemon.
|
90
|
-
|
91
|
-
trap("INT") do
|
92
|
-
daemon.shutdown
|
93
|
-
end
|
94
|
-
rescue
|
81
|
+
@daemon.run
|
82
|
+
rescue Exception => error
|
95
83
|
$stderr.puts "=== Daemon Exception Backtrace @ #{Time.now.to_s} ==="
|
96
|
-
$stderr.puts "#{
|
84
|
+
$stderr.puts "#{error.class}: #{error.message}"
|
97
85
|
$!.backtrace.each { |at| $stderr.puts at }
|
98
86
|
$stderr.puts "=== Daemon Crashed ==="
|
99
87
|
$stderr.flush
|
@@ -102,63 +90,84 @@ module Process
|
|
102
90
|
$stderr.flush
|
103
91
|
end
|
104
92
|
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# This function starts the daemon process in the background.
|
96
|
+
def start
|
97
|
+
@output.puts Rainbow("Starting daemon...").blue
|
98
|
+
|
99
|
+
case ProcessFile.status(@daemon)
|
100
|
+
when :running
|
101
|
+
@output.puts Rainbow("Daemon already running!").blue
|
102
|
+
return
|
103
|
+
when :stopped
|
104
|
+
# We are good to go...
|
105
|
+
else
|
106
|
+
@output.puts Rainbow("Daemon in unknown state! Will clear previous state and continue.").red
|
107
|
+
ProcessFile.clear(@daemon)
|
108
|
+
end
|
109
|
+
|
110
|
+
spawn
|
105
111
|
|
106
|
-
puts Rainbow("Waiting for daemon to start...").color(:blue)
|
107
112
|
sleep 0.1
|
108
113
|
timer = TIMEOUT
|
109
|
-
pid = ProcessFile.recall(daemon)
|
114
|
+
pid = ProcessFile.recall(@daemon)
|
110
115
|
|
111
116
|
while pid == nil and timer > 0
|
112
117
|
# Wait a moment for the forking to finish...
|
113
|
-
puts Rainbow("Waiting for daemon to start (#{timer}/#{TIMEOUT})").
|
118
|
+
@output.puts Rainbow("Waiting for daemon to start (#{timer}/#{TIMEOUT})").blue
|
114
119
|
sleep 1
|
115
120
|
|
116
|
-
# If the daemon has crashed, it is never going to start...
|
117
|
-
break if daemon.crashed?
|
121
|
+
# If the @daemon has crashed, it is never going to start...
|
122
|
+
break if @daemon.crashed?
|
118
123
|
|
119
|
-
pid = ProcessFile.recall(daemon)
|
124
|
+
pid = ProcessFile.recall(@daemon)
|
120
125
|
|
121
126
|
timer -= 1
|
122
127
|
end
|
123
128
|
end
|
124
|
-
|
129
|
+
|
125
130
|
# Prints out the status of the daemon
|
126
|
-
def
|
127
|
-
|
131
|
+
def status
|
132
|
+
daemon_state = ProcessFile.status(@daemon)
|
133
|
+
|
134
|
+
case daemon_state
|
128
135
|
when :running
|
129
|
-
puts Rainbow("Daemon status: running pid=#{ProcessFile.recall(daemon)}").
|
136
|
+
@output.puts Rainbow("Daemon status: running pid=#{ProcessFile.recall(@daemon)}").green
|
130
137
|
when :unknown
|
131
|
-
if daemon.crashed?
|
132
|
-
puts Rainbow("Daemon status: crashed").
|
138
|
+
if @daemon.crashed?
|
139
|
+
@output.puts Rainbow("Daemon status: crashed").red
|
133
140
|
|
134
|
-
|
135
|
-
|
136
|
-
daemon.tail_log(
|
141
|
+
@output.flush
|
142
|
+
@output.puts Rainbow("Dumping daemon crash log:").red
|
143
|
+
@daemon.tail_log(@output)
|
137
144
|
else
|
138
|
-
puts Rainbow("Daemon status: unknown").
|
145
|
+
@output.puts Rainbow("Daemon status: unknown").red
|
139
146
|
end
|
140
147
|
when :stopped
|
141
|
-
puts Rainbow("Daemon status: stopped").
|
148
|
+
@output.puts Rainbow("Daemon status: stopped").blue
|
142
149
|
end
|
150
|
+
|
151
|
+
return daemon_state
|
143
152
|
end
|
144
153
|
|
145
154
|
# Stops the daemon process.
|
146
|
-
def
|
147
|
-
puts Rainbow("Stopping daemon...").
|
155
|
+
def stop
|
156
|
+
@output.puts Rainbow("Stopping daemon...").blue
|
148
157
|
|
149
158
|
# Check if the pid file exists...
|
150
|
-
unless File.file?(daemon.process_file_path)
|
151
|
-
puts Rainbow("Pid file not found. Is the daemon running?").
|
159
|
+
unless File.file?(@daemon.process_file_path)
|
160
|
+
@output.puts Rainbow("Pid file not found. Is the daemon running?").red
|
152
161
|
return
|
153
162
|
end
|
154
163
|
|
155
|
-
pid = ProcessFile.recall(daemon)
|
164
|
+
pid = ProcessFile.recall(@daemon)
|
156
165
|
|
157
|
-
# Check if the daemon is already stopped...
|
158
|
-
unless ProcessFile.running(daemon)
|
159
|
-
puts Rainbow("Pid #{pid} is not running. Has daemon crashed?").
|
166
|
+
# Check if the @daemon is already stopped...
|
167
|
+
unless ProcessFile.running(@daemon)
|
168
|
+
@output.puts Rainbow("Pid #{pid} is not running. Has daemon crashed?").red
|
160
169
|
|
161
|
-
daemon.tail_log($stderr)
|
170
|
+
@daemon.tail_log($stderr)
|
162
171
|
|
163
172
|
return
|
164
173
|
end
|
@@ -168,29 +177,29 @@ module Process
|
|
168
177
|
Process.kill("INT", pgid)
|
169
178
|
sleep 0.1
|
170
179
|
|
171
|
-
sleep 1 if ProcessFile.running(daemon)
|
180
|
+
sleep 1 if ProcessFile.running(@daemon)
|
172
181
|
|
173
|
-
# Kill/Term loop - if the daemon didn't die easily, shoot
|
182
|
+
# Kill/Term loop - if the @daemon didn't die easily, shoot
|
174
183
|
# it a few more times.
|
175
184
|
attempts = 5
|
176
|
-
while ProcessFile.running(daemon) and attempts > 0
|
185
|
+
while ProcessFile.running(@daemon) and attempts > 0
|
177
186
|
sig = (attempts >= 2) ? "KILL" : "TERM"
|
178
187
|
|
179
|
-
puts Rainbow("Sending #{sig} to process group #{pgid}...").
|
188
|
+
@output.puts Rainbow("Sending #{sig} to process group #{pgid}...").red
|
180
189
|
Process.kill(sig, pgid)
|
181
190
|
|
182
191
|
attempts -= 1
|
183
192
|
sleep 1
|
184
193
|
end
|
185
194
|
|
186
|
-
# If after doing our best the daemon is still running (pretty odd)...
|
187
|
-
if ProcessFile.running(daemon)
|
188
|
-
puts Rainbow("Daemon appears to be still running!").
|
195
|
+
# If after doing our best the @daemon is still running (pretty odd)...
|
196
|
+
if ProcessFile.running(@daemon)
|
197
|
+
@output.puts Rainbow("Daemon appears to be still running!").red
|
189
198
|
return
|
190
199
|
end
|
191
200
|
|
192
|
-
# Otherwise the daemon has been stopped.
|
193
|
-
ProcessFile.clear(daemon)
|
201
|
+
# Otherwise the @daemon has been stopped.
|
202
|
+
ProcessFile.clear(@daemon)
|
194
203
|
end
|
195
204
|
end
|
196
205
|
end
|
@@ -25,10 +25,6 @@ module Process
|
|
25
25
|
module Priviledges
|
26
26
|
# Set the user of the current process. Supply either a user ID
|
27
27
|
# or a user name.
|
28
|
-
#
|
29
|
-
# Be aware that on Mac OS X / Ruby 1.8 there are bugs when the user id
|
30
|
-
# is negative (i.e. it doesn't work). For example "nobody" with uid -2
|
31
|
-
# won't work.
|
32
28
|
def self.change_user(user)
|
33
29
|
if user.kind_of?(String)
|
34
30
|
user = Etc.getpwnam(user).uid
|
data/process-daemon.gemspec
CHANGED
@@ -16,9 +16,11 @@ Gem::Specification.new do |spec|
|
|
16
16
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
17
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
18
|
spec.require_paths = ["lib"]
|
19
|
-
|
19
|
+
|
20
|
+
spec.required_ruby_version = '>= 1.9.3'
|
21
|
+
|
20
22
|
spec.add_dependency "rainbow", "~> 2.0"
|
21
|
-
|
23
|
+
|
22
24
|
spec.add_development_dependency "bundler", "~> 1.3"
|
23
25
|
spec.add_development_dependency "minitest", "~> 5.3.2"
|
24
26
|
spec.add_development_dependency "rake"
|
data/test/test_daemon.rb
CHANGED
@@ -106,4 +106,24 @@ class DaemonTest < MiniTest::Test
|
|
106
106
|
def test_instances
|
107
107
|
refute_equal SleepDaemon.instance, XMLRPCDaemon.instance
|
108
108
|
end
|
109
|
+
|
110
|
+
def test_output
|
111
|
+
output = StringIO.new
|
112
|
+
|
113
|
+
controller = Process::Daemon::Controller.new(XMLRPCDaemon.instance, :output => output)
|
114
|
+
|
115
|
+
assert_equal :running, controller.status
|
116
|
+
|
117
|
+
assert_match /Daemon status: running pid=\d+/, output.string
|
118
|
+
|
119
|
+
output.rewind
|
120
|
+
controller.stop
|
121
|
+
|
122
|
+
assert_match /Stopping daemon/, output.string
|
123
|
+
|
124
|
+
output.rewind
|
125
|
+
controller.start
|
126
|
+
|
127
|
+
assert_match /Starting daemon/, output.string
|
128
|
+
end
|
109
129
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: process-daemon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-05-
|
11
|
+
date: 2014-05-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rainbow
|
@@ -99,7 +99,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
99
99
|
requirements:
|
100
100
|
- - '>='
|
101
101
|
- !ruby/object:Gem::Version
|
102
|
-
version:
|
102
|
+
version: 1.9.3
|
103
103
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
104
104
|
requirements:
|
105
105
|
- - '>='
|