servolux 0.11.0 → 0.12.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/README.md +4 -1
- data/examples/echo.rb +1 -1
- data/lib/servolux.rb +1 -3
- data/lib/servolux/daemon.rb +32 -46
- data/lib/servolux/null_logger.rb +19 -0
- data/lib/servolux/pid_file.rb +172 -0
- data/lib/servolux/server.rb +24 -29
- data/lib/servolux/version.rb +1 -1
- data/spec/daemon_spec.rb +0 -1
- data/spec/pid_file_spec.rb +133 -0
- data/spec/server_spec.rb +22 -16
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 581567ae92a495cdeb92737c979d52e6bd8cf996
|
4
|
+
data.tar.gz: 0f887e81fc0a8b78e1f340578f2183b460a27ca7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b67919d827ef8ca1e956918d631bbb2a1ffebcedf3334c4197664c674f340910c0156b6973e476f62c1030069a49332752c7847fab262ee219f633260d971e3f
|
7
|
+
data.tar.gz: 4cb69e76d39fece3215048a9032bfa3904888061dc1676b818500bca238503f0659e67ee583d98efd9286caf2abc30ccf0e27b283c8deccc62b5e1ec5fe3c558
|
data/README.md
CHANGED
@@ -8,7 +8,7 @@ by Tim Pease [](http://travis-
|
|
8
8
|
|
9
9
|
Serv-O-Lux is a collection of Ruby classes that are useful for daemon and
|
10
10
|
process management, and for writing your own Ruby services. The code is well
|
11
|
-
documented and tested. It works with Ruby and JRuby supporting
|
11
|
+
documented and tested. It works with Ruby and JRuby supporting 1.9 and 2.0
|
12
12
|
interpreters.
|
13
13
|
|
14
14
|
### Features
|
@@ -42,6 +42,9 @@ process to die if it does not exit in a given amount of time.
|
|
42
42
|
-- provides a pre-forking worker pool for executing tasks in parallel using
|
43
43
|
multiple processes.
|
44
44
|
|
45
|
+
[Servolux::PidFile](http://www.rubydoc.info/github/TwP/servolux/Servolux/PidFile)
|
46
|
+
-- provides PID file management and process signaling and liveness checks.
|
47
|
+
|
45
48
|
All the documentation is available online at http://rdoc.info/projects/TwP/servolux
|
46
49
|
|
47
50
|
### Install
|
data/examples/echo.rb
CHANGED
@@ -35,7 +35,7 @@ trap('EXIT') { acceptor.close }
|
|
35
35
|
# Create the worker pool passing in the code to execute in each child
|
36
36
|
# process.
|
37
37
|
pool = Servolux::Prefork.new {
|
38
|
-
socket,
|
38
|
+
socket,_ = acceptor.accept
|
39
39
|
socket.write "child #$$ echo> "
|
40
40
|
socket.flush
|
41
41
|
message = socket.gets
|
data/lib/servolux.rb
CHANGED
@@ -35,10 +35,8 @@ module Servolux
|
|
35
35
|
def self.fork?
|
36
36
|
RUBY_PLATFORM != 'java' and test(?e, '/dev/null')
|
37
37
|
end
|
38
|
-
|
39
38
|
end
|
40
39
|
|
41
|
-
%w[version threaded server piper daemon child prefork].each do |lib|
|
40
|
+
%w[version null_logger threaded pid_file server piper daemon child prefork].each do |lib|
|
42
41
|
require Servolux.libpath('servolux', lib)
|
43
42
|
end
|
44
|
-
|
data/lib/servolux/daemon.rb
CHANGED
@@ -77,7 +77,7 @@ class Servolux::Daemon
|
|
77
77
|
|
78
78
|
attr_accessor :name
|
79
79
|
attr_accessor :logger
|
80
|
-
|
80
|
+
attr_reader :pid_file
|
81
81
|
attr_reader :startup_command
|
82
82
|
attr_accessor :shutdown_command
|
83
83
|
attr_accessor :timeout
|
@@ -153,10 +153,10 @@ class Servolux::Daemon
|
|
153
153
|
def initialize( opts = {} )
|
154
154
|
@piper = nil
|
155
155
|
@logfile_reader = nil
|
156
|
+
@pid_file = nil
|
156
157
|
|
157
158
|
self.name = opts.fetch(:name, nil)
|
158
|
-
self.logger = opts.fetch(:logger,
|
159
|
-
self.pid_file = opts.fetch(:pid_file, nil)
|
159
|
+
self.logger = opts.fetch(:logger, Servolux::NullLogger())
|
160
160
|
self.startup_command = opts.fetch(:server, nil) || opts.fetch(:startup_command, nil)
|
161
161
|
self.shutdown_command = opts.fetch(:shutdown_command, nil)
|
162
162
|
self.timeout = opts.fetch(:timeout, 30)
|
@@ -167,6 +167,8 @@ class Servolux::Daemon
|
|
167
167
|
self.after_fork = opts.fetch(:after_fork, nil)
|
168
168
|
self.before_exec = opts.fetch(:before_exec, nil)
|
169
169
|
|
170
|
+
self.pid_file = opts.fetch(:pid_file, name) if pid_file.nil?
|
171
|
+
|
170
172
|
yield self if block_given?
|
171
173
|
|
172
174
|
ary = %w[name logger pid_file startup_command].map { |var|
|
@@ -198,14 +200,35 @@ class Servolux::Daemon
|
|
198
200
|
@startup_command = val
|
199
201
|
return unless val.is_a?(::Servolux::Server)
|
200
202
|
|
201
|
-
|
202
|
-
|
203
|
-
|
203
|
+
self.name = val.name
|
204
|
+
self.logger = val.logger
|
205
|
+
self.pid_file = val.pid_file
|
204
206
|
@shutdown_command = nil
|
205
207
|
end
|
206
208
|
alias :server= :startup_command=
|
207
209
|
alias :server :startup_command
|
208
210
|
|
211
|
+
# Set the PID file to the given `value`. If a PidFile instance is given, then
|
212
|
+
# it is used. If a name is given, then that name is used to create a PifFile
|
213
|
+
# instance.
|
214
|
+
#
|
215
|
+
# value - The PID file name or a PidFile instance.
|
216
|
+
#
|
217
|
+
# Raises an ArgumentError if the `value` cannot be used as a PID file.
|
218
|
+
def pid_file=( value )
|
219
|
+
@pid_file =
|
220
|
+
case value
|
221
|
+
when Servolux::PidFile
|
222
|
+
value
|
223
|
+
when String
|
224
|
+
path = File.dirname(value)
|
225
|
+
fn = File.basename(value, ".pid")
|
226
|
+
Servolux::PidFile.new(:name => fn, :path => path, :logger => logger)
|
227
|
+
else
|
228
|
+
raise ArgumentError, "#{value.inspect} cannot be used as a PID file"
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
209
232
|
# Assign the log file name. This log file will be monitored to determine
|
210
233
|
# if the daemon process is running.
|
211
234
|
#
|
@@ -289,15 +312,7 @@ class Servolux::Daemon
|
|
289
312
|
# @return [Boolean]
|
290
313
|
#
|
291
314
|
def alive?
|
292
|
-
|
293
|
-
Process.kill(0, pid)
|
294
|
-
true
|
295
|
-
rescue Errno::ESRCH, Errno::ENOENT
|
296
|
-
false
|
297
|
-
rescue Errno::EACCES => err
|
298
|
-
logger.error "You do not have access to the PID file at " \
|
299
|
-
"#{pid_file.inspect}: #{err.message}"
|
300
|
-
false
|
315
|
+
pid_file.alive?
|
301
316
|
end
|
302
317
|
|
303
318
|
# Send a signal to the daemon process identified by the PID file. The
|
@@ -309,32 +324,9 @@ class Servolux::Daemon
|
|
309
324
|
# @return [Daemon] self
|
310
325
|
#
|
311
326
|
def kill( signal = 'INT' )
|
312
|
-
|
313
|
-
pid = retrieve_pid
|
314
|
-
logger.info "Killing PID #{pid} with #{signal}"
|
315
|
-
Process.kill(signal, pid)
|
316
|
-
self
|
317
|
-
rescue Errno::EINVAL
|
318
|
-
logger.error "Failed to kill PID #{pid} with #{signal}: " \
|
319
|
-
"'#{signal}' is an invalid or unsupported signal number."
|
320
|
-
rescue Errno::EPERM
|
321
|
-
logger.error "Failed to kill PID #{pid} with #{signal}: " \
|
322
|
-
"Insufficient permissions."
|
323
|
-
rescue Errno::ESRCH
|
324
|
-
logger.error "Failed to kill PID #{pid} with #{signal}: " \
|
325
|
-
"Process is deceased or zombie."
|
326
|
-
rescue Errno::EACCES => err
|
327
|
-
logger.error err.message
|
328
|
-
rescue Errno::ENOENT => err
|
329
|
-
logger.error "Could not find a PID file at #{pid_file.inspect}. " \
|
330
|
-
"Most likely the process is no longer running."
|
331
|
-
rescue Exception => err
|
332
|
-
unless err.is_a?(SystemExit)
|
333
|
-
logger.error "Failed to kill PID #{pid} with #{signal}: #{err.message}"
|
334
|
-
end
|
327
|
+
pid_file.kill signal
|
335
328
|
end
|
336
329
|
|
337
|
-
|
338
330
|
private
|
339
331
|
|
340
332
|
def run_startup_command
|
@@ -373,11 +365,7 @@ private
|
|
373
365
|
end
|
374
366
|
|
375
367
|
def retrieve_pid
|
376
|
-
@piper ? @piper.pid :
|
377
|
-
rescue TypeError
|
378
|
-
raise Error, "A PID file was not specified."
|
379
|
-
rescue ArgumentError
|
380
|
-
raise Error, "#{pid_file.inspect} does not contain a valid PID."
|
368
|
+
@piper ? @piper.pid : pid_file.pid
|
381
369
|
end
|
382
370
|
|
383
371
|
def started?
|
@@ -475,6 +463,4 @@ private
|
|
475
463
|
end
|
476
464
|
end
|
477
465
|
# :startdoc:
|
478
|
-
|
479
466
|
end
|
480
|
-
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "singleton"
|
2
|
+
require "logger"
|
3
|
+
|
4
|
+
module Servolux
|
5
|
+
# A "do nothing" implementation of the standard Ruby Logger class.
|
6
|
+
class NullLogger < Logger
|
7
|
+
include Singleton
|
8
|
+
def initialize(*args); end
|
9
|
+
def add(*args, &block); end
|
10
|
+
end
|
11
|
+
|
12
|
+
# Syntactic sugar for getting the null logger instance.
|
13
|
+
#
|
14
|
+
# Servolux::NullLogger() #=> NullLogger singleton instance
|
15
|
+
#
|
16
|
+
def self.NullLogger
|
17
|
+
NullLogger.instance
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
# == Synopsis
|
2
|
+
# The PidFile manages the lifecycle of a PID file.
|
3
|
+
#
|
4
|
+
# == Details
|
5
|
+
# A PID file contains the process ID of a given program. This file can be used
|
6
|
+
# by the program to indicate that it started successfully. The file can be used
|
7
|
+
# programmatically to look up the process ID and send signals to the program.
|
8
|
+
# The file can be used to ensure two instances of the same program are not
|
9
|
+
# started at the same time.
|
10
|
+
#
|
11
|
+
# The PidFile class supports creating and deleting PID files. Methods are
|
12
|
+
# provided to check if the program associated with the PID is `alive?`. Signals
|
13
|
+
# can be sent to the program using the `kill` method.
|
14
|
+
#
|
15
|
+
# == Examples
|
16
|
+
#
|
17
|
+
# Here is a simple example creating a PID file in the "/var/run" directory.
|
18
|
+
#
|
19
|
+
# pid_file = Servolux::PidFile.new(:name => "test", :path => "/var/run")
|
20
|
+
# pid_file.filename #=> "/var/run/test.pid"
|
21
|
+
# pid_file.write
|
22
|
+
#
|
23
|
+
# From another process we can access this PID file and send a `HUP` signal to
|
24
|
+
# the program.
|
25
|
+
#
|
26
|
+
# pid_file = Servolux::PidFile.new(:name => "test", :path => "/var/run")
|
27
|
+
# pid_file.kill("HUP") if pid_file.alive?
|
28
|
+
#
|
29
|
+
class Servolux::PidFile
|
30
|
+
|
31
|
+
DEFAULT_MODE = 0640
|
32
|
+
|
33
|
+
attr_accessor :name # the process name
|
34
|
+
attr_accessor :path # the path to the PID file
|
35
|
+
attr_accessor :mode # PID file permissions mode
|
36
|
+
attr_accessor :logger # logger for outputting messages
|
37
|
+
|
38
|
+
# Create a new PID file instance.
|
39
|
+
#
|
40
|
+
# opts - The options Hash
|
41
|
+
# :name - the name of the program
|
42
|
+
# :path - path to the PID file location
|
43
|
+
# :mode - file permissions mode
|
44
|
+
# :logger - logger for outputting messages
|
45
|
+
#
|
46
|
+
def initialize( opts = {} )
|
47
|
+
@name = opts.fetch(:name, $0)
|
48
|
+
@path = opts.fetch(:path, ".")
|
49
|
+
@mode = opts.fetch(:mode, DEFAULT_MODE)
|
50
|
+
@logger = opts.fetch(:logger, Servolux::NullLogger())
|
51
|
+
|
52
|
+
yield self if block_given?
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns the full name of the PID file including path and extension.
|
56
|
+
def filename
|
57
|
+
fn = name.to_s.downcase.tr(" ","_") + ".pid"
|
58
|
+
fn = File.join(path, fn) unless path.nil?
|
59
|
+
fn
|
60
|
+
end
|
61
|
+
|
62
|
+
# Writes the given `pid` to the PID file. The `pid` defaults to the current
|
63
|
+
# process ID.
|
64
|
+
#
|
65
|
+
# pid - The process ID to write to the file
|
66
|
+
#
|
67
|
+
# Returns the filename of PID file.
|
68
|
+
# Raises Errno::EACCESS if you do not have permission to write the file.
|
69
|
+
def write( pid = Process.pid )
|
70
|
+
fn = filename
|
71
|
+
logger.debug "Writing pid file #{fn.inspect}"
|
72
|
+
File.open(fn, 'w', mode) { |fd| fd.write(pid.to_s) }
|
73
|
+
fn
|
74
|
+
end
|
75
|
+
|
76
|
+
# Delete the PID file if it exists. This method first checks that the current
|
77
|
+
# process PID is the same as the PID stored in the file. If the PIDs do not
|
78
|
+
# match, then this method returns `nil` without taking any action.
|
79
|
+
#
|
80
|
+
# Returns the filename of the deleted file or `nil` if no action was taken.
|
81
|
+
# Raises Errno::EACCESS if you do not have permission to delete the file.
|
82
|
+
def delete
|
83
|
+
return unless pid == Process.pid
|
84
|
+
fn = filename
|
85
|
+
logger.debug "Deleting pid file #{fn.inspect}"
|
86
|
+
File.delete fn
|
87
|
+
fn
|
88
|
+
end
|
89
|
+
|
90
|
+
# Forcibly delete the PID file if it exists. This method does NOT check that
|
91
|
+
# the current process PID against the PID stored in the file.
|
92
|
+
#
|
93
|
+
# Returns the filename of the deleted file or `nil` if no action was taken.
|
94
|
+
# Raises Errno::EACCESS if you do not have permission to delete the file.
|
95
|
+
def delete!
|
96
|
+
return unless exist?
|
97
|
+
fn = filename
|
98
|
+
logger.debug "Deleting pid file #{fn.inspect}"
|
99
|
+
File.delete fn
|
100
|
+
fn
|
101
|
+
end
|
102
|
+
|
103
|
+
# Returns `true` if the PID file exists. Returns `false` otherwise.
|
104
|
+
def exist?
|
105
|
+
File.exist? filename
|
106
|
+
end
|
107
|
+
|
108
|
+
# Returns the numeric PID read from the file or `nil` if the file does not
|
109
|
+
# exist. If you do not have permission to access the file `nil` is returned.
|
110
|
+
def pid
|
111
|
+
fn = filename
|
112
|
+
Integer(File.read(fn).strip) if File.exist?(fn)
|
113
|
+
rescue Errno::EACCES => err
|
114
|
+
logger.error "You do not have access to the PID file at " \
|
115
|
+
"#{fn.inspect}: #{err.message}"
|
116
|
+
nil
|
117
|
+
end
|
118
|
+
|
119
|
+
# Returns `true` if the process is currently running. Returns `false` if this
|
120
|
+
# is not the case. The status of the process is determined by sending signal 0
|
121
|
+
# to the process.
|
122
|
+
def alive?
|
123
|
+
pid = self.pid
|
124
|
+
return if pid.nil?
|
125
|
+
|
126
|
+
Process.kill(0, pid)
|
127
|
+
true
|
128
|
+
rescue Errno::ESRCH, Errno::ENOENT
|
129
|
+
false
|
130
|
+
end
|
131
|
+
|
132
|
+
# Send a signal the process identified by the PID file. The default signal to
|
133
|
+
# send is 'INT' (2). The signal can be given either as a string or a signal
|
134
|
+
# number.
|
135
|
+
#
|
136
|
+
# signal - The signal to send to the process (String or Integer)
|
137
|
+
#
|
138
|
+
# Returns an Integer or `nil` if an error was encountered.
|
139
|
+
def kill( signal = 'INT' )
|
140
|
+
pid = self.pid
|
141
|
+
return if pid.nil?
|
142
|
+
|
143
|
+
signal = Signal.list.invert[signal] if signal.is_a?(Integer)
|
144
|
+
logger.info "Killing PID #{pid} with #{signal}"
|
145
|
+
Process.kill(signal, pid)
|
146
|
+
|
147
|
+
rescue Errno::EINVAL
|
148
|
+
logger.error "Failed to kill PID #{pid} with #{signal}: " \
|
149
|
+
"'#{signal}' is an invalid or unsupported signal number."
|
150
|
+
nil
|
151
|
+
rescue Errno::EPERM
|
152
|
+
logger.error "Failed to kill PID #{pid} with #{signal}: " \
|
153
|
+
"Insufficient permissions."
|
154
|
+
nil
|
155
|
+
rescue Errno::ESRCH
|
156
|
+
logger.error "Failed to kill PID #{pid} with #{signal}: " \
|
157
|
+
"Process is deceased or zombie."
|
158
|
+
nil
|
159
|
+
rescue Errno::EACCES => err
|
160
|
+
logger.error err.message
|
161
|
+
nil
|
162
|
+
rescue Errno::ENOENT => err
|
163
|
+
logger.error "Could not find a PID file at #{pid_file.inspect}. " \
|
164
|
+
"Most likely the process is no longer running."
|
165
|
+
nil
|
166
|
+
rescue Exception => err
|
167
|
+
unless err.is_a?(SystemExit)
|
168
|
+
logger.error "Failed to kill PID #{pid} with #{signal}: #{err.message}"
|
169
|
+
end
|
170
|
+
nil
|
171
|
+
end
|
172
|
+
end
|
data/lib/servolux/server.rb
CHANGED
@@ -129,12 +129,9 @@ class Servolux::Server
|
|
129
129
|
|
130
130
|
Error = Class.new(::Servolux::Error)
|
131
131
|
|
132
|
-
DEFAULT_PID_FILE_MODE = 0640
|
133
|
-
|
134
132
|
attr_reader :name
|
135
133
|
attr_accessor :logger
|
136
|
-
|
137
|
-
attr_accessor :pid_file_mode
|
134
|
+
attr_reader :pid_file
|
138
135
|
|
139
136
|
# call-seq:
|
140
137
|
# Server.new( name, options = {} ) { block }
|
@@ -153,10 +150,9 @@ class Servolux::Server
|
|
153
150
|
@mutex = Mutex.new
|
154
151
|
@shutdown = nil
|
155
152
|
|
156
|
-
self.logger
|
157
|
-
self.
|
158
|
-
self.
|
159
|
-
self.interval = opts.fetch(:interval, 0)
|
153
|
+
self.logger = opts.fetch(:logger, Servolux::NullLogger())
|
154
|
+
self.interval = opts.fetch(:interval, 0)
|
155
|
+
self.pid_file = opts.fetch(:pid_file, name)
|
160
156
|
|
161
157
|
if block
|
162
158
|
eg = class << self; self; end
|
@@ -191,12 +187,12 @@ class Servolux::Server
|
|
191
187
|
|
192
188
|
begin
|
193
189
|
trap_signals
|
194
|
-
|
190
|
+
pid_file.write
|
195
191
|
start
|
196
192
|
join
|
197
193
|
wait_for_shutdown if wait
|
198
194
|
ensure
|
199
|
-
|
195
|
+
pid_file.delete
|
200
196
|
end
|
201
197
|
return self
|
202
198
|
end
|
@@ -240,30 +236,29 @@ class Servolux::Server
|
|
240
236
|
alias :term :shutdown # handles the TERM signal
|
241
237
|
private :start, :stop
|
242
238
|
|
243
|
-
#
|
244
|
-
#
|
239
|
+
# Set the PID file to the given `value`. If a PidFile instance is given, then
|
240
|
+
# it is used. If a name is given, then that name is used to create a PifFile
|
241
|
+
# instance.
|
242
|
+
#
|
243
|
+
# value - The PID file name or a PidFile instance.
|
245
244
|
#
|
246
|
-
|
247
|
-
|
245
|
+
# Raises an ArgumentError if the `value` cannot be used as a PID file.
|
246
|
+
def pid_file=( value )
|
247
|
+
@pid_file =
|
248
|
+
case value
|
249
|
+
when Servolux::PidFile
|
250
|
+
value
|
251
|
+
when String
|
252
|
+
path = File.dirname(value)
|
253
|
+
fn = File.basename(value, ".pid")
|
254
|
+
Servolux::PidFile.new(:name => fn, :path => path, :logger => logger)
|
255
|
+
else
|
256
|
+
raise ArgumentError, "#{value.inspect} cannot be used as a PID file"
|
257
|
+
end
|
248
258
|
end
|
249
259
|
|
250
260
|
private
|
251
261
|
|
252
|
-
def create_pid_file
|
253
|
-
logger.debug "Server #{name.inspect} creating pid file #{pid_file.inspect}"
|
254
|
-
File.open(pid_file, 'w', pid_file_mode) {|fd| fd.write(Process.pid.to_s)}
|
255
|
-
end
|
256
|
-
|
257
|
-
def delete_pid_file
|
258
|
-
if test(?f, pid_file)
|
259
|
-
pid = Integer(File.read(pid_file).strip)
|
260
|
-
return unless pid == Process.pid
|
261
|
-
|
262
|
-
logger.debug "Server #{name.inspect} removing pid file #{pid_file.inspect}"
|
263
|
-
File.delete(pid_file)
|
264
|
-
end
|
265
|
-
end
|
266
|
-
|
267
262
|
def trap_signals
|
268
263
|
SIGNALS.each do |sig|
|
269
264
|
method = sig.downcase.to_sym
|
data/lib/servolux/version.rb
CHANGED
data/spec/daemon_spec.rb
CHANGED
@@ -0,0 +1,133 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe Servolux::PidFile do
|
4
|
+
before :all do
|
5
|
+
tmp = Tempfile.new "servolux-pid-file"
|
6
|
+
@path = tmp.path; tmp.unlink
|
7
|
+
FileUtils.mkdir @path
|
8
|
+
end
|
9
|
+
|
10
|
+
after :all do
|
11
|
+
FileUtils.rm_rf @path
|
12
|
+
end
|
13
|
+
|
14
|
+
before :each do
|
15
|
+
FileUtils.rm_f Dir.glob("#@path/*.pid")
|
16
|
+
@pid_file = Servolux::PidFile.new \
|
17
|
+
:name => "test",
|
18
|
+
:path => @path,
|
19
|
+
:logger => Logging.logger['Servolux']
|
20
|
+
|
21
|
+
@filename = @pid_file.filename
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "filename" do
|
25
|
+
it "normalizes the process name" do
|
26
|
+
pid = Servolux::PidFile.new :name => "Test Server"
|
27
|
+
expect(pid.filename).to eq("./test_server.pid")
|
28
|
+
end
|
29
|
+
|
30
|
+
it "includes the path" do
|
31
|
+
pid = Servolux::PidFile.new :name => "Test Server", :path => @path
|
32
|
+
expect(pid.filename).to eq("#@path/test_server.pid")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "creating" do
|
37
|
+
it "writes a PID file" do
|
38
|
+
expect(test(?e, @filename)).to be false
|
39
|
+
|
40
|
+
@pid_file.write(123456)
|
41
|
+
expect(test(?e, @filename)).to be true
|
42
|
+
expect(@log_output.readline.chomp).to \
|
43
|
+
eq(%Q{DEBUG Servolux : Writing pid file "#@path/test.pid"})
|
44
|
+
|
45
|
+
pid = Integer(File.read(@filename).strip)
|
46
|
+
expect(pid).to eq(123456)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "uses mode rw-r----- by default" do
|
50
|
+
expect(test(?e, @filename)).to be false
|
51
|
+
|
52
|
+
@pid_file.write
|
53
|
+
expect(test(?e, @filename)).to be true
|
54
|
+
expect(@log_output.readline.chomp).to \
|
55
|
+
eq(%Q{DEBUG Servolux : Writing pid file "#@path/test.pid"})
|
56
|
+
|
57
|
+
mode = File.stat(@filename).mode & 0777
|
58
|
+
expect(mode).to eq(0640)
|
59
|
+
end
|
60
|
+
|
61
|
+
it "uses the given mode" do
|
62
|
+
@pid_file.mode = 0400
|
63
|
+
expect(test(?e, @filename)).to be false
|
64
|
+
|
65
|
+
@pid_file.write
|
66
|
+
expect(test(?e, @filename)).to be true
|
67
|
+
expect(@log_output.readline.chomp).to \
|
68
|
+
eq(%Q{DEBUG Servolux : Writing pid file "#@path/test.pid"})
|
69
|
+
|
70
|
+
mode = File.stat(@filename).mode & 0777
|
71
|
+
expect(mode).to eq(0400)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "deleting" do
|
76
|
+
it "removes a PID file" do
|
77
|
+
expect(test(?e, @filename)).to be false
|
78
|
+
expect { @pid_file.delete }.not_to raise_error
|
79
|
+
|
80
|
+
@pid_file.write
|
81
|
+
expect(test(?e, @filename)).to be true
|
82
|
+
expect(@log_output.readline.chomp).to \
|
83
|
+
eq(%Q{DEBUG Servolux : Writing pid file "#@path/test.pid"})
|
84
|
+
|
85
|
+
@pid_file.delete
|
86
|
+
expect(test(?e, @filename)).to be false
|
87
|
+
expect(@log_output.readline.chomp).to \
|
88
|
+
eq(%Q{DEBUG Servolux : Deleting pid file "#@path/test.pid"})
|
89
|
+
end
|
90
|
+
|
91
|
+
it "removes the PID file only from the same process" do
|
92
|
+
@pid_file.write(654321)
|
93
|
+
expect(test(?e, @filename)).to be true
|
94
|
+
expect(@log_output.readline.chomp).to \
|
95
|
+
eq(%Q{DEBUG Servolux : Writing pid file "#@path/test.pid"})
|
96
|
+
|
97
|
+
@pid_file.delete
|
98
|
+
expect(test(?e, @filename)).to be true
|
99
|
+
expect(@log_output.readline).to be_nil
|
100
|
+
end
|
101
|
+
|
102
|
+
it "can forcibly remove a PID file" do
|
103
|
+
@pid_file.write(135790)
|
104
|
+
expect(test(?e, @filename)).to be true
|
105
|
+
expect(@log_output.readline.chomp).to \
|
106
|
+
eq(%Q{DEBUG Servolux : Writing pid file "#@path/test.pid"})
|
107
|
+
|
108
|
+
@pid_file.delete!
|
109
|
+
expect(test(?e, @filename)).to be false
|
110
|
+
expect(@log_output.readline.chomp).to \
|
111
|
+
eq(%Q{DEBUG Servolux : Deleting pid file "#@path/test.pid"})
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
it "returns the PID from the file" do
|
116
|
+
expect(@pid_file.pid).to be_nil
|
117
|
+
|
118
|
+
File.open(@filename, 'w') { |fd| fd.write(314159) }
|
119
|
+
expect(@pid_file.pid).to eq(314159)
|
120
|
+
|
121
|
+
File.delete(@filename)
|
122
|
+
expect(@pid_file.pid).to be_nil
|
123
|
+
end
|
124
|
+
|
125
|
+
it "reports if the process is alive" do
|
126
|
+
expect(@pid_file.pid).to be_nil # there is no PID file yet
|
127
|
+
expect(@pid_file).not_to be_alive # and so we cannot determine
|
128
|
+
# if the process is alive
|
129
|
+
@pid_file.write
|
130
|
+
expect(@pid_file.pid).not_to be_nil
|
131
|
+
expect(@pid_file).to be_alive
|
132
|
+
end
|
133
|
+
end
|
data/spec/server_spec.rb
CHANGED
@@ -9,61 +9,67 @@ describe Servolux::Server do
|
|
9
9
|
end
|
10
10
|
|
11
11
|
base = Class.new(Servolux::Server) do
|
12
|
-
def initialize
|
13
|
-
super('Test Server', :logger => Logging.logger['Servolux']
|
12
|
+
def initialize
|
13
|
+
super('Test Server', :logger => Logging.logger['Servolux'])
|
14
14
|
end
|
15
15
|
def run() sleep; end
|
16
16
|
end
|
17
17
|
|
18
18
|
before :each do
|
19
19
|
@server = base.new
|
20
|
-
|
20
|
+
@server.pid_file.delete
|
21
21
|
end
|
22
22
|
|
23
23
|
after :each do
|
24
24
|
@server.shutdown
|
25
|
-
|
25
|
+
@server.wait_for_shutdown
|
26
26
|
end
|
27
27
|
|
28
28
|
it 'generates a PID file' do
|
29
|
-
expect(
|
29
|
+
expect(@server.pid_file).to_not exist
|
30
30
|
|
31
31
|
t = Thread.new {@server.startup}
|
32
32
|
wait_until { @server.running? and t.status == 'sleep' }
|
33
|
-
expect(
|
33
|
+
expect(@server.pid_file).to exist
|
34
34
|
|
35
35
|
@server.shutdown
|
36
36
|
wait_until { t.status == false }
|
37
|
-
expect(
|
37
|
+
expect(@server.pid_file).to_not exist
|
38
38
|
end
|
39
39
|
|
40
40
|
it 'generates a PID file with mode rw-r----- by default' do
|
41
41
|
t = Thread.new {@server.startup}
|
42
42
|
wait_until { @server.running? and t.status == 'sleep' }
|
43
|
-
expect(
|
43
|
+
expect(@server.pid_file).to exist
|
44
44
|
|
45
|
-
expect(@log_output.readline.chomp).to eq(%q(DEBUG Servolux :
|
45
|
+
expect(@log_output.readline.chomp).to eq(%q(DEBUG Servolux : Writing pid file "./test_server.pid"))
|
46
46
|
expect(@log_output.readline.chomp).to eq(%q(DEBUG Servolux : Starting))
|
47
|
-
|
47
|
+
|
48
|
+
filename = @server.pid_file.filename
|
49
|
+
mode = File.stat(filename).mode & 0777
|
50
|
+
expect(mode).to eq(0640)
|
48
51
|
|
49
52
|
@server.shutdown
|
50
53
|
wait_until { t.status == false }
|
51
|
-
expect(
|
54
|
+
expect(@server.pid_file).to_not exist
|
52
55
|
end
|
53
56
|
|
54
57
|
it 'generates PID file with the specified permissions' do
|
55
|
-
@server.
|
58
|
+
@server.pid_file.mode = 0400
|
56
59
|
t = Thread.new {@server.startup}
|
57
60
|
wait_until { @server.running? and t.status == 'sleep' }
|
58
|
-
expect(
|
61
|
+
expect(@server.pid_file).to exist
|
59
62
|
|
60
|
-
expect(@log_output.readline.chomp).to eq(%q(DEBUG Servolux :
|
63
|
+
expect(@log_output.readline.chomp).to eq(%q(DEBUG Servolux : Writing pid file "./test_server.pid"))
|
61
64
|
expect(@log_output.readline.chomp).to eq(%q(DEBUG Servolux : Starting))
|
62
|
-
|
65
|
+
|
66
|
+
filename = @server.pid_file.filename
|
67
|
+
mode = File.stat(filename).mode & 0777
|
68
|
+
expect(mode).to eq(0400)
|
63
69
|
|
64
70
|
@server.shutdown
|
65
71
|
wait_until { t.status == false }
|
66
|
-
expect(
|
72
|
+
expect(@server.pid_file).to_not exist
|
67
73
|
end
|
68
74
|
|
69
75
|
it 'shuts down gracefully when signaled' do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: servolux
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.12.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tim Pease
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-06-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bones-rspec
|
@@ -69,7 +69,7 @@ dependencies:
|
|
69
69
|
description: |-
|
70
70
|
Serv-O-Lux is a collection of Ruby classes that are useful for daemon and
|
71
71
|
process management, and for writing your own Ruby services. The code is well
|
72
|
-
documented and tested. It works with Ruby and JRuby supporting
|
72
|
+
documented and tested. It works with Ruby and JRuby supporting 1.9 and 2.0
|
73
73
|
interpreters.
|
74
74
|
email: tim.pease@gmail.com
|
75
75
|
executables: []
|
@@ -89,6 +89,8 @@ files:
|
|
89
89
|
- lib/servolux.rb
|
90
90
|
- lib/servolux/child.rb
|
91
91
|
- lib/servolux/daemon.rb
|
92
|
+
- lib/servolux/null_logger.rb
|
93
|
+
- lib/servolux/pid_file.rb
|
92
94
|
- lib/servolux/piper.rb
|
93
95
|
- lib/servolux/prefork.rb
|
94
96
|
- lib/servolux/server.rb
|
@@ -97,6 +99,7 @@ files:
|
|
97
99
|
- script/bootstrap
|
98
100
|
- spec/child_spec.rb
|
99
101
|
- spec/daemon_spec.rb
|
102
|
+
- spec/pid_file_spec.rb
|
100
103
|
- spec/piper_spec.rb
|
101
104
|
- spec/prefork_spec.rb
|
102
105
|
- spec/server_spec.rb
|