raad 0.4.0 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +9 -2
- data/README.md +107 -37
- data/lib/raad/{service.rb → bootstrap.rb} +16 -16
- data/lib/raad/env.rb +21 -3
- data/lib/raad/runner.rb +15 -8
- data/lib/raad/unix_daemon.rb +7 -13
- data/lib/raad/version.rb +1 -1
- data/lib/raad.rb +1 -1
- metadata +11 -11
data/CHANGELOG.md
CHANGED
@@ -7,6 +7,13 @@
|
|
7
7
|
- wrong exit code bug
|
8
8
|
- comments & cosmetics
|
9
9
|
|
10
|
-
# 0.4, 09-
|
10
|
+
# 0.4.0, 09-13-2011
|
11
11
|
- using SignalTrampoline for safe signal handling
|
12
|
-
- JRuby support
|
12
|
+
- JRuby support
|
13
|
+
- specs and validations for all supported rubies
|
14
|
+
- tested on MRI 1.8.7 & 1.9.2, REE 1.8.7, JRuby 1.6.4 under OSX 10.6.8 and Linux Ubuntu 10.04
|
15
|
+
|
16
|
+
# 0.4.1, 09-22-2011
|
17
|
+
- allow arbitrary environment name
|
18
|
+
- added Raad.stopped?
|
19
|
+
- avoid calling stop multiple times, https://github.com/praized/raad/issues/3
|
data/README.md
CHANGED
@@ -2,56 +2,116 @@
|
|
2
2
|
|
3
3
|
Raad - Ruby as a daemon lightweight service wrapper.
|
4
4
|
|
5
|
-
Raad is a non-intrusive, lightweight, simple Ruby daemon wrapper. Basically
|
6
|
-
the start and stop methods, can be used seamlessly as a daemon or a normal console app.
|
5
|
+
Raad is a non-intrusive, lightweight, simple Ruby daemon wrapper. Basically any class which implements
|
6
|
+
the `start` and `stop` methods, can be used seamlessly as a daemon or a normal console app.
|
7
7
|
|
8
|
-
Raad
|
9
|
-
|
8
|
+
Raad **deamonizing** will work the same way for both MRI Ruby and **JRuby**, without
|
9
|
+
modification in your code.
|
10
|
+
|
11
|
+
Raad provides basic daemon control using the start/stop commands. Your code can also use the Raad
|
12
|
+
logging module and benefit easy log file output while daemonized.
|
10
13
|
|
11
14
|
## Installation
|
12
15
|
|
13
16
|
### Gem
|
14
|
-
|
17
|
+
|
18
|
+
``` sh
|
19
|
+
$ gem install raad
|
20
|
+
```
|
15
21
|
|
16
22
|
### Bundler
|
17
23
|
#### Latest from github
|
24
|
+
|
25
|
+
``` sh
|
18
26
|
gem "raad", :git => "git://github.com/praized/raad.git", :branch => "master"
|
27
|
+
```
|
19
28
|
|
20
29
|
#### Released gem
|
30
|
+
|
31
|
+
``` bash
|
21
32
|
gem "raad", "~> 0.4.0"
|
33
|
+
```
|
22
34
|
|
23
35
|
## Example
|
24
|
-
Create a class with a start and a stop method. Just by requiring 'raad', your class will be
|
25
|
-
wrapped by Raad and daemonizable
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
end
|
37
|
-
|
38
|
-
def stop
|
39
|
-
@stopped = true
|
40
|
-
Raad::Logger.info("simple_daemon stopped")
|
41
|
-
end
|
36
|
+
Create a class with a `start` and a `stop` method. Just by requiring 'raad', your class will be
|
37
|
+
wrapped by Raad and become **daemonizable**.
|
38
|
+
|
39
|
+
``` ruby
|
40
|
+
require 'rubygems'
|
41
|
+
require 'raad'
|
42
|
+
|
43
|
+
class SimpleDaemon
|
44
|
+
def start
|
45
|
+
while !Raad.stopped?
|
46
|
+
Raad::Logger.info("simple_daemon running")
|
47
|
+
sleep(1)
|
42
48
|
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def stop
|
52
|
+
Raad::Logger.info("simple_daemon stopped")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
```
|
56
|
+
- run it in console mode, `^C` will stop it, calling the stop method
|
57
|
+
|
58
|
+
``` sh
|
59
|
+
$ ruby simple_daemon.rb start
|
60
|
+
```
|
61
|
+
- run it daemonized, by default `./simple_daemon.log` and `./simple_daemon.pid` will be created
|
43
62
|
|
44
|
-
|
45
|
-
|
63
|
+
``` sh
|
64
|
+
$ ruby simple_daemon.rb -d start
|
65
|
+
```
|
46
66
|
|
47
|
-
|
48
|
-
$ ruby simple_daemon.rb -d start
|
67
|
+
- stop daemon, removing `./simple_daemon.pid`
|
49
68
|
|
50
|
-
|
51
|
-
|
69
|
+
``` sh
|
70
|
+
$ ruby simple_daemon.rb stop
|
71
|
+
```
|
52
72
|
|
53
73
|
## Documentation
|
54
74
|
|
75
|
+
### Introduction
|
76
|
+
|
77
|
+
By requiring 'raad' in your class, it will automagically be wrapped by the Raad bootstrap code.
|
78
|
+
When running your class file with the `start` parameter, Raad will call your class `start` method.
|
79
|
+
|
80
|
+
The `start` method **should not return** unless your service has completed its work or has been
|
81
|
+
instructed to stop.
|
82
|
+
|
83
|
+
There are two ways to know when your service has been instructed to stop:
|
84
|
+
|
85
|
+
* the `stop` method of your class will be called if it is defined
|
86
|
+
* `Raad.stopped?` will return true
|
87
|
+
|
88
|
+
There are basically 3 ways to run execute your service:
|
89
|
+
|
90
|
+
* start it in foreground console mode, useful for debugging, `^C` to execute the stop sequence
|
91
|
+
|
92
|
+
``` sh
|
93
|
+
$ ruby your_service.rb start
|
94
|
+
```
|
95
|
+
|
96
|
+
* start it as a detached, backgrounded daemon:
|
97
|
+
|
98
|
+
``` sh
|
99
|
+
$ ruby your_service.rb -d start
|
100
|
+
```
|
101
|
+
|
102
|
+
* stop the daemonized service by signaling it to execute the stop sequence
|
103
|
+
|
104
|
+
``` sh
|
105
|
+
$ ruby your_service.rb stop
|
106
|
+
```
|
107
|
+
|
108
|
+
In **console mode** Raad logging for level `:info` and up and stdout, ie `puts`, will be displayed by default.
|
109
|
+
|
110
|
+
In **daemon mode**, Raad logging for level `:info` and up will be output in `your_service.log` log file and the
|
111
|
+
`your_service.pid`pid file will be created.
|
112
|
+
|
113
|
+
To toggle output of all logging levels simply use the verbose `-v` parameter.
|
114
|
+
|
55
115
|
### Supported rubies and environments
|
56
116
|
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
117
|
|
@@ -87,28 +147,38 @@ tbd.
|
|
87
147
|
tbd.
|
88
148
|
|
89
149
|
### Testing
|
90
|
-
There are specs and a validation suite which ca be run in
|
150
|
+
There are specs and a validation suite which ca be run in your **current** ruby environment:
|
91
151
|
|
92
|
-
|
93
|
-
|
152
|
+
``` sh
|
153
|
+
$ rake spec
|
154
|
+
```
|
155
|
+
``` sh
|
156
|
+
$ rake validation
|
157
|
+
```
|
94
158
|
|
95
|
-
Also, specs and validations can be run in all currently tested Ruby environement
|
159
|
+
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
160
|
|
97
161
|
- ruby-1.8.7
|
98
162
|
- ree-1.8.7
|
99
163
|
- ruby-1.9.2
|
100
164
|
- jruby-1.6.4
|
101
165
|
|
102
|
-
In each of these rubies, the gemset @raad containing log4r
|
166
|
+
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
167
|
|
104
168
|
This RVM environment can be created/updated using:
|
105
169
|
|
106
|
-
|
170
|
+
``` sh
|
171
|
+
$ rake rvm_setup
|
172
|
+
```
|
107
173
|
|
108
174
|
To launch the tests for all rubies use:
|
109
175
|
|
110
|
-
|
111
|
-
|
176
|
+
``` sh
|
177
|
+
$ rake specs
|
178
|
+
```
|
179
|
+
``` sh
|
180
|
+
$ rake validations
|
181
|
+
```
|
112
182
|
|
113
183
|
## TODO
|
114
184
|
- better doc
|
@@ -1,18 +1,16 @@
|
|
1
1
|
module Raad
|
2
2
|
|
3
|
-
# The
|
4
|
-
# handler to run the
|
5
|
-
class
|
3
|
+
# The bootstrap class for Raad. This will execute in the at_exit
|
4
|
+
# handler to run the service.
|
5
|
+
class Bootstrap
|
6
6
|
|
7
|
-
# Set of caller regex's to be skippe when looking for our API file
|
8
7
|
CALLERS_TO_IGNORE = [ # :nodoc:
|
9
|
-
/\/raad(\/(
|
10
|
-
/rubygems\/custom_require\.rb$/,
|
11
|
-
/bundler(\/runtime)?\.rb/,
|
12
|
-
/<internal:/
|
8
|
+
/\/raad(\/(bootstrap))?\.rb$/, # all raad code
|
9
|
+
/rubygems\/custom_require\.rb$/, # rubygems require hacks
|
10
|
+
/bundler(\/runtime)?\.rb/, # bundler require hacks
|
11
|
+
/<internal:/ # internal in ruby >= 1.9.2
|
13
12
|
]
|
14
13
|
|
15
|
-
# @todo add rubinius (and hopefully other VM impls) ignore patterns ...
|
16
14
|
CALLERS_TO_IGNORE.concat(RUBY_IGNORE_CALLERS) if defined?(RUBY_IGNORE_CALLERS)
|
17
15
|
|
18
16
|
# Like Kernel#caller but excluding certain magic entries and without
|
@@ -21,7 +19,7 @@ module Raad
|
|
21
19
|
caller_locations.map { |file, line| file }
|
22
20
|
end
|
23
21
|
|
24
|
-
#
|
22
|
+
# like caller_files, but containing Arrays rather than strings with the
|
25
23
|
# first element being the file, and the second being the line.
|
26
24
|
def self.caller_locations
|
27
25
|
caller(1).
|
@@ -29,7 +27,7 @@ module Raad
|
|
29
27
|
reject { |file, line| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } }
|
30
28
|
end
|
31
29
|
|
32
|
-
#
|
30
|
+
# find the service_file that was used to execute the service
|
33
31
|
#
|
34
32
|
# @return [String] The service file
|
35
33
|
def self.service_file
|
@@ -38,20 +36,20 @@ module Raad
|
|
38
36
|
c
|
39
37
|
end
|
40
38
|
|
41
|
-
#
|
39
|
+
# execute the service
|
42
40
|
#
|
43
41
|
# @return [Nil]
|
44
42
|
def self.run!
|
45
43
|
file = File.basename(service_file, '.rb')
|
46
44
|
service = Object.module_eval(camel_case(file)).new
|
47
45
|
|
48
|
-
runner =
|
46
|
+
runner = Runner.new(ARGV, service)
|
49
47
|
runner.run
|
50
48
|
end
|
51
49
|
|
52
50
|
private
|
53
51
|
|
54
|
-
#
|
52
|
+
# convert a string to camel case
|
55
53
|
#
|
56
54
|
# @param str [String] The string to convert
|
57
55
|
# @return [String] The camel cased string
|
@@ -63,8 +61,10 @@ module Raad
|
|
63
61
|
end
|
64
62
|
|
65
63
|
at_exit do
|
66
|
-
|
67
|
-
|
64
|
+
unless defined?($RAAD_NOT_RUN)
|
65
|
+
if $!.nil? && $0 == Raad::Bootstrap.service_file
|
66
|
+
Raad::Bootstrap.run!
|
67
|
+
end
|
68
68
|
end
|
69
69
|
end
|
70
70
|
end
|
data/lib/raad/env.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
require 'rbconfig'
|
2
|
+
require 'thread'
|
2
3
|
|
3
4
|
module Raad
|
4
5
|
|
5
6
|
@env = :development
|
7
|
+
@stopped = false
|
8
|
+
@stop_lock = Mutex.new
|
6
9
|
|
7
10
|
# retrieves the current environment
|
8
11
|
#
|
@@ -13,13 +16,14 @@ module Raad
|
|
13
16
|
|
14
17
|
# sets the current environment
|
15
18
|
#
|
16
|
-
# @param [String or Symbol] env the environment
|
19
|
+
# @param [String or Symbol] env the environment
|
17
20
|
def env=(env)
|
18
21
|
case(env.to_s)
|
19
22
|
when 'dev', 'development' then @env = :development
|
20
23
|
when 'prod', 'production' then @env = :production
|
21
24
|
when 'stage', 'staging' then @env = :stage
|
22
25
|
when 'test' then @env = :test
|
26
|
+
else @env = env.to_sym
|
23
27
|
end
|
24
28
|
end
|
25
29
|
|
@@ -55,7 +59,7 @@ module Raad
|
|
55
59
|
#
|
56
60
|
# @return [Boolean] true if runnig inside jruby
|
57
61
|
def jruby?
|
58
|
-
defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
|
62
|
+
!!(defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby')
|
59
63
|
end
|
60
64
|
|
61
65
|
# absolute path of current interpreter
|
@@ -65,6 +69,20 @@ module Raad
|
|
65
69
|
File.join(Config::CONFIG["bindir"], Config::CONFIG["RUBY_INSTALL_NAME"] + Config::CONFIG["EXEEXT"])
|
66
70
|
end
|
67
71
|
|
68
|
-
|
72
|
+
# a request to stop the service has been received (or the #start method has returned and, if defined, the service #stop method has been called by Raad.
|
73
|
+
#
|
74
|
+
# @return [Boolean] true is the service has been stopped
|
75
|
+
def stopped?
|
76
|
+
@stop_lock.synchronize{@stopped}
|
77
|
+
end
|
78
|
+
|
79
|
+
# used internally to set the stopped flag
|
80
|
+
#
|
81
|
+
# @param [Boolean] state true to set the stopped flag
|
82
|
+
def stopped=(state)
|
83
|
+
@stop_lock.synchronize{@stopped = !!state}
|
84
|
+
end
|
85
|
+
|
86
|
+
module_function :env, :env=, :production?, :development?, :stage?, :test?, :jruby?, :ruby_path, :stopped?, :stopped=
|
69
87
|
|
70
88
|
end
|
data/lib/raad/runner.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
require 'optparse'
|
2
|
+
require 'timeout'
|
3
|
+
require 'thread'
|
2
4
|
|
3
5
|
module Raad
|
4
6
|
class Runner
|
@@ -29,6 +31,7 @@ module Raad
|
|
29
31
|
@logger_options = nil
|
30
32
|
@pid_file = nil
|
31
33
|
|
34
|
+
@stop_lock = Mutex.new
|
32
35
|
@stop_signaled = false
|
33
36
|
end
|
34
37
|
|
@@ -58,8 +61,8 @@ module Raad
|
|
58
61
|
if options[:command] == 'stop'
|
59
62
|
puts(">> Raad service wrapper v#{VERSION} stopping")
|
60
63
|
# 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)
|
64
|
+
# if still not stopped afer @stop_timeout + 2 seconds, KILL -9 will be sent.
|
65
|
+
success = send_signal('TERM', @stop_timeout + (2 * SECOND))
|
63
66
|
exit(success)
|
64
67
|
end
|
65
68
|
|
@@ -92,25 +95,29 @@ module Raad
|
|
92
95
|
end
|
93
96
|
|
94
97
|
# do not trap :QUIT because its not supported in jruby
|
95
|
-
[:INT, :TERM].each
|
96
|
-
SignalTrampoline.trap(sig) {@stop_signaled = true; stop_service}
|
97
|
-
end
|
98
|
+
[:INT, :TERM].each{|sig| SignalTrampoline.trap(sig) {stop_service}}
|
98
99
|
|
99
100
|
# launch the service thread and call start. we expect start not to return
|
100
101
|
# unless it is done or has been stopped.
|
101
102
|
service_thread = Thread.new do
|
102
103
|
Thread.current.abort_on_exception = true
|
103
104
|
service.start
|
104
|
-
stop_service
|
105
|
+
stop_service
|
105
106
|
end
|
106
107
|
|
108
|
+
result = wait_or_kill(service_thread)
|
109
|
+
# if not daemonized start a sentinel thread, if still alive after 2 seconds, do arakiri
|
110
|
+
Thread.new{sleep(2 * SECOND); Process.kill(:KILL, Process.pid)} unless options[:daemonize]
|
107
111
|
# use exit and not exit! to make sure the at_exit hooks are called, like the pid cleanup, etc.
|
108
|
-
exit(
|
112
|
+
exit(result)
|
109
113
|
end
|
110
114
|
|
111
115
|
def stop_service
|
116
|
+
return if @stop_lock.synchronize{s = @stop_signaled; @stop_signaled = true; s}
|
117
|
+
|
112
118
|
Logger.info("stopping #{@service_name} service")
|
113
119
|
service.stop if service.respond_to?(:stop)
|
120
|
+
Raad.stopped = true
|
114
121
|
end
|
115
122
|
|
116
123
|
# try to do a timeout join periodically on the given thread. if the join succeed then the stop
|
@@ -122,7 +129,7 @@ module Raad
|
|
122
129
|
def wait_or_kill(thread)
|
123
130
|
while thread.join(SECOND).nil?
|
124
131
|
# the join has timed out, thread is still buzzy.
|
125
|
-
if @stop_signaled
|
132
|
+
if @stop_lock.synchronize{@stop_signaled}
|
126
133
|
# but if stop has been signalled, start "the final countdown" ♫
|
127
134
|
try = 0; join = nil
|
128
135
|
while (try += 1) <= @stop_timeout && join.nil? do
|
data/lib/raad/unix_daemon.rb
CHANGED
@@ -78,28 +78,22 @@ module Daemonizable
|
|
78
78
|
false
|
79
79
|
end
|
80
80
|
rescue Timeout::Error
|
81
|
-
force_kill_and_remove_pid_file
|
81
|
+
force_kill_and_remove_pid_file(pid)
|
82
82
|
rescue Interrupt
|
83
|
-
force_kill_and_remove_pid_file
|
83
|
+
force_kill_and_remove_pid_file(pid)
|
84
84
|
rescue Errno::ESRCH # No such process
|
85
85
|
puts(">> can't stop process, no such process #{pid}")
|
86
86
|
remove_pid_file
|
87
87
|
false
|
88
88
|
end
|
89
89
|
|
90
|
-
def force_kill_and_remove_pid_file
|
91
|
-
|
92
|
-
|
93
|
-
Process.kill("KILL", pid)
|
94
|
-
true
|
95
|
-
else
|
96
|
-
puts(">> can't stop process, no pid found in #{@pid_file}")
|
97
|
-
false
|
98
|
-
end
|
90
|
+
def force_kill_and_remove_pid_file(pid)
|
91
|
+
puts(">> sending KILL signal to process #{pid}")
|
92
|
+
Process.kill("KILL", pid)
|
99
93
|
remove_pid_file
|
100
|
-
|
94
|
+
true
|
101
95
|
rescue Errno::ESRCH # No such process
|
102
|
-
puts(">> can't
|
96
|
+
puts(">> can't send KILL, no such process #{pid}")
|
103
97
|
remove_pid_file
|
104
98
|
false
|
105
99
|
end
|
data/lib/raad/version.rb
CHANGED
data/lib/raad.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: raad
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-09-
|
12
|
+
date: 2011-09-23 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rubyforge
|
16
|
-
requirement: &
|
16
|
+
requirement: &2165092620 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *2165092620
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rspec
|
27
|
-
requirement: &
|
27
|
+
requirement: &2165092060 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: 2.6.0
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *2165092060
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: rake
|
38
|
-
requirement: &
|
38
|
+
requirement: &2165091540 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ~>
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: 0.9.2
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *2165091540
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: log4r
|
49
|
-
requirement: &
|
49
|
+
requirement: &2165091060 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ~>
|
@@ -54,7 +54,7 @@ dependencies:
|
|
54
54
|
version: 1.1.9
|
55
55
|
type: :runtime
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *2165091060
|
58
58
|
description: Ruby as a Daemon lightweight service wrapper
|
59
59
|
email:
|
60
60
|
- colin.surprenant@gmail.com
|
@@ -62,11 +62,11 @@ executables: []
|
|
62
62
|
extensions: []
|
63
63
|
extra_rdoc_files: []
|
64
64
|
files:
|
65
|
+
- lib/raad/bootstrap.rb
|
65
66
|
- lib/raad/configuration.rb
|
66
67
|
- lib/raad/env.rb
|
67
68
|
- lib/raad/logger.rb
|
68
69
|
- lib/raad/runner.rb
|
69
|
-
- lib/raad/service.rb
|
70
70
|
- lib/raad/signal_trampoline.rb
|
71
71
|
- lib/raad/spoon.rb
|
72
72
|
- lib/raad/unix_daemon.rb
|