raad 0.3.1
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 +2 -0
- data/LICENSE.md +77 -0
- data/README.md +98 -0
- data/lib/raad.rb +7 -0
- data/lib/raad/configuration.rb +46 -0
- data/lib/raad/env.rb +54 -0
- data/lib/raad/logger.rb +64 -0
- data/lib/raad/runner.rb +185 -0
- data/lib/raad/service.rb +70 -0
- data/lib/raad/unix_daemon.rb +111 -0
- data/lib/raad/version.rb +3 -0
- metadata +97 -0
data/CHANGELOG.md
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# Raad
|
2
|
+
|
3
|
+
Raad - Ruby as a Daemon lightweight service wrapper
|
4
|
+
Authored by Colin Surprenant, [@colinsurprenant][twitter], [colin.surprenant@needium.com][needium], [colin.surprenant@gmail.com][gmail], [http://github.com/colinsurprenant][github]
|
5
|
+
|
6
|
+
Copyright 2011 PraizedMedia Inc.
|
7
|
+
|
8
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
9
|
+
you may not use this file except in compliance with the License.
|
10
|
+
You may obtain a copy of the License at
|
11
|
+
|
12
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
13
|
+
|
14
|
+
Unless required by applicable law or agreed to in writing, software
|
15
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
16
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
17
|
+
See the License for the specific language governing permissions and
|
18
|
+
limitations under the License.
|
19
|
+
|
20
|
+
-------------------------------------------------------------------------------
|
21
|
+
|
22
|
+
Portions of this code are from the Thin project (https://github.com/macournoyer/thin) and under the following license:
|
23
|
+
Ruby License, http://www.ruby-lang.org/en/LICENSE.txt.
|
24
|
+
|
25
|
+
-------------------------------------------------------------------------------
|
26
|
+
|
27
|
+
Portions of this code are from the Goliath project (https://github.com/postrank-labs/goliath/) and under the following license:
|
28
|
+
Copyright (c) 2011 PostRank Inc.
|
29
|
+
|
30
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
31
|
+
of this software and associated documentation files (the "Software"), to
|
32
|
+
deal in the Software without restriction, including without limitation the
|
33
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
34
|
+
sell copies of the Software, and to permit persons to whom the Software is
|
35
|
+
furnished to do so, subject to the following conditions:
|
36
|
+
|
37
|
+
The above copyright notice and this permission notice shall be included in
|
38
|
+
all copies or substantial portions of the Software.
|
39
|
+
|
40
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
41
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
42
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
43
|
+
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
44
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
45
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
46
|
+
|
47
|
+
-------------------------------------------------------------------------------
|
48
|
+
|
49
|
+
Portions of this code are from the Sinatra project (https://github.com/bmizerany/sinatra) and under the following license:
|
50
|
+
Copyright (c) 2007, 2008, 2009, 2010, 2011 Blake Mizerany
|
51
|
+
|
52
|
+
Permission is hereby granted, free of charge, to any person
|
53
|
+
obtaining a copy of this software and associated documentation
|
54
|
+
files (the "Software"), to deal in the Software without
|
55
|
+
restriction, including without limitation the rights to use,
|
56
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
57
|
+
copies of the Software, and to permit persons to whom the
|
58
|
+
Software is furnished to do so, subject to the following
|
59
|
+
conditions:
|
60
|
+
|
61
|
+
The above copyright notice and this permission notice shall be
|
62
|
+
included in all copies or substantial portions of the Software.
|
63
|
+
|
64
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
65
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
66
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
67
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
68
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
69
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
70
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
71
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
72
|
+
|
73
|
+
|
74
|
+
[needium]: colin.surprenant@needium.com
|
75
|
+
[gmail]: colin.surprenant@gmail.com
|
76
|
+
[twitter]: http://twitter.com/colinsurprenant
|
77
|
+
[github]: http://github.com/colinsurprenant
|
data/README.md
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
# Raad v0.3.1
|
2
|
+
|
3
|
+
Raad - Ruby as a Daemon lightweight service wrapper.
|
4
|
+
|
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 seemslessy as a daemon or a normal console app.
|
7
|
+
|
8
|
+
Raad provides daemon control using the start/stop commands. Your code can optionnally use the Raad
|
9
|
+
logging module.
|
10
|
+
|
11
|
+
## Example
|
12
|
+
Create a class with a start and a stop method. Just by requiring 'raad', your class will be
|
13
|
+
wrapped by Raad and daemonizable.
|
14
|
+
|
15
|
+
require 'raad'
|
16
|
+
|
17
|
+
class SimpleDaemon
|
18
|
+
def start
|
19
|
+
@stopped = false
|
20
|
+
while !@stopped
|
21
|
+
Raad::Logger.info("simple_daemon running")
|
22
|
+
sleep(1)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def stop
|
27
|
+
@stopped = true
|
28
|
+
Raad::Logger.info("simple_daemon stopped")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
run it in console mode, ^C will stop it, calling the stop method
|
33
|
+
$ ruby simple_daemon.rb start
|
34
|
+
|
35
|
+
run it daemonized, by default ./simple_daemon.log and ./simple_daemon.pid will be created
|
36
|
+
$ ruby simple_daemon.rb -d start
|
37
|
+
|
38
|
+
stop daemon, removing ./simple_daemon.pid
|
39
|
+
$ ruby simple_daemon.rb stop
|
40
|
+
|
41
|
+
## Documentation
|
42
|
+
|
43
|
+
### Supported rubies
|
44
|
+
Raad has only been tested on MRI 1.8 and 1.9.
|
45
|
+
|
46
|
+
### Command line options
|
47
|
+
usage: ruby <service>.rb [options] start|stop
|
48
|
+
|
49
|
+
Raad common options:
|
50
|
+
-e, --environment NAME set the execution environment (default: development)
|
51
|
+
-l, --log FILE log to file (default: in console mode: no, daemonized: <service>.log)
|
52
|
+
-s, --stdout log to stdout (default: in console mode: true, daemonized: false)
|
53
|
+
-c, --config FILE config file (default: ./config/<service>.rb)
|
54
|
+
-d, --daemonize run daemonized in the background (default: false)
|
55
|
+
-P, --pid FILE pid file when daemonized (default: <service>.pid)
|
56
|
+
-r, --redirect FILE redirect stdout to FILE when daemonized (default: no)
|
57
|
+
-n, --name NAME daemon process name (default: <service>)
|
58
|
+
-v, --verbose enable verbose logging (default: false)
|
59
|
+
-h, --help display help message
|
60
|
+
|
61
|
+
Note that the command line options will always override any config file settings if present.
|
62
|
+
### Config file usage
|
63
|
+
tbd.
|
64
|
+
|
65
|
+
### Logging
|
66
|
+
tbd.
|
67
|
+
|
68
|
+
### Adding custom command line options
|
69
|
+
tbd.
|
70
|
+
|
71
|
+
### Stop sequence details
|
72
|
+
tbd.
|
73
|
+
|
74
|
+
## TODO
|
75
|
+
- better doc, more examples
|
76
|
+
- specs
|
77
|
+
- JRuby support
|
78
|
+
|
79
|
+
## Dependencies
|
80
|
+
The Log4r gem (~> 1.1.9) is required.
|
81
|
+
|
82
|
+
## Author
|
83
|
+
Authored by Colin Surprenant, [@colinsurprenant][twitter], [colin.surprenant@needium.com][needium], [colin.surprenant@gmail.com][gmail], [http://github.com/colinsurprenant][github]
|
84
|
+
|
85
|
+
## Acknoledgements
|
86
|
+
Thanks to the Thin ([https://github.com/macournoyer/thin][thin]), Goliath ([https://github.com/postrank-labs/goliath/][goliath])
|
87
|
+
and Sinatra ([https://github.com/bmizerany/sinatra][sinatra]) projects for providing inspiration and/or code!
|
88
|
+
|
89
|
+
## License
|
90
|
+
Raada is distributed under the Apache License, Version 2.0. See the LICENSE.md file.
|
91
|
+
|
92
|
+
[needium]: colin.surprenant@needium.com
|
93
|
+
[gmail]: colin.surprenant@gmail.com
|
94
|
+
[twitter]: http://twitter.com/colinsurprenant
|
95
|
+
[github]: http://github.com/colinsurprenant
|
96
|
+
[thin]: https://github.com/macournoyer/thin
|
97
|
+
[goliath]: https://github.com/postrank-labs/goliath/
|
98
|
+
[sinatra]: https://github.com/bmizerany/sinatra
|
data/lib/raad.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
module Raad
|
2
|
+
module Configuration
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def self.init(&block)
|
6
|
+
instance_eval(&block)
|
7
|
+
end
|
8
|
+
|
9
|
+
def [](key)
|
10
|
+
config[key]
|
11
|
+
end
|
12
|
+
|
13
|
+
# Loads a configuration file and eval its content in the service object context
|
14
|
+
#
|
15
|
+
# @param file [String] The file to load, if not set will use ./config/{servive_name}
|
16
|
+
# @return [Nil]
|
17
|
+
def load(file = nil)
|
18
|
+
return unless File.exists?(file)
|
19
|
+
self.instance_eval(IO.read(file))
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# cosmetic alias for config dsl
|
25
|
+
def configuration(&block)
|
26
|
+
Configuration.init(&block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def set(key, value)
|
30
|
+
config[key] = value
|
31
|
+
end
|
32
|
+
|
33
|
+
def config
|
34
|
+
@config ||= Hash.new
|
35
|
+
end
|
36
|
+
|
37
|
+
def method_missing(sym, *args)
|
38
|
+
if sym.to_s =~ /(.+)=$/
|
39
|
+
config[$1] = args.first
|
40
|
+
else
|
41
|
+
config[sym]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
data/lib/raad/env.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
module Raad
|
2
|
+
|
3
|
+
@env = :development
|
4
|
+
|
5
|
+
# Retrieves the current environment
|
6
|
+
#
|
7
|
+
# @return [Symbol] the current environment
|
8
|
+
def env
|
9
|
+
@env
|
10
|
+
end
|
11
|
+
|
12
|
+
# Sets the current environment
|
13
|
+
#
|
14
|
+
# @param [String or Symbol] env the environment [development|production|stage|test]
|
15
|
+
def env=(env)
|
16
|
+
case(env.to_s)
|
17
|
+
when 'dev', 'development' then @env = :development
|
18
|
+
when 'prod', 'production' then @env = :production
|
19
|
+
when 'stage', 'staging' then @env = :stage
|
20
|
+
when 'test' then @env = :test
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Determines if we are in the production environment
|
25
|
+
#
|
26
|
+
# @return [Boolean] true if current environemnt is production, false otherwise
|
27
|
+
def production?
|
28
|
+
@env == :production
|
29
|
+
end
|
30
|
+
|
31
|
+
# Determines if we are in the development environment
|
32
|
+
#
|
33
|
+
# @return [Boolean] true if current environemnt is development, false otherwise
|
34
|
+
def development?
|
35
|
+
@env == :development
|
36
|
+
end
|
37
|
+
|
38
|
+
# Determines if we are in the staging environment
|
39
|
+
#
|
40
|
+
# @return [Boolean] true if current environemnt is staging, false otherwise
|
41
|
+
def stage?
|
42
|
+
@env == :stage
|
43
|
+
end
|
44
|
+
|
45
|
+
# Determines if we are in the test environment
|
46
|
+
#
|
47
|
+
# @return [Boolean] true if current environemnt is test, false otherwise
|
48
|
+
def test?
|
49
|
+
@env == :test
|
50
|
+
end
|
51
|
+
|
52
|
+
module_function :env, :env=, :production?, :development?, :stage?, :test?
|
53
|
+
|
54
|
+
end
|
data/lib/raad/logger.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'log4r'
|
2
|
+
|
3
|
+
module Raad
|
4
|
+
module Logger
|
5
|
+
|
6
|
+
extend self
|
7
|
+
|
8
|
+
# Sets up the logging for the runner
|
9
|
+
# @return [Logger] The logger object
|
10
|
+
def setup(options = {})
|
11
|
+
@log = Log4r::Logger.new('raad')
|
12
|
+
|
13
|
+
log_format = Log4r::PatternFormatter.new(:pattern => "[#{Process.pid}:%l] %d :: %m")
|
14
|
+
setup_file_logger(@log, log_format, options[:file]) if options[:file]
|
15
|
+
setup_stdout_logger(@log, log_format) if options[:stdout]
|
16
|
+
|
17
|
+
@verbose = !!options[:verbose]
|
18
|
+
|
19
|
+
@log.level = @verbose ? Log4r::DEBUG : Log4r::INFO
|
20
|
+
@log
|
21
|
+
end
|
22
|
+
|
23
|
+
def level=(l)
|
24
|
+
levels = {
|
25
|
+
:debug => Log4r::DEBUG,
|
26
|
+
:info => Log4r::INFO,
|
27
|
+
:warn => Log4r::WARN,
|
28
|
+
:error => Log4r::ERROR,
|
29
|
+
}
|
30
|
+
@log.level = @verbose ? Log4r::DEBUG : levels[l]
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# setup file logging
|
36
|
+
#
|
37
|
+
# @param log [Logger] The logger to add file logging too
|
38
|
+
# @param log_format [Log4r::Formatter] The log format to use
|
39
|
+
# @return [Nil]
|
40
|
+
def setup_file_logger(log, log_format, file)
|
41
|
+
FileUtils.mkdir_p(File.dirname(file))
|
42
|
+
|
43
|
+
@log.add(Log4r::FileOutputter.new('fileOutput', {
|
44
|
+
:filename => file,
|
45
|
+
:trunc => false,
|
46
|
+
:formatter => log_format
|
47
|
+
}))
|
48
|
+
end
|
49
|
+
|
50
|
+
# setup stdout logging
|
51
|
+
#
|
52
|
+
# @param log [Logger] The logger to add stdout logging too
|
53
|
+
# @param log_format [Log4r::Formatter] The log format to use
|
54
|
+
# @return [Nil]
|
55
|
+
def setup_stdout_logger(log, log_format)
|
56
|
+
@log.add(Log4r::StdoutOutputter.new('console', :formatter => log_format))
|
57
|
+
end
|
58
|
+
|
59
|
+
def method_missing(sym, *args)
|
60
|
+
@log.send(sym, *args)
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
data/lib/raad/runner.rb
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
module Raad
|
5
|
+
class Runner
|
6
|
+
include Daemonizable
|
7
|
+
|
8
|
+
SECOND = 1
|
9
|
+
SOFT_STOP_TIMEOUT = 58 * SECOND
|
10
|
+
HARD_STOP_TIMEOUT = 60 * SECOND
|
11
|
+
|
12
|
+
# The pid file for the server
|
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
|
23
|
+
|
24
|
+
# Create a new Runner
|
25
|
+
#
|
26
|
+
# @param argv [Array] The command line arguments
|
27
|
+
# @param service [Object] The service to execute
|
28
|
+
def initialize(argv, service)
|
29
|
+
create_options_parser(service).parse!(argv)
|
30
|
+
|
31
|
+
options[:command] = argv[0].to_s.downcase
|
32
|
+
unless ['start', 'stop'].include?(options[:command])
|
33
|
+
puts(">> start|stop command is required")
|
34
|
+
exit!
|
35
|
+
end
|
36
|
+
|
37
|
+
@service = service
|
38
|
+
@service_name = nil
|
39
|
+
@logger_options = nil
|
40
|
+
@pid_file = nil
|
41
|
+
@service_thread = nil
|
42
|
+
@stopped = false
|
43
|
+
end
|
44
|
+
|
45
|
+
def run
|
46
|
+
# first load config if present
|
47
|
+
Configuration.load(options[:config] || File.expand_path("./config/#{default_service_name}.rb"))
|
48
|
+
|
49
|
+
# then set vars which depends on configuration
|
50
|
+
@service_name = options[:name] || Configuration.daemon_name || default_service_name
|
51
|
+
|
52
|
+
# ajust "dynamic defaults"
|
53
|
+
unless options[:log_file]
|
54
|
+
options[:log_file] = (options[:daemonize] ? File.expand_path("#{@service_name}.log") : nil)
|
55
|
+
end
|
56
|
+
unless options[:log_stdout]
|
57
|
+
options[:log_stdout] = !options[:daemonize]
|
58
|
+
end
|
59
|
+
|
60
|
+
@logger_options = {
|
61
|
+
:file => options.delete(:log_file),
|
62
|
+
:stdout => options.delete(:log_stdout),
|
63
|
+
:verbose => options.delete(:verbose),
|
64
|
+
}
|
65
|
+
@pid_file = options.delete(:pid_file) || "./#{@service_name}.pid"
|
66
|
+
|
67
|
+
# setup logging
|
68
|
+
Logger.setup(@logger_options)
|
69
|
+
Logger.level = Configuration.log_level if Configuration.log_level
|
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
|
+
puts(">> Raad service wrapper v#{VERSION} starting")
|
77
|
+
|
78
|
+
Dir.chdir(File.expand_path(File.dirname("./"))) unless Raad.test?
|
79
|
+
|
80
|
+
jruby = (defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby')
|
81
|
+
raise("daemonize not supported in JRuby") if options[:daemonize] && jruby
|
82
|
+
|
83
|
+
options[:daemonize] ? daemonize(@service_name, options[:redirect]) {run_service} : run_service
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def default_service_name
|
89
|
+
service.class.to_s.split('::').last.gsub(/(.)([A-Z])/,'\1_\2').downcase!
|
90
|
+
end
|
91
|
+
|
92
|
+
# Create the options parser
|
93
|
+
#
|
94
|
+
# @return [OptionParser] Creates the options parser for the runner with the default options
|
95
|
+
def create_options_parser(service)
|
96
|
+
@options ||= {
|
97
|
+
:daemonize => false,
|
98
|
+
:verbose => false,
|
99
|
+
}
|
100
|
+
|
101
|
+
options_parser ||= OptionParser.new do |opts|
|
102
|
+
opts.banner = "usage: ruby <service>.rb [options] start|stop"
|
103
|
+
|
104
|
+
opts.separator ""
|
105
|
+
opts.separator "Raad common options:"
|
106
|
+
|
107
|
+
opts.on('-e', '--environment NAME', "set the execution environment (default: #{Raad.env.to_s})") { |val| Raad.env = val }
|
108
|
+
|
109
|
+
opts.on('-l', '--log FILE', "log to file (default: in console mode: no, daemonized: <service>.log)") { |file| @options[:log_file] = file }
|
110
|
+
opts.on('-s', '--stdout', "log to stdout (default: in console mode: true, daemonized: false)") { |v| @options[:log_stdout] = v }
|
111
|
+
|
112
|
+
opts.on('-c', '--config FILE', "config file (default: ./config/<service>.rb)") { |v| @options[:config] = v }
|
113
|
+
opts.on('-d', '--daemonize', "run daemonized in the background (default: #{@options[:daemonize]})") { |v| @options[:daemonize] = v }
|
114
|
+
opts.on('-P', '--pid FILE', "pid file when daemonized (default: <service>.pid)") { |file| @options[:pid_file] = file }
|
115
|
+
opts.on('-r', '--redirect FILE', "redirect stdout to FILE when daemonized (default: no)") { |v| @options[:redirect] = v }
|
116
|
+
opts.on('-n', '--name NAME', "daemon process name (default: <service>)") { |v| @options[:name] = v }
|
117
|
+
opts.on('-v', '--verbose', "enable verbose logging (default: #{@options[:verbose]})") { |v| @options[:verbose] = v }
|
118
|
+
|
119
|
+
opts.on('-h', '--help', 'display help message') { show_options(opts) }
|
120
|
+
end
|
121
|
+
service.respond_to?(:options_parser) ? service.options_parser(options_parser) : options_parser
|
122
|
+
end
|
123
|
+
|
124
|
+
# Output the servers options
|
125
|
+
#
|
126
|
+
# @param opts [OptionsParser] The options parser
|
127
|
+
# @return [exit] This will exit Ruby
|
128
|
+
def show_options(opts)
|
129
|
+
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
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
end
|
data/lib/raad/service.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
module Raad
|
2
|
+
|
3
|
+
# The main execution class for Raad. This will execute in the at_exit
|
4
|
+
# handler to run the server.
|
5
|
+
class Service
|
6
|
+
|
7
|
+
# Set of caller regex's to be skippe when looking for our API file
|
8
|
+
CALLERS_TO_IGNORE = [ # :nodoc:
|
9
|
+
/\/raad(\/(service))?\.rb$/, # all raad code
|
10
|
+
/rubygems\/custom_require\.rb$/, # rubygems require hacks
|
11
|
+
/bundler(\/runtime)?\.rb/, # bundler require hacks
|
12
|
+
/<internal:/ # internal in ruby >= 1.9.2
|
13
|
+
]
|
14
|
+
|
15
|
+
# @todo add rubinius (and hopefully other VM impls) ignore patterns ...
|
16
|
+
CALLERS_TO_IGNORE.concat(RUBY_IGNORE_CALLERS) if defined?(RUBY_IGNORE_CALLERS)
|
17
|
+
|
18
|
+
# Like Kernel#caller but excluding certain magic entries and without
|
19
|
+
# line / method information; the resulting array contains filenames only.
|
20
|
+
def self.caller_files
|
21
|
+
caller_locations.map { |file, line| file }
|
22
|
+
end
|
23
|
+
|
24
|
+
# Like caller_files, but containing Arrays rather than strings with the
|
25
|
+
# first element being the file, and the second being the line.
|
26
|
+
def self.caller_locations
|
27
|
+
caller(1).
|
28
|
+
map { |line| line.split(/:(?=\d|in )/)[0,2] }.
|
29
|
+
reject { |file, line| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } }
|
30
|
+
end
|
31
|
+
|
32
|
+
# Find the service_file that was used to execute the service
|
33
|
+
#
|
34
|
+
# @return [String] The service file
|
35
|
+
def self.service_file
|
36
|
+
c = caller_files.first
|
37
|
+
c = $0 if !c || c.empty?
|
38
|
+
c
|
39
|
+
end
|
40
|
+
|
41
|
+
# Execute the service
|
42
|
+
#
|
43
|
+
# @return [Nil]
|
44
|
+
def self.run!
|
45
|
+
file = File.basename(service_file, '.rb')
|
46
|
+
service = Object.module_eval(camel_case(file)).new
|
47
|
+
|
48
|
+
runner = Raad::Runner.new(ARGV, service)
|
49
|
+
runner.run
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
# Convert a string to camel case
|
55
|
+
#
|
56
|
+
# @param str [String] The string to convert
|
57
|
+
# @return [String] The camel cased string
|
58
|
+
def self.camel_case(str)
|
59
|
+
return str if str !~ /_/ && str =~ /[A-Z]+.*/
|
60
|
+
|
61
|
+
str.split('_').map { |e| e.capitalize }.join
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
at_exit do
|
66
|
+
if $!.nil? && $0 == Raad::Service.service_file
|
67
|
+
Service.run!
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'etc'
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
module Process
|
5
|
+
|
6
|
+
def running?(pid)
|
7
|
+
Process.getpgid(pid) != -1
|
8
|
+
rescue Errno::EPERM
|
9
|
+
true
|
10
|
+
rescue Errno::ESRCH
|
11
|
+
false
|
12
|
+
end
|
13
|
+
|
14
|
+
module_function :running?
|
15
|
+
end
|
16
|
+
|
17
|
+
# Raised when the pid file already exist starting as a daemon.
|
18
|
+
class PidFileExist < RuntimeError; end
|
19
|
+
|
20
|
+
# module Daemonizable requires that the including class defines the @pid_file instance variable
|
21
|
+
module Daemonizable
|
22
|
+
|
23
|
+
def pid
|
24
|
+
File.exist?(@pid_file) ? open(@pid_file).read.to_i : nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def daemonize(name, stdout_file)
|
28
|
+
remove_stale_pid_file
|
29
|
+
pwd = Dir.pwd
|
30
|
+
|
31
|
+
# do the double fork dance
|
32
|
+
Process.fork do
|
33
|
+
Process.setsid
|
34
|
+
exit if fork
|
35
|
+
$0 = name # set process name
|
36
|
+
|
37
|
+
File.umask(0000) # file mode creation mask to 000 to allow creation of files with any required permission late
|
38
|
+
Dir.chdir(pwd)
|
39
|
+
write_pid_file
|
40
|
+
|
41
|
+
# redirect stdout into a log
|
42
|
+
STDIN.reopen('/dev/null')
|
43
|
+
stdout_file ? STDOUT.reopen(stdout_file, "a") : STDOUT.reopen('/dev/null', 'a')
|
44
|
+
STDERR.reopen(STDOUT)
|
45
|
+
|
46
|
+
at_exit do
|
47
|
+
remove_pid_file
|
48
|
+
end
|
49
|
+
|
50
|
+
yield
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def send_signal(signal, timeout = 60)
|
55
|
+
if pid = read_pid_file
|
56
|
+
puts(">> sending #{signal} signal to process #{pid}")
|
57
|
+
Process.kill(signal, pid)
|
58
|
+
Timeout.timeout(timeout) do
|
59
|
+
sleep 0.1 while Process.running?(pid)
|
60
|
+
end
|
61
|
+
else
|
62
|
+
puts(">> can't stop process, no pid found in #{@pid_file}")
|
63
|
+
end
|
64
|
+
rescue Timeout::Error
|
65
|
+
force_kill
|
66
|
+
rescue Interrupt
|
67
|
+
force_kill
|
68
|
+
rescue Errno::ESRCH # No such process
|
69
|
+
force_kill
|
70
|
+
end
|
71
|
+
|
72
|
+
def force_kill
|
73
|
+
if pid = read_pid_file
|
74
|
+
puts(">> sending KILL signal to process #{pid}")
|
75
|
+
Process.kill("KILL", pid)
|
76
|
+
File.delete(@pid_file) if File.exist?(@pid_file)
|
77
|
+
else
|
78
|
+
puts(">> can't stop process, no pid found in #{@pid_file}")
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def read_pid_file
|
83
|
+
if File.file?(@pid_file) && pid = File.read(@pid_file)
|
84
|
+
pid.to_i
|
85
|
+
else
|
86
|
+
nil
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def remove_pid_file
|
91
|
+
File.delete(@pid_file) if @pid_file && File.exists?(@pid_file)
|
92
|
+
end
|
93
|
+
|
94
|
+
def write_pid_file
|
95
|
+
puts(">> writing pid to #{@pid_file}")
|
96
|
+
open(@pid_file,"w") { |f| f.write(Process.pid) }
|
97
|
+
File.chmod(0644, @pid_file)
|
98
|
+
end
|
99
|
+
|
100
|
+
def remove_stale_pid_file
|
101
|
+
if File.exist?(@pid_file)
|
102
|
+
if pid && Process.running?(pid)
|
103
|
+
raise PidFileExist, "#{@pid_file} exists and process #{pid} is runnig. stop the process or delete #{@pid_file}"
|
104
|
+
else
|
105
|
+
puts(">> deleting stale pid file #{@pid_file}")
|
106
|
+
remove_pid_file
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
data/lib/raad/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: raad
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.3.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Colin Surprenant
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-08-24 00:00:00 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rubyforge
|
17
|
+
prerelease: false
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
type: :development
|
25
|
+
version_requirements: *id001
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: rspec
|
28
|
+
prerelease: false
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ~>
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 2.5.0
|
35
|
+
type: :development
|
36
|
+
version_requirements: *id002
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: log4r
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.1.9
|
46
|
+
type: :runtime
|
47
|
+
version_requirements: *id003
|
48
|
+
description: Ruby as a Daemon lightweight service wrapper
|
49
|
+
email:
|
50
|
+
- colin.surprenant@gmail.com
|
51
|
+
executables: []
|
52
|
+
|
53
|
+
extensions: []
|
54
|
+
|
55
|
+
extra_rdoc_files: []
|
56
|
+
|
57
|
+
files:
|
58
|
+
- lib/raad/configuration.rb
|
59
|
+
- lib/raad/env.rb
|
60
|
+
- lib/raad/logger.rb
|
61
|
+
- lib/raad/runner.rb
|
62
|
+
- lib/raad/service.rb
|
63
|
+
- lib/raad/unix_daemon.rb
|
64
|
+
- lib/raad/version.rb
|
65
|
+
- lib/raad.rb
|
66
|
+
- README.md
|
67
|
+
- CHANGELOG.md
|
68
|
+
- LICENSE.md
|
69
|
+
homepage: http://github.com/praized/raad
|
70
|
+
licenses: []
|
71
|
+
|
72
|
+
post_install_message:
|
73
|
+
rdoc_options: []
|
74
|
+
|
75
|
+
require_paths:
|
76
|
+
- lib
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
78
|
+
none: false
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: "0"
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: 1.3.0
|
89
|
+
requirements: []
|
90
|
+
|
91
|
+
rubyforge_project: raad
|
92
|
+
rubygems_version: 1.8.9
|
93
|
+
signing_key:
|
94
|
+
specification_version: 3
|
95
|
+
summary: Ruby as a Daemon
|
96
|
+
test_files: []
|
97
|
+
|