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