raad 0.3.3 → 0.4.0

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.
@@ -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
-