raad 0.3.3 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,4 +5,8 @@
5
5
  - reworked runner stop sequence & signals trapping
6
6
  - force kill + pid removal bug
7
7
  - wrong exit code bug
8
- - comments & cosmetics
8
+ - comments & cosmetics
9
+
10
+ # 0.4, 09-05-2011
11
+ - using SignalTrampoline for safe signal handling
12
+ - JRuby support
data/LICENSE.md CHANGED
@@ -70,6 +70,15 @@ Authored by Colin Surprenant, [@colinsurprenant][twitter], [colin.surprenant@nee
70
70
  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
71
71
  OTHER DEALINGS IN THE SOFTWARE.
72
72
 
73
+ -------------------------------------------------------------------------------
74
+
75
+ Portions of this code are from the Spoon project https://github.com/headius/spoon) and under the following license:
76
+
77
+ Licensed under the Apache License, Version 2.0 (the "License");
78
+ you may not use this file except in compliance with the License.
79
+ You may obtain a copy of the License at
80
+
81
+ http://www.apache.org/licenses/LICENSE-2.0
73
82
 
74
83
  [needium]: colin.surprenant@needium.com
75
84
  [gmail]: colin.surprenant@gmail.com
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
- # Raad v0.3.3
1
+ # Raad v0.4.0
2
2
 
3
3
  Raad - Ruby as a daemon lightweight service wrapper.
4
4
 
5
- Raad is a non-intrusive, lightweight, simple Ruby daemon maker. Basically A simple class which implements
5
+ Raad is a non-intrusive, lightweight, simple Ruby daemon wrapper. Basically A simple class which implements
6
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
@@ -18,7 +18,7 @@ gem install raad
18
18
  gem "raad", :git => "git://github.com/praized/raad.git", :branch => "master"
19
19
 
20
20
  #### Released gem
21
- gem "raad", "~> 0.3.3"
21
+ gem "raad", "~> 0.4.0"
22
22
 
23
23
  ## Example
24
24
  Create a class with a start and a stop method. Just by requiring 'raad', your class will be
@@ -52,8 +52,9 @@ wrapped by Raad and daemonizable.
52
52
 
53
53
  ## Documentation
54
54
 
55
- ### Supported rubies
56
- Raad has only been tested on MRI 1.8 and 1.9.
55
+ ### Supported rubies and environments
56
+ Raad has been tested on MRI 1.8.7, MRI 1.9.2, REE 1.8.7, JRuby 1.6.4 under OSX 10.6.8 and Linux Ubuntu 10.04
57
+
57
58
 
58
59
  ### Command line options
59
60
  usage: ruby <service>.rb [options] start|stop
@@ -62,41 +63,66 @@ Raad has only been tested on MRI 1.8 and 1.9.
62
63
  -e, --environment NAME set the execution environment (default: development)
63
64
  -l, --log FILE log to file (default: in console mode: no, daemonized: <service>.log)
64
65
  -s, --stdout log to stdout (default: in console mode: true, daemonized: false)
66
+ -v, --verbose enable verbose logging (default: false)
67
+ --pattern PATTERN log4r log formatter pattern
65
68
  -c, --config FILE config file (default: ./config/<service>.rb)
66
69
  -d, --daemonize run daemonized in the background (default: false)
67
70
  -P, --pid FILE pid file when daemonized (default: <service>.pid)
68
71
  -r, --redirect FILE redirect stdout to FILE when daemonized (default: no)
69
72
  -n, --name NAME daemon process name (default: <service>)
70
- -v, --verbose enable verbose logging (default: false)
73
+ --timeout SECONDS seconds to wait before force stopping the service (default: 60)
71
74
  -h, --help display help message
72
75
 
73
76
  Note that the command line options will always override any config file settings if present.
74
- ### Config file usage
77
+ ### Configuration and options
75
78
  tbd.
76
79
 
77
- ### Logging
80
+ ### Adding custom command line options
78
81
  tbd.
79
82
 
80
- ### Adding custom command line options
83
+ ### Logging
81
84
  tbd.
82
85
 
83
86
  ### Stop sequence details
84
87
  tbd.
85
88
 
89
+ ### Testing
90
+ There are specs and a validation suite which ca be run in the current ruby environment:
91
+
92
+ - rake spec
93
+ - rake validation
94
+
95
+ Also, specs and validations can be run in all currently tested Ruby environement. For this [RVM][rvm] is required and the following rubies must be installed:
96
+
97
+ - ruby-1.8.7
98
+ - ree-1.8.7
99
+ - ruby-1.9.2
100
+ - jruby-1.6.4
101
+
102
+ In each of these rubies, the gemset @raad containing log4r (~> 1.1.9), rake (~> 0.9.2) and rspec (~> 2.6.0) must be created.
103
+
104
+ This RVM environment can be created/updated using:
105
+
106
+ - rake rvm_setup
107
+
108
+ To launch the tests for all rubies use:
109
+
110
+ - rake specs
111
+ - rake validations
112
+
86
113
  ## TODO
87
- - better doc, more examples
88
- - specs
89
- - JRuby support
114
+ - better doc
115
+ - more examples
90
116
 
91
117
  ## Dependencies
92
- The Log4r gem (~> 1.1.9) is required.
118
+ - For normal usage, the log4r gem (~> 1.1.9) is required.
119
+ - For testings, the rspec (~> 2.6.0), rake (~> 0.9.2) gems and [RVM][rvm] are required.
93
120
 
94
121
  ## Author
95
- Authored by Colin Surprenant, [@colinsurprenant][twitter], [colin.surprenant@needium.com][needium], [colin.surprenant@gmail.com][gmail], [http://github.com/colinsurprenant][github]
122
+ Colin Surprenant, [@colinsurprenant][twitter], [colin.surprenant@needium.com][needium], [colin.surprenant@gmail.com][gmail], [http://github.com/colinsurprenant][github]
96
123
 
97
124
  ## Acknowledgements
98
- Thanks to the Thin ([https://github.com/macournoyer/thin][thin]), Goliath ([https://github.com/postrank-labs/goliath/][goliath])
99
- and Sinatra ([https://github.com/bmizerany/sinatra][sinatra]) projects for providing inspiration and/or code!
125
+ Thanks to the Thin ([https://github.com/macournoyer/thin][thin]), Goliath ([https://github.com/postrank-labs/goliath][goliath]), Sinatra ([https://github.com/bmizerany/sinatra][sinatra]) and Spoon ([https://github.com/headius/spoon][spoon]) projects for providing inspiration and/or code!
100
126
 
101
127
  ## License
102
128
  Raad is distributed under the Apache License, Version 2.0. See the LICENSE.md file.
@@ -106,5 +132,7 @@ Raad is distributed under the Apache License, Version 2.0. See the LICENSE.md fi
106
132
  [twitter]: http://twitter.com/colinsurprenant
107
133
  [github]: http://github.com/colinsurprenant
108
134
  [thin]: https://github.com/macournoyer/thin
109
- [goliath]: https://github.com/postrank-labs/goliath/
135
+ [goliath]: https://github.com/postrank-labs/goliath
110
136
  [sinatra]: https://github.com/bmizerany/sinatra
137
+ [spoon]: https://github.com/headius/spoon
138
+ [rvm]: http://beginrescueend.com/
@@ -3,5 +3,6 @@ require 'raad/env'
3
3
  require 'raad/logger'
4
4
  require 'raad/configuration'
5
5
  require 'raad/unix_daemon'
6
+ require 'raad/signal_trampoline'
6
7
  require 'raad/runner'
7
8
  require 'raad/service'
@@ -1,15 +1,17 @@
1
+ require 'rbconfig'
2
+
1
3
  module Raad
2
4
 
3
5
  @env = :development
4
6
 
5
- # Retrieves the current environment
7
+ # retrieves the current environment
6
8
  #
7
9
  # @return [Symbol] the current environment
8
10
  def env
9
11
  @env
10
12
  end
11
13
 
12
- # Sets the current environment
14
+ # sets the current environment
13
15
  #
14
16
  # @param [String or Symbol] env the environment [development|production|stage|test]
15
17
  def env=(env)
@@ -28,27 +30,41 @@ module Raad
28
30
  @env == :production
29
31
  end
30
32
 
31
- # Determines if we are in the development environment
33
+ # are we in the development environment
32
34
  #
33
35
  # @return [Boolean] true if current environemnt is development, false otherwise
34
36
  def development?
35
37
  @env == :development
36
38
  end
37
39
 
38
- # Determines if we are in the staging environment
40
+ # are we in the staging environment
39
41
  #
40
42
  # @return [Boolean] true if current environemnt is staging, false otherwise
41
43
  def stage?
42
44
  @env == :stage
43
45
  end
44
46
 
45
- # Determines if we are in the test environment
47
+ # are we in the test environment
46
48
  #
47
49
  # @return [Boolean] true if current environemnt is test, false otherwise
48
50
  def test?
49
51
  @env == :test
50
52
  end
51
53
 
52
- module_function :env, :env=, :production?, :development?, :stage?, :test?
54
+ # are we running inside jruby
55
+ #
56
+ # @return [Boolean] true if runnig inside jruby
57
+ def jruby?
58
+ defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
59
+ end
60
+
61
+ # absolute path of current interpreter
62
+ #
63
+ # @return [String] absolute path of current interpreter
64
+ def ruby_path
65
+ File.join(Config::CONFIG["bindir"], Config::CONFIG["RUBY_INSTALL_NAME"] + Config::CONFIG["EXEEXT"])
66
+ end
67
+
68
+ module_function :env, :env=, :production?, :development?, :stage?, :test?, :jruby?, :ruby_path
53
69
 
54
70
  end
@@ -10,12 +10,14 @@ module Raad
10
10
  def setup(options = {})
11
11
  @log = Log4r::Logger.new('raad')
12
12
 
13
- log_format = Log4r::PatternFormatter.new(:pattern => "[#{Process.pid}:%l] %d :: %m")
13
+ # select only :pattern, :date_pattern, :date_method and flush nils
14
+ formatter_options = {:pattern => "[#{Process.pid}:%l] %d :: %m"}.merge(options.reject{|k, v| !([:pattern, :date_pattern, :date_method].include?(k) && !v.nil?)})
15
+
16
+ log_format = Log4r::PatternFormatter.new(formatter_options)
14
17
  setup_file_logger(@log, log_format, options[:file]) if options[:file]
15
18
  setup_stdout_logger(@log, log_format) if options[:stdout]
16
19
 
17
20
  @verbose = !!options[:verbose]
18
-
19
21
  @log.level = @verbose ? Log4r::DEBUG : Log4r::INFO
20
22
  @log
21
23
  end
@@ -1,14 +1,11 @@
1
1
  require 'optparse'
2
- require 'thread'
3
- require 'monitor'
4
2
 
5
3
  module Raad
6
4
  class Runner
7
5
  include Daemonizable
8
6
 
9
7
  SECOND = 1
10
- SOFT_STOP_TIMEOUT = 58 * SECOND
11
- HARD_STOP_TIMEOUT = 60 * SECOND
8
+ STOP_TIMEOUT = 60 * SECOND
12
9
 
13
10
  attr_accessor :service, :pid_file, :options
14
11
 
@@ -17,11 +14,12 @@ module Raad
17
14
  # @param argv [Array] command line arguments
18
15
  # @param service [Object] service to execute
19
16
  def initialize(argv, service)
17
+ @argv = argv.dup # lets keep a copy for jruby double-launch
20
18
  create_options_parser(service).parse!(argv)
21
19
 
22
20
  # start/stop
23
21
  @options[:command] = argv[0].to_s.downcase
24
- unless ['start', 'stop'].include?(options[:command])
22
+ unless ['start', 'stop', 'post_fork'].include?(options[:command])
25
23
  puts(">> start|stop command is required")
26
24
  exit!(false)
27
25
  end
@@ -31,9 +29,6 @@ module Raad
31
29
  @logger_options = nil
32
30
  @pid_file = nil
33
31
 
34
- # signals handling
35
- @signals = []
36
- @monitor = Monitor.new
37
32
  @stop_signaled = false
38
33
  end
39
34
 
@@ -51,31 +46,37 @@ module Raad
51
46
  options[:log_stdout] = !options[:daemonize]
52
47
  end
53
48
  @logger_options = {
54
- :file => options.delete(:log_file),
55
- :stdout => options.delete(:log_stdout),
56
- :verbose => options.delete(:verbose),
49
+ :file => options.delete(:log_file) || Configuration.log_file,
50
+ :stdout => options.delete(:log_stdout) || Configuration.log_stdout,
51
+ :verbose => options.delete(:verbose) || Configuration.verbose,
52
+ :pattern => options.delete(:log_pattern) || Configuration.log_pattern,
57
53
  }
58
54
  @pid_file = options.delete(:pid_file) || "./#{@service_name}.pid"
55
+ @stop_timeout = (options.delete(:stop_timeout) || Configuration.stop_timeout || STOP_TIMEOUT).to_i
59
56
 
60
57
  # check for stop command, @pid_file must be set
61
58
  if options[:command] == 'stop'
62
59
  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)
60
+ # first send the TERM signal which will invoke the daemon wait_or_will method which will timeout after @stop_timeout
61
+ # if still not stopped afer @stop_timeout + 2, KILL -9 will be sent.
62
+ success = send_signal('TERM', @stop_timeout + 2)
63
+ exit(success)
65
64
  end
66
65
 
67
66
  # setup logging
68
67
  Logger.setup(@logger_options)
69
68
  Logger.level = Configuration.log_level if Configuration.log_level
70
69
 
71
- puts(">> Raad service wrapper v#{VERSION} starting")
72
-
73
70
  Dir.chdir(File.expand_path(File.dirname("./"))) unless Raad.test?
74
71
 
75
- jruby = (defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby')
76
- raise("daemonize not supported in JRuby") if options[:daemonize] && jruby
77
-
78
- options[:daemonize] ? daemonize(@service_name, options[:redirect]) {run_service} : run_service
72
+ if options[:command] == 'post_fork'
73
+ # we've been spawned and re executed, finish setup
74
+ post_fork_setup(@service_name, options[:redirect])
75
+ run_service
76
+ else
77
+ puts(">> Raad service wrapper v#{VERSION} starting")
78
+ options[:daemonize] ? daemonize(@argv, @service_name, options[:redirect]) {run_service} : run_service
79
+ end
79
80
  end
80
81
 
81
82
  private
@@ -90,28 +91,9 @@ module Raad
90
91
  Logger.info(">> Raad service wrapper stopped")
91
92
  end
92
93
 
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
94
+ # do not trap :QUIT because its not supported in jruby
95
+ [:INT, :TERM].each do |sig|
96
+ SignalTrampoline.trap(sig) {@stop_signaled = true; stop_service}
115
97
  end
116
98
 
117
99
  # launch the service thread and call start. we expect start not to return
@@ -119,7 +101,7 @@ module Raad
119
101
  service_thread = Thread.new do
120
102
  Thread.current.abort_on_exception = true
121
103
  service.start
122
- stop_service unless @stop_signaled # don't stop twice if already called from the signals_thread
104
+ stop_service unless @stop_signaled # don't stop twice if already called from the signal handler
123
105
  end
124
106
 
125
107
  # use exit and not exit! to make sure the at_exit hooks are called, like the pid cleanup, etc.
@@ -133,7 +115,7 @@ module Raad
133
115
 
134
116
  # try to do a timeout join periodically on the given thread. if the join succeed then the stop
135
117
  # sequence is successful and return true.
136
- # Otherwise, on timeout if stop has beed signaled, wait a maximum of SOFT_STOP_TIMEOUT on the
118
+ # Otherwise, on timeout if stop has beed signaled, wait a maximum of @stop_timeout on the
137
119
  # thread and kill it if the timeout is reached and return false in that case.
138
120
  #
139
121
  # @return [Boolean] true if the thread normally terminated, false if a kill was necessary
@@ -143,9 +125,9 @@ module Raad
143
125
  if @stop_signaled
144
126
  # but if stop has been signalled, start "the final countdown" ♫
145
127
  try = 0; join = nil
146
- while (try += 1) <= SOFT_STOP_TIMEOUT && join.nil? do
128
+ while (try += 1) <= @stop_timeout && join.nil? do
147
129
  join = thread.join(SECOND)
148
- Logger.debug("waiting for service to stop #{try}/#{SOFT_STOP_TIMEOUT}") if join.nil?
130
+ Logger.debug("waiting for service to stop #{try}/#{@stop_timeout}") if join.nil?
149
131
  end
150
132
  if join.nil?
151
133
  Logger.error("stop timeout exhausted, killing service thread")
@@ -184,13 +166,15 @@ module Raad
184
166
 
185
167
  opts.on('-l', '--log FILE', "log to file (default: in console mode: no, daemonized: <service>.log)") { |file| @options[:log_file] = file }
186
168
  opts.on('-s', '--stdout', "log to stdout (default: in console mode: true, daemonized: false)") { |v| @options[:log_stdout] = v }
169
+ opts.on('-v', '--verbose', "enable verbose logging (default: #{@options[:verbose]})") { |v| @options[:verbose] = v }
170
+ opts.on('--pattern PATTERN', "log4r log formatter pattern") { |v| @options[:log_pattern] = v }
187
171
 
188
172
  opts.on('-c', '--config FILE', "config file (default: ./config/<service>.rb)") { |v| @options[:config] = v }
189
173
  opts.on('-d', '--daemonize', "run daemonized in the background (default: #{@options[:daemonize]})") { |v| @options[:daemonize] = v }
190
174
  opts.on('-P', '--pid FILE', "pid file when daemonized (default: <service>.pid)") { |file| @options[:pid_file] = file }
191
175
  opts.on('-r', '--redirect FILE', "redirect stdout to FILE when daemonized (default: no)") { |v| @options[:redirect] = v }
192
176
  opts.on('-n', '--name NAME', "daemon process name (default: <service>)") { |v| @options[:name] = v }
193
- opts.on('-v', '--verbose', "enable verbose logging (default: #{@options[:verbose]})") { |v| @options[:verbose] = v }
177
+ opts.on('--timeout SECONDS', "seconds to wait before force stopping the service (default: 60)") { |v| @options[:stop_timeout] = v }
194
178
 
195
179
  opts.on('-h', '--help', 'display help message') { show_options(opts) }
196
180
  end
@@ -0,0 +1,39 @@
1
+ require 'thread'
2
+
3
+ module SignalTrampoline
4
+
5
+ module_function
6
+
7
+ SIGNALS = {
8
+ :EXIT => 0, :HUP => 1, :INT => 2, :QUIT => 3, :ILL => 4, :TRAP => 5, :IOT => 6, :ABRT => 6, :FPE => 8, :KILL => 9,
9
+ :BUS => 7, :SEGV => 11, :SYS => 31, :PIPE => 13, :ALRM => 14, :TERM => 15, :URG => 23, :STOP => 19, :TSTP => 20,
10
+ :CONT => 18, :CHLD => 17, :CLD => 17, :TTIN => 21, :TTOU => 22, :IO => 29, :XCPU => 24, :XFSZ => 25, :VTALRM => 26,
11
+ :PROF => 27, :WINCH => 28, :USR1 => 10, :USR2 => 12, :PWR => 30, :POLL => 29
12
+ }
13
+
14
+ @signal_q = Queue.new
15
+ @handlers = {}
16
+ @handler_thread = nil
17
+
18
+ # using threads to bounce signal using a thread-safe queue seem the most robust way to handle signals.
19
+ # it minimizes the code in the trap block and reissue the signal and its handling in the normal Ruby
20
+ # flow, within normal threads.
21
+
22
+ def trap(signal, &block)
23
+ raise("unknown signal") unless SIGNALS.has_key?(signal)
24
+ @handler_thread ||= detach_handler_thread
25
+ @handlers[signal] = block
26
+ Kernel.trap(signal) {Thread.new{@signal_q << signal}}
27
+ end
28
+
29
+ def detach_handler_thread
30
+ Thread.new do
31
+ Thread.current.abort_on_exception = true
32
+ loop do
33
+ s = @signal_q.pop
34
+ @handlers[s].call if @handlers[s]
35
+ end
36
+ end
37
+ end
38
+
39
+ end
@@ -0,0 +1,46 @@
1
+ require 'ffi'
2
+
3
+ # spoon code taken from Charles Oliver Nutter's spoon gem https://github.com/headius/spoon
4
+ # also see http://blog.headius.com/2009/05/fork-and-exec-on-jvm-jruby-to-rescue.html
5
+
6
+ module Spoon
7
+ extend FFI::Library
8
+ ffi_lib 'c'
9
+
10
+ # int
11
+ # posix_spawn(pid_t *restrict pid, const char *restrict path,
12
+ # const posix_spawn_file_actions_t *file_actions,
13
+ # const posix_spawnattr_t *restrict attrp, char *const argv[restrict],
14
+ # char *const envp[restrict]);
15
+
16
+ attach_function :_posix_spawn, :posix_spawn, [:pointer, :string, :pointer, :pointer, :pointer, :pointer], :int
17
+ attach_function :_posix_spawnp, :posix_spawnp, [:pointer, :string, :pointer, :pointer, :pointer, :pointer], :int
18
+
19
+ def self.spawn(*args)
20
+ spawn_args = _prepare_spawn_args(args)
21
+ _posix_spawn(*spawn_args)
22
+ spawn_args[0].read_int
23
+ end
24
+
25
+ def self.spawnp(*args)
26
+ spawn_args = _prepare_spawn_args(args)
27
+ _posix_spawnp(*spawn_args)
28
+ spawn_args[0].read_int
29
+ end
30
+
31
+ private
32
+
33
+ def self._prepare_spawn_args(args)
34
+ pid_ptr = FFI::MemoryPointer.new(:pid_t, 1)
35
+
36
+ args_ary = FFI::MemoryPointer.new(:pointer, args.length + 1)
37
+ str_ptrs = args.map {|str| FFI::MemoryPointer.from_string(str)}
38
+ args_ary.put_array_of_pointer(0, str_ptrs)
39
+
40
+ env_ary = FFI::MemoryPointer.new(:pointer, ENV.length + 1)
41
+ env_ptrs = ENV.map {|key,value| FFI::MemoryPointer.from_string("#{key}=#{value}")}
42
+ env_ary.put_array_of_pointer(0, env_ptrs)
43
+
44
+ [pid_ptr, args[0], nil, nil, args_ary, env_ary]
45
+ end
46
+ end
@@ -1,6 +1,8 @@
1
1
  require 'etc'
2
2
  require 'timeout'
3
3
 
4
+ require 'raad/spoon' if Raad.jruby?
5
+
4
6
  module Process
5
7
 
6
8
  def running?(pid)
@@ -24,30 +26,42 @@ module Daemonizable
24
26
  File.exist?(@pid_file) ? open(@pid_file).read.to_i : nil
25
27
  end
26
28
 
27
- def daemonize(name, stdout_file)
29
+ def daemonize(argv, name, stdout_file = nil)
28
30
  remove_stale_pid_file
29
31
  pwd = Dir.pwd
30
32
 
31
- # do the double fork dance
32
- Process.fork do
33
- Process.setsid
34
- exit if fork
35
- $0 = name # set process name
33
+ if Raad.jruby?
34
+ # in jruby the process is to posix-spawn a new process and re execute ourself using Spoon.
35
+ # swap command 'start' for 'post_fork' to signal the second exec
36
+ spawn_argv = argv.map{|arg| arg == 'start' ? 'post_fork' : arg}
37
+ Spoon.spawnp(Raad.ruby_path, $0, *spawn_argv)
38
+ else
39
+ # do the double fork dance
40
+ Process.fork do
41
+ Process.setsid
42
+ exit if fork # exit parent
43
+
44
+ Dir.chdir(pwd)
45
+ post_fork_setup(name, stdout_file)
46
+
47
+ yield
48
+ end
49
+ end
50
+ end
36
51
 
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
52
+ def post_fork_setup(name, stdout_file = nil)
53
+ $0 = name # set process name, does not work with jruby
40
54
 
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)
55
+ File.umask(0000) # file mode creation mask to 000 to allow creation of files with any required permission late
56
+ write_pid_file
45
57
 
46
- at_exit do
47
- remove_pid_file
48
- end
58
+ # redirect stdin, stdout, stderr
59
+ STDIN.reopen('/dev/null')
60
+ stdout_file ? STDOUT.reopen(stdout_file, "a") : STDOUT.reopen('/dev/null', 'a')
61
+ STDERR.reopen(STDOUT)
49
62
 
50
- yield
63
+ at_exit do
64
+ remove_pid_file
51
65
  end
52
66
  end
53
67
 
@@ -103,7 +117,6 @@ module Daemonizable
103
117
  end
104
118
 
105
119
  def write_pid_file
106
- puts(">> writing pid to #{@pid_file}")
107
120
  open(@pid_file,"w") { |f| f.write(Process.pid) }
108
121
  File.chmod(0644, @pid_file)
109
122
  end
@@ -1,3 +1,3 @@
1
1
  module Raad
2
- VERSION = "0.3.3"
2
+ VERSION = "0.4.0"
3
3
  end unless defined?(Raad::VERSION)
metadata CHANGED
@@ -1,65 +1,74 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: raad
3
- version: !ruby/object:Gem::Version
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.0
4
5
  prerelease:
5
- version: 0.3.3
6
6
  platform: ruby
7
- authors:
7
+ authors:
8
8
  - Colin Surprenant
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
-
13
- date: 2011-08-26 00:00:00 Z
14
- dependencies:
15
- - !ruby/object:Gem::Dependency
12
+ date: 2011-09-13 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
16
15
  name: rubyforge
17
- prerelease: false
18
- requirement: &id001 !ruby/object:Gem::Requirement
16
+ requirement: &2152610040 !ruby/object:Gem::Requirement
19
17
  none: false
20
- requirements:
21
- - - ">="
22
- - !ruby/object:Gem::Version
23
- version: "0"
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
24
22
  type: :development
25
- version_requirements: *id001
26
- - !ruby/object:Gem::Dependency
23
+ prerelease: false
24
+ version_requirements: *2152610040
25
+ - !ruby/object:Gem::Dependency
27
26
  name: rspec
27
+ requirement: &2152609480 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: 2.6.0
33
+ type: :development
28
34
  prerelease: false
29
- requirement: &id002 !ruby/object:Gem::Requirement
35
+ version_requirements: *2152609480
36
+ - !ruby/object:Gem::Dependency
37
+ name: rake
38
+ requirement: &2152608960 !ruby/object:Gem::Requirement
30
39
  none: false
31
- requirements:
40
+ requirements:
32
41
  - - ~>
33
- - !ruby/object:Gem::Version
34
- version: 2.5.0
42
+ - !ruby/object:Gem::Version
43
+ version: 0.9.2
35
44
  type: :development
36
- version_requirements: *id002
37
- - !ruby/object:Gem::Dependency
38
- name: log4r
39
45
  prerelease: false
40
- requirement: &id003 !ruby/object:Gem::Requirement
46
+ version_requirements: *2152608960
47
+ - !ruby/object:Gem::Dependency
48
+ name: log4r
49
+ requirement: &2152608480 !ruby/object:Gem::Requirement
41
50
  none: false
42
- requirements:
51
+ requirements:
43
52
  - - ~>
44
- - !ruby/object:Gem::Version
53
+ - !ruby/object:Gem::Version
45
54
  version: 1.1.9
46
55
  type: :runtime
47
- version_requirements: *id003
56
+ prerelease: false
57
+ version_requirements: *2152608480
48
58
  description: Ruby as a Daemon lightweight service wrapper
49
- email:
59
+ email:
50
60
  - colin.surprenant@gmail.com
51
61
  executables: []
52
-
53
62
  extensions: []
54
-
55
63
  extra_rdoc_files: []
56
-
57
- files:
64
+ files:
58
65
  - lib/raad/configuration.rb
59
66
  - lib/raad/env.rb
60
67
  - lib/raad/logger.rb
61
68
  - lib/raad/runner.rb
62
69
  - lib/raad/service.rb
70
+ - lib/raad/signal_trampoline.rb
71
+ - lib/raad/spoon.rb
63
72
  - lib/raad/unix_daemon.rb
64
73
  - lib/raad/version.rb
65
74
  - lib/raad.rb
@@ -68,30 +77,26 @@ files:
68
77
  - LICENSE.md
69
78
  homepage: http://github.com/praized/raad
70
79
  licenses: []
71
-
72
80
  post_install_message:
73
81
  rdoc_options: []
74
-
75
- require_paths:
82
+ require_paths:
76
83
  - lib
77
- required_ruby_version: !ruby/object:Gem::Requirement
84
+ required_ruby_version: !ruby/object:Gem::Requirement
78
85
  none: false
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: "0"
83
- required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
91
  none: false
85
- requirements:
86
- - - ">="
87
- - !ruby/object:Gem::Version
92
+ requirements:
93
+ - - ! '>='
94
+ - !ruby/object:Gem::Version
88
95
  version: 1.3.0
89
96
  requirements: []
90
-
91
97
  rubyforge_project: raad
92
- rubygems_version: 1.8.9
98
+ rubygems_version: 1.8.10
93
99
  signing_key:
94
100
  specification_version: 3
95
101
  summary: Ruby as a Daemon
96
102
  test_files: []
97
-