raad 0.4.0 → 0.4.1

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 CHANGED
@@ -7,6 +7,13 @@
7
7
  - wrong exit code bug
8
8
  - comments & cosmetics
9
9
 
10
- # 0.4, 09-05-2011
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 A simple class which implements
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 provides daemon control using the start/stop commands. Your code can optionnally use the Raad
9
- logging module.
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
- gem install raad
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
- require 'raad'
28
-
29
- class SimpleDaemon
30
- def start
31
- @stopped = false
32
- while !@stopped
33
- Raad::Logger.info("simple_daemon running")
34
- sleep(1)
35
- end
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
- run it in console mode, ^C will stop it, calling the stop method
45
- $ ruby simple_daemon.rb start
63
+ ``` sh
64
+ $ ruby simple_daemon.rb -d start
65
+ ```
46
66
 
47
- run it daemonized, by default ./simple_daemon.log and ./simple_daemon.pid will be created
48
- $ ruby simple_daemon.rb -d start
67
+ - stop daemon, removing `./simple_daemon.pid`
49
68
 
50
- stop daemon, removing ./simple_daemon.pid
51
- $ ruby simple_daemon.rb stop
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 the current ruby environment:
150
+ There are specs and a validation suite which ca be run in your **current** ruby environment:
91
151
 
92
- - rake spec
93
- - rake validation
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. For this [RVM][rvm] is required and the following rubies must be installed:
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 (~> 1.1.9), rake (~> 0.9.2) and rspec (~> 2.6.0) must be created.
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
- - rake rvm_setup
170
+ ``` sh
171
+ $ rake rvm_setup
172
+ ```
107
173
 
108
174
  To launch the tests for all rubies use:
109
175
 
110
- - rake specs
111
- - rake validations
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 main execution class for Raad. This will execute in the at_exit
4
- # handler to run the server.
5
- class Service
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(\/(service))?\.rb$/, # all raad code
10
- /rubygems\/custom_require\.rb$/, # rubygems require hacks
11
- /bundler(\/runtime)?\.rb/, # bundler require hacks
12
- /<internal:/ # internal in ruby >= 1.9.2
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
- # Like caller_files, but containing Arrays rather than strings with the
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
- # Find the service_file that was used to execute the service
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
- # Execute the service
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 = Raad::Runner.new(ARGV, service)
46
+ runner = Runner.new(ARGV, service)
49
47
  runner.run
50
48
  end
51
49
 
52
50
  private
53
51
 
54
- # Convert a string to camel case
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
- if $!.nil? && $0 == Raad::Service.service_file
67
- Service.run!
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 [development|production|stage|test]
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
- module_function :env, :env=, :production?, :development?, :stage?, :test?, :jruby?, :ruby_path
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 do |sig|
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 unless @stop_signaled # don't stop twice if already called from the signal handler
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(wait_or_kill(service_thread))
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
@@ -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
- success = if (pid = read_pid_file)
92
- puts(">> sending KILL signal to process #{pid}")
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
- success
94
+ true
101
95
  rescue Errno::ESRCH # No such process
102
- puts(">> can't stop process, no such process #{pid}")
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
@@ -1,3 +1,3 @@
1
1
  module Raad
2
- VERSION = "0.4.0"
2
+ VERSION = "0.4.1"
3
3
  end unless defined?(Raad::VERSION)
data/lib/raad.rb CHANGED
@@ -5,4 +5,4 @@ require 'raad/configuration'
5
5
  require 'raad/unix_daemon'
6
6
  require 'raad/signal_trampoline'
7
7
  require 'raad/runner'
8
- require 'raad/service'
8
+ require 'raad/bootstrap'
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.0
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-13 00:00:00.000000000Z
12
+ date: 2011-09-23 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rubyforge
16
- requirement: &2152610040 !ruby/object:Gem::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: *2152610040
24
+ version_requirements: *2165092620
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &2152609480 !ruby/object:Gem::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: *2152609480
35
+ version_requirements: *2165092060
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rake
38
- requirement: &2152608960 !ruby/object:Gem::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: *2152608960
46
+ version_requirements: *2165091540
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: log4r
49
- requirement: &2152608480 !ruby/object:Gem::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: *2152608480
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