raad 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+