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.
@@ -0,0 +1,2 @@
1
+ # 0.3.1, 08-24-2011
2
+ - initial release. support for MRI 1.8 & 1.9 only
@@ -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
@@ -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
@@ -0,0 +1,7 @@
1
+ require 'raad/version'
2
+ require 'raad/env'
3
+ require 'raad/logger'
4
+ require 'raad/configuration'
5
+ require 'raad/unix_daemon'
6
+ require 'raad/runner'
7
+ require 'raad/service'
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,3 @@
1
+ module Raad
2
+ VERSION = "0.3.1"
3
+ end unless defined?(Raad::VERSION)
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
+