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