raad 0.3.3 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +5 -1
- data/LICENSE.md +9 -0
- data/README.md +45 -17
- data/lib/raad.rb +1 -0
- data/lib/raad/env.rb +22 -6
- data/lib/raad/logger.rb +4 -2
- data/lib/raad/runner.rb +30 -46
- data/lib/raad/signal_trampoline.rb +39 -0
- data/lib/raad/spoon.rb +46 -0
- data/lib/raad/unix_daemon.rb +31 -18
- data/lib/raad/version.rb +1 -1
- metadata +52 -47
data/CHANGELOG.md
CHANGED
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.
|
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
|
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.
|
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
|
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
|
-
|
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
|
-
###
|
77
|
+
### Configuration and options
|
75
78
|
tbd.
|
76
79
|
|
77
|
-
###
|
80
|
+
### Adding custom command line options
|
78
81
|
tbd.
|
79
82
|
|
80
|
-
###
|
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
|
88
|
-
-
|
89
|
-
- JRuby support
|
114
|
+
- better doc
|
115
|
+
- more examples
|
90
116
|
|
91
117
|
## Dependencies
|
92
|
-
|
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
|
-
|
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
|
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/
|
data/lib/raad.rb
CHANGED
data/lib/raad/env.rb
CHANGED
@@ -1,15 +1,17 @@
|
|
1
|
+
require 'rbconfig'
|
2
|
+
|
1
3
|
module Raad
|
2
4
|
|
3
5
|
@env = :development
|
4
6
|
|
5
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
|
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
|
data/lib/raad/logger.rb
CHANGED
@@ -10,12 +10,14 @@ module Raad
|
|
10
10
|
def setup(options = {})
|
11
11
|
@log = Log4r::Logger.new('raad')
|
12
12
|
|
13
|
-
|
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
|
data/lib/raad/runner.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
64
|
-
|
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
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
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
|
-
#
|
94
|
-
|
95
|
-
|
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
|
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
|
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) <=
|
128
|
+
while (try += 1) <= @stop_timeout && join.nil? do
|
147
129
|
join = thread.join(SECOND)
|
148
|
-
Logger.debug("waiting for service to stop #{try}/#{
|
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('
|
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
|
data/lib/raad/spoon.rb
ADDED
@@ -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
|
data/lib/raad/unix_daemon.rb
CHANGED
@@ -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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
$0
|
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
|
-
|
38
|
-
|
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
|
-
|
42
|
-
|
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
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
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
|
data/lib/raad/version.rb
CHANGED
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
|
-
|
14
|
-
|
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
|
-
|
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:
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
24
22
|
type: :development
|
25
|
-
|
26
|
-
|
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
|
-
|
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:
|
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
|
-
|
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
|
-
|
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:
|
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.
|
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
|
-
|