raad 0.3.1 → 0.3.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/CHANGELOG.md +7 -1
- data/README.md +17 -5
- data/lib/raad/runner.rb +103 -79
- data/lib/raad/unix_daemon.rb +17 -6
- data/lib/raad/version.rb +1 -1
- metadata +2 -2
data/CHANGELOG.md
CHANGED
@@ -1,2 +1,8 @@
|
|
1
1
|
# 0.3.1, 08-24-2011
|
2
|
-
- initial release. support for MRI 1.8 & 1.9 only
|
2
|
+
- initial release. support for MRI 1.8 & 1.9 only
|
3
|
+
|
4
|
+
# 0.3.3, 08-26-2011
|
5
|
+
- reworked runner stop sequence & signals trapping
|
6
|
+
- force kill + pid removal bug
|
7
|
+
- wrong exit code bug
|
8
|
+
- comments & cosmetics
|
data/README.md
CHANGED
@@ -1,13 +1,25 @@
|
|
1
|
-
# Raad v0.3.
|
1
|
+
# Raad v0.3.3
|
2
2
|
|
3
|
-
Raad - Ruby as a
|
3
|
+
Raad - Ruby as a daemon lightweight service wrapper.
|
4
4
|
|
5
5
|
Raad is a non-intrusive, lightweight, simple Ruby daemon maker. Basically A simple class which implements
|
6
|
-
the start and stop methods, can be used
|
6
|
+
the start and stop methods, can be used seamlessly as a daemon or a normal console app.
|
7
7
|
|
8
8
|
Raad provides daemon control using the start/stop commands. Your code can optionnally use the Raad
|
9
9
|
logging module.
|
10
10
|
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
### Gem
|
14
|
+
gem install raad
|
15
|
+
|
16
|
+
### Bundler
|
17
|
+
#### Latest from github
|
18
|
+
gem "raad", :git => "git://github.com/praized/raad.git", :branch => "master"
|
19
|
+
|
20
|
+
#### Released gem
|
21
|
+
gem "raad", "~> 0.3.3"
|
22
|
+
|
11
23
|
## Example
|
12
24
|
Create a class with a start and a stop method. Just by requiring 'raad', your class will be
|
13
25
|
wrapped by Raad and daemonizable.
|
@@ -82,12 +94,12 @@ The Log4r gem (~> 1.1.9) is required.
|
|
82
94
|
## Author
|
83
95
|
Authored by Colin Surprenant, [@colinsurprenant][twitter], [colin.surprenant@needium.com][needium], [colin.surprenant@gmail.com][gmail], [http://github.com/colinsurprenant][github]
|
84
96
|
|
85
|
-
##
|
97
|
+
## Acknowledgements
|
86
98
|
Thanks to the Thin ([https://github.com/macournoyer/thin][thin]), Goliath ([https://github.com/postrank-labs/goliath/][goliath])
|
87
99
|
and Sinatra ([https://github.com/bmizerany/sinatra][sinatra]) projects for providing inspiration and/or code!
|
88
100
|
|
89
101
|
## License
|
90
|
-
|
102
|
+
Raad is distributed under the Apache License, Version 2.0. See the LICENSE.md file.
|
91
103
|
|
92
104
|
[needium]: colin.surprenant@needium.com
|
93
105
|
[gmail]: colin.surprenant@gmail.com
|
data/lib/raad/runner.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'optparse'
|
2
2
|
require 'thread'
|
3
|
+
require 'monitor'
|
3
4
|
|
4
5
|
module Raad
|
5
6
|
class Runner
|
@@ -9,54 +10,46 @@ module Raad
|
|
9
10
|
SOFT_STOP_TIMEOUT = 58 * SECOND
|
10
11
|
HARD_STOP_TIMEOUT = 60 * SECOND
|
11
12
|
|
12
|
-
|
13
|
-
# @return [String] The file to write the servers pid file into
|
14
|
-
attr_accessor :pid_file
|
15
|
-
|
16
|
-
# The application
|
17
|
-
# @return [Object] The service to execute
|
18
|
-
attr_accessor :service
|
19
|
-
|
20
|
-
# The parsed options
|
21
|
-
# @return [Hash] The options parsed by the runner
|
22
|
-
attr_reader :options
|
13
|
+
attr_accessor :service, :pid_file, :options
|
23
14
|
|
24
15
|
# Create a new Runner
|
25
16
|
#
|
26
|
-
# @param argv [Array]
|
27
|
-
# @param service [Object]
|
17
|
+
# @param argv [Array] command line arguments
|
18
|
+
# @param service [Object] service to execute
|
28
19
|
def initialize(argv, service)
|
29
20
|
create_options_parser(service).parse!(argv)
|
30
21
|
|
31
|
-
|
22
|
+
# start/stop
|
23
|
+
@options[:command] = argv[0].to_s.downcase
|
32
24
|
unless ['start', 'stop'].include?(options[:command])
|
33
25
|
puts(">> start|stop command is required")
|
34
|
-
exit!
|
26
|
+
exit!(false)
|
35
27
|
end
|
36
28
|
|
37
29
|
@service = service
|
38
30
|
@service_name = nil
|
39
31
|
@logger_options = nil
|
40
32
|
@pid_file = nil
|
41
|
-
|
42
|
-
|
33
|
+
|
34
|
+
# signals handling
|
35
|
+
@signals = []
|
36
|
+
@monitor = Monitor.new
|
37
|
+
@stop_signaled = false
|
43
38
|
end
|
44
39
|
|
45
40
|
def run
|
46
41
|
# first load config if present
|
47
42
|
Configuration.load(options[:config] || File.expand_path("./config/#{default_service_name}.rb"))
|
48
43
|
|
49
|
-
#
|
44
|
+
# settings which depends on configuration
|
50
45
|
@service_name = options[:name] || Configuration.daemon_name || default_service_name
|
51
46
|
|
52
|
-
# ajust "dynamic defaults"
|
53
47
|
unless options[:log_file]
|
54
48
|
options[:log_file] = (options[:daemonize] ? File.expand_path("#{@service_name}.log") : nil)
|
55
49
|
end
|
56
50
|
unless options[:log_stdout]
|
57
51
|
options[:log_stdout] = !options[:daemonize]
|
58
52
|
end
|
59
|
-
|
60
53
|
@logger_options = {
|
61
54
|
:file => options.delete(:log_file),
|
62
55
|
:stdout => options.delete(:log_stdout),
|
@@ -64,15 +57,17 @@ module Raad
|
|
64
57
|
}
|
65
58
|
@pid_file = options.delete(:pid_file) || "./#{@service_name}.pid"
|
66
59
|
|
60
|
+
# check for stop command, @pid_file must be set
|
61
|
+
if options[:command] == 'stop'
|
62
|
+
puts(">> Raad service wrapper v#{VERSION} stopping")
|
63
|
+
success = send_signal('TERM', HARD_STOP_TIMEOUT) # if not stopped afer HARD_STOP_TIMEOUT, SIGKILL will be sent
|
64
|
+
exit!(success)
|
65
|
+
end
|
66
|
+
|
67
67
|
# setup logging
|
68
68
|
Logger.setup(@logger_options)
|
69
69
|
Logger.level = Configuration.log_level if Configuration.log_level
|
70
70
|
|
71
|
-
if options[:command] == 'stop'
|
72
|
-
puts(">> Raad service wrapper v#{VERSION} stopping")
|
73
|
-
send_signal('TERM', HARD_STOP_TIMEOUT) # if not stopped afer HARD_STOP_TIMEOUT, SIGKILL will be sent
|
74
|
-
exit!
|
75
|
-
end
|
76
71
|
puts(">> Raad service wrapper v#{VERSION} starting")
|
77
72
|
|
78
73
|
Dir.chdir(File.expand_path(File.dirname("./"))) unless Raad.test?
|
@@ -85,6 +80,87 @@ module Raad
|
|
85
80
|
|
86
81
|
private
|
87
82
|
|
83
|
+
# Run the service
|
84
|
+
#
|
85
|
+
# @return nil
|
86
|
+
def run_service
|
87
|
+
Logger.info("starting #{@service_name} service in #{Raad.env.to_s} mode")
|
88
|
+
|
89
|
+
at_exit do
|
90
|
+
Logger.info(">> Raad service wrapper stopped")
|
91
|
+
end
|
92
|
+
|
93
|
+
# store received signals into the @signal queue. here we want to avoid
|
94
|
+
# doing anything complex from within the trap block.
|
95
|
+
[:INT, :TERM, :QUIT].each do |sig|
|
96
|
+
trap(sig) {@monitor.synchronize{@signals << :STOP}}
|
97
|
+
end
|
98
|
+
|
99
|
+
# launch the signal handler thread. the idea here is to handle signal outside the
|
100
|
+
# trap block. the trap block will simply store the signal which this thread will
|
101
|
+
# retrieve and do whatever is required.
|
102
|
+
signals_thread = Thread.new do
|
103
|
+
Thread.current.abort_on_exception = true
|
104
|
+
loop do
|
105
|
+
signals = @monitor.synchronize{s = @signals.dup; @signals.clear; s}
|
106
|
+
|
107
|
+
if signals.include?(:STOP)
|
108
|
+
@stop_signaled = true
|
109
|
+
stop_service
|
110
|
+
break
|
111
|
+
end
|
112
|
+
|
113
|
+
sleep(0.5)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# launch the service thread and call start. we expect start not to return
|
118
|
+
# unless it is done or has been stopped.
|
119
|
+
service_thread = Thread.new do
|
120
|
+
Thread.current.abort_on_exception = true
|
121
|
+
service.start
|
122
|
+
stop_service unless @stop_signaled # don't stop twice if already called from the signals_thread
|
123
|
+
end
|
124
|
+
|
125
|
+
# use exit and not exit! to make sure the at_exit hooks are called, like the pid cleanup, etc.
|
126
|
+
exit(wait_or_kill(service_thread))
|
127
|
+
end
|
128
|
+
|
129
|
+
def stop_service
|
130
|
+
Logger.info("stopping #{@service_name} service")
|
131
|
+
service.stop if service.respond_to?(:stop)
|
132
|
+
end
|
133
|
+
|
134
|
+
# try to do a timeout join periodically on the given thread. if the join succeed then the stop
|
135
|
+
# sequence is successful and return true.
|
136
|
+
# Otherwise, on timeout if stop has beed signaled, wait a maximum of SOFT_STOP_TIMEOUT on the
|
137
|
+
# thread and kill it if the timeout is reached and return false in that case.
|
138
|
+
#
|
139
|
+
# @return [Boolean] true if the thread normally terminated, false if a kill was necessary
|
140
|
+
def wait_or_kill(thread)
|
141
|
+
while thread.join(SECOND).nil?
|
142
|
+
# the join has timed out, thread is still buzzy.
|
143
|
+
if @stop_signaled
|
144
|
+
# but if stop has been signalled, start "the final countdown" ♫
|
145
|
+
try = 0; join = nil
|
146
|
+
while (try += 1) <= SOFT_STOP_TIMEOUT && join.nil? do
|
147
|
+
join = thread.join(SECOND)
|
148
|
+
Logger.debug("waiting for service to stop #{try}/#{SOFT_STOP_TIMEOUT}") if join.nil?
|
149
|
+
end
|
150
|
+
if join.nil?
|
151
|
+
Logger.error("stop timeout exhausted, killing service thread")
|
152
|
+
thread.kill
|
153
|
+
return false
|
154
|
+
end
|
155
|
+
return true
|
156
|
+
end
|
157
|
+
end
|
158
|
+
true
|
159
|
+
end
|
160
|
+
|
161
|
+
# convert the service class name from CameCase to underscore
|
162
|
+
#
|
163
|
+
# @return [String] underscored service class name
|
88
164
|
def default_service_name
|
89
165
|
service.class.to_s.split('::').last.gsub(/(.)([A-Z])/,'\1_\2').downcase!
|
90
166
|
end
|
@@ -121,64 +197,12 @@ module Raad
|
|
121
197
|
service.respond_to?(:options_parser) ? service.options_parser(options_parser) : options_parser
|
122
198
|
end
|
123
199
|
|
124
|
-
# Output the servers options
|
200
|
+
# Output the servers options and exit Ruby
|
125
201
|
#
|
126
202
|
# @param opts [OptionsParser] The options parser
|
127
|
-
# @return [exit] This will exit Ruby
|
128
203
|
def show_options(opts)
|
129
204
|
puts(opts)
|
130
|
-
exit!
|
131
|
-
end
|
132
|
-
|
133
|
-
# Run the server
|
134
|
-
#
|
135
|
-
# @return [Nil]
|
136
|
-
def run_service
|
137
|
-
Logger.info("starting #{@service_name} service in #{Raad.env.to_s} mode")
|
138
|
-
|
139
|
-
at_exit do
|
140
|
-
Logger.info(">> Raad service wrapper stopped")
|
141
|
-
end
|
142
|
-
|
143
|
-
# by default exit on SIGTERM and SIGINT
|
144
|
-
[:INT, :TERM, :QUIT].each do |sig|
|
145
|
-
trap(sig) {stop_service}
|
146
|
-
end
|
147
|
-
|
148
|
-
@service_thread = Thread.new do
|
149
|
-
Thread.current.abort_on_exception = true
|
150
|
-
service.start
|
151
|
-
end
|
152
|
-
while @service_thread.join(SECOND).nil?
|
153
|
-
if @stopped
|
154
|
-
Logger.info("stopping #{@service_name} service")
|
155
|
-
service.stop if service.respond_to?(:stop)
|
156
|
-
wait_or_kill_service
|
157
|
-
return
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
unless @stopped
|
162
|
-
Logger.info("stopping #{@service_name} service")
|
163
|
-
service.stop if service.respond_to?(:stop)
|
164
|
-
end
|
165
|
-
end
|
166
|
-
|
167
|
-
def stop_service
|
168
|
-
@stopped = true
|
169
|
-
end
|
170
|
-
|
171
|
-
def wait_or_kill_service
|
172
|
-
try = 0; join = nil
|
173
|
-
while (try += 1) <= SOFT_STOP_TIMEOUT && join.nil? do
|
174
|
-
join = @service_thread.join(SECOND)
|
175
|
-
Logger.debug("waiting for service to stop #{try}/#{SOFT_STOP_TIMEOUT}") if join.nil?
|
176
|
-
end
|
177
|
-
if join.nil?
|
178
|
-
Logger.error("stop timeout exhausted, killing service thread")
|
179
|
-
@service_thread.kill
|
180
|
-
@service_thread.join
|
181
|
-
end
|
205
|
+
exit!(false)
|
182
206
|
end
|
183
207
|
|
184
208
|
end
|
data/lib/raad/unix_daemon.rb
CHANGED
@@ -58,25 +58,36 @@ module Daemonizable
|
|
58
58
|
Timeout.timeout(timeout) do
|
59
59
|
sleep 0.1 while Process.running?(pid)
|
60
60
|
end
|
61
|
+
true
|
61
62
|
else
|
62
63
|
puts(">> can't stop process, no pid found in #{@pid_file}")
|
64
|
+
false
|
63
65
|
end
|
64
66
|
rescue Timeout::Error
|
65
|
-
|
67
|
+
force_kill_and_remove_pid_file
|
66
68
|
rescue Interrupt
|
67
|
-
|
69
|
+
force_kill_and_remove_pid_file
|
68
70
|
rescue Errno::ESRCH # No such process
|
69
|
-
|
71
|
+
puts(">> can't stop process, no such process #{pid}")
|
72
|
+
remove_pid_file
|
73
|
+
false
|
70
74
|
end
|
71
75
|
|
72
|
-
def
|
73
|
-
if pid = read_pid_file
|
76
|
+
def force_kill_and_remove_pid_file
|
77
|
+
success = if (pid = read_pid_file)
|
74
78
|
puts(">> sending KILL signal to process #{pid}")
|
75
79
|
Process.kill("KILL", pid)
|
76
|
-
|
80
|
+
true
|
77
81
|
else
|
78
82
|
puts(">> can't stop process, no pid found in #{@pid_file}")
|
83
|
+
false
|
79
84
|
end
|
85
|
+
remove_pid_file
|
86
|
+
success
|
87
|
+
rescue Errno::ESRCH # No such process
|
88
|
+
puts(">> can't stop process, no such process #{pid}")
|
89
|
+
remove_pid_file
|
90
|
+
false
|
80
91
|
end
|
81
92
|
|
82
93
|
def read_pid_file
|
data/lib/raad/version.rb
CHANGED
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: raad
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.3.
|
5
|
+
version: 0.3.3
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Colin Surprenant
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-08-
|
13
|
+
date: 2011-08-26 00:00:00 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rubyforge
|