raad_totem 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +30 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +30 -0
- data/LICENSE.md +103 -0
- data/README.md +37 -0
- data/Rakefile +14 -0
- data/lib/raad_totem/bootstrap.rb +67 -0
- data/lib/raad_totem/env.rb +61 -0
- data/lib/raad_totem/runner.rb +195 -0
- data/lib/raad_totem/shell_cmds/service.rb +43 -0
- data/lib/raad_totem/signal_trampoline.rb +39 -0
- data/lib/raad_totem/spoon.rb +46 -0
- data/lib/raad_totem/unix_daemon.rb +131 -0
- data/lib/raad_totem/version.rb +3 -0
- data/lib/raad_totem.rb +9 -0
- data/raad.gemspec +26 -0
- data/spec/raad_totem/unit/bootstrap_spec.rb +11 -0
- data/spec/raad_totem/unit/env_spec.rb +21 -0
- data/spec/raad_totem/unit/signal_trampoline_spec.rb +22 -0
- data/spec/raad_totem/unit/spoon_spec.rb +13 -0
- data/spec/raad_totem/unit/unix_daemon_spec.rb +201 -0
- data/spec/raad_totem/unit/version_spec.rb +8 -0
- data/spec/spec_helper.rb +4 -0
- metadata +134 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b6cc5ec492a3a7e4ff5ee49cf38c01826f0efb27
|
4
|
+
data.tar.gz: df041b77a669ce3a6abf6443f9a186ef215a775d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8aaf81a66fbe9eb507da57698f2d36f49507f49f3af90dfd3cda219d39222b2426f226a13660d242ae8c11242d122eef060822b87eb48da5e66882fb34cb4eed
|
7
|
+
data.tar.gz: 76cb583b22e8bbbc7981edf97b5566c3ebaf480e5d237c67eafc08dabb2f825f81b55cff52b96ab4a640eae8cf79b549ba7f6bc0d073cec2f43ac1e695c47ef3
|
data/.gitignore
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
.ruby-version
|
19
|
+
.ruby-gemset
|
20
|
+
.rvmrc
|
21
|
+
test/validation/output/
|
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# 0.0.1 03-14-2014
|
2
|
+
- Fork project and name it raad_totem.
|
3
|
+
- From this point forward, this Changelog will not be updated. Read git log instead.
|
4
|
+
|
5
|
+
# 0.3.1, 08-24-2011
|
6
|
+
- initial release. support for MRI 1.8 & 1.9 only
|
7
|
+
|
8
|
+
# 0.3.3, 08-26-2011
|
9
|
+
- reworked runner stop sequence & signals trapping
|
10
|
+
- force kill + pid removal bug
|
11
|
+
- wrong exit code bug
|
12
|
+
- comments & cosmetics
|
13
|
+
|
14
|
+
# 0.4.0, 09-13-2011
|
15
|
+
- using SignalTrampoline for safe signal handling
|
16
|
+
- JRuby support
|
17
|
+
- specs and validations for all supported rubies
|
18
|
+
- 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
|
19
|
+
|
20
|
+
# 0.4.1, 09-22-2011
|
21
|
+
- issue #1, allow arbitrary environment name, https://github.com/praized/raad/issues/1
|
22
|
+
- issue #3, avoid calling stop multiple times, https://github.com/praized/raad/issues/3
|
23
|
+
- added Raad.stopped?
|
24
|
+
|
25
|
+
# 0.5.0, 10-12-2011
|
26
|
+
- issue #4, allow logger usage in service initialize method, https://github.com/praized/raad/issues/4
|
27
|
+
- issue #7, Raad.env not correct in service.initialize, https://github.com/praized/raad/issues/7
|
28
|
+
- refactored Runner initialize/run/start_service methods
|
29
|
+
- custom options now uses options_parser class method in the service
|
30
|
+
- added examples/custom_options.rb
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
raad_totem (0.0.1)
|
5
|
+
totem (>= 0.0.5)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: http://rubygems.org/
|
9
|
+
specs:
|
10
|
+
diff-lcs (1.1.3)
|
11
|
+
rake (10.1.1)
|
12
|
+
rspec (2.8.0)
|
13
|
+
rspec-core (~> 2.8.0)
|
14
|
+
rspec-expectations (~> 2.8.0)
|
15
|
+
rspec-mocks (~> 2.8.0)
|
16
|
+
rspec-core (2.8.0)
|
17
|
+
rspec-expectations (2.8.0)
|
18
|
+
diff-lcs (~> 1.1.2)
|
19
|
+
rspec-mocks (2.8.0)
|
20
|
+
totem (0.0.5)
|
21
|
+
|
22
|
+
PLATFORMS
|
23
|
+
java
|
24
|
+
ruby
|
25
|
+
|
26
|
+
DEPENDENCIES
|
27
|
+
bundler (~> 1.5)
|
28
|
+
raad_totem!
|
29
|
+
rake
|
30
|
+
rspec (~> 2.8.0)
|
data/LICENSE.md
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
Raad - Totem
|
2
|
+
Authored by Chad Remesch (chad@remesch.com) as a derivate of the original Raad Ruby gem.
|
3
|
+
|
4
|
+
Copyright 2014 Chad Remesch
|
5
|
+
|
6
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
you may not use this file except in compliance with the License.
|
8
|
+
You may obtain a copy of the License at
|
9
|
+
|
10
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
|
12
|
+
Unless required by applicable law or agreed to in writing, software
|
13
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
See the License for the specific language governing permissions and
|
16
|
+
limitations under the License.
|
17
|
+
|
18
|
+
-------------------------------------------------------------------------------
|
19
|
+
|
20
|
+
Raad - Ruby as a Daemon lightweight service wrapper
|
21
|
+
Authored by Colin Surprenant, [@colinsurprenant][twitter], [colin.surprenant@needium.com][needium], [colin.surprenant@gmail.com][gmail], [http://github.com/colinsurprenant][github]
|
22
|
+
|
23
|
+
Copyright 2011 PraizedMedia Inc.
|
24
|
+
|
25
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
26
|
+
you may not use this file except in compliance with the License.
|
27
|
+
You may obtain a copy of the License at
|
28
|
+
|
29
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
30
|
+
|
31
|
+
Unless required by applicable law or agreed to in writing, software
|
32
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
33
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
34
|
+
See the License for the specific language governing permissions and
|
35
|
+
limitations under the License.
|
36
|
+
|
37
|
+
-------------------------------------------------------------------------------
|
38
|
+
|
39
|
+
Portions of this code are from the Thin project (https://github.com/macournoyer/thin) and under the following license:
|
40
|
+
Ruby License, http://www.ruby-lang.org/en/LICENSE.txt.
|
41
|
+
|
42
|
+
-------------------------------------------------------------------------------
|
43
|
+
|
44
|
+
Portions of this code are from the Goliath project (https://github.com/postrank-labs/goliath/) and under the following license:
|
45
|
+
Copyright (c) 2011 PostRank Inc.
|
46
|
+
|
47
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
48
|
+
of this software and associated documentation files (the "Software"), to
|
49
|
+
deal in the Software without restriction, including without limitation the
|
50
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
51
|
+
sell copies of the Software, and to permit persons to whom the Software is
|
52
|
+
furnished to do so, subject to the following conditions:
|
53
|
+
|
54
|
+
The above copyright notice and this permission notice shall be included in
|
55
|
+
all copies or substantial portions of the Software.
|
56
|
+
|
57
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
58
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
59
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
60
|
+
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
61
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
62
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
63
|
+
|
64
|
+
-------------------------------------------------------------------------------
|
65
|
+
|
66
|
+
Portions of this code are from the Sinatra project (https://github.com/bmizerany/sinatra) and under the following license:
|
67
|
+
Copyright (c) 2007, 2008, 2009, 2010, 2011 Blake Mizerany
|
68
|
+
|
69
|
+
Permission is hereby granted, free of charge, to any person
|
70
|
+
obtaining a copy of this software and associated documentation
|
71
|
+
files (the "Software"), to deal in the Software without
|
72
|
+
restriction, including without limitation the rights to use,
|
73
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
74
|
+
copies of the Software, and to permit persons to whom the
|
75
|
+
Software is furnished to do so, subject to the following
|
76
|
+
conditions:
|
77
|
+
|
78
|
+
The above copyright notice and this permission notice shall be
|
79
|
+
included in all copies or substantial portions of the Software.
|
80
|
+
|
81
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
82
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
83
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
84
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
85
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
86
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
87
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
88
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
89
|
+
|
90
|
+
-------------------------------------------------------------------------------
|
91
|
+
|
92
|
+
Portions of this code are from the Spoon project https://github.com/headius/spoon) and under the following license:
|
93
|
+
|
94
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
95
|
+
you may not use this file except in compliance with the License.
|
96
|
+
You may obtain a copy of the License at
|
97
|
+
|
98
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
99
|
+
|
100
|
+
[needium]: colin.surprenant@needium.com
|
101
|
+
[gmail]: colin.surprenant@gmail.com
|
102
|
+
[twitter]: http://twitter.com/colinsurprenant
|
103
|
+
[github]: http://github.com/colinsurprenant
|
data/README.md
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# Raad - Totem
|
2
|
+
|
3
|
+
This gem allows you to easily create daemons (also known as servers, services, or background processes) in a project generated with [Totem](https://github.com/chadrem/totem). Both MRI and Jruby are supported.
|
4
|
+
|
5
|
+
This is a fork of the [Raad](https://github.com/colinsurprenant/raad) gem that is modified for Totem projects. All credit goes to the original project as they did all the hard work.
|
6
|
+
|
7
|
+
## Changes from the original Raad v0.5.0:
|
8
|
+
|
9
|
+
- Removal of log4j since Totem already has a built in logger.
|
10
|
+
- Integration with Totem's environment setting.
|
11
|
+
- Change namespaces so as not to conflict with Raad if both gems are installed.
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
Add this line to your application's Gemfile:
|
16
|
+
|
17
|
+
gem 'raad-totem'
|
18
|
+
|
19
|
+
And then execute:
|
20
|
+
|
21
|
+
$ bundle
|
22
|
+
|
23
|
+
Or install it yourself as:
|
24
|
+
|
25
|
+
$ gem install raad-totem
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
Coming soon.
|
30
|
+
|
31
|
+
## Contributing
|
32
|
+
|
33
|
+
1. Fork it
|
34
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
35
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
36
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
37
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
|
3
|
+
task :default => :spec
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'rspec/core/rake_task'
|
7
|
+
desc 'run specs in the current ruby env'
|
8
|
+
task :spec do
|
9
|
+
sh 'ruby -v'
|
10
|
+
RSpec::Core::RakeTask.new
|
11
|
+
end
|
12
|
+
rescue NameError, LoadError => e
|
13
|
+
puts e
|
14
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module RaadTotem
|
2
|
+
|
3
|
+
# The bootstrap class for RaadTotem. This will execute in the at_exit
|
4
|
+
# handler to run the service.
|
5
|
+
class Bootstrap
|
6
|
+
|
7
|
+
CALLERS_TO_IGNORE = [ # :nodoc:
|
8
|
+
/\/raad_totem(\/(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
|
12
|
+
]
|
13
|
+
|
14
|
+
CALLERS_TO_IGNORE.concat(RUBY_IGNORE_CALLERS) if defined?(RUBY_IGNORE_CALLERS)
|
15
|
+
|
16
|
+
# Like Kernel#caller but excluding certain magic entries and without
|
17
|
+
# line / method information; the resulting array contains filenames only.
|
18
|
+
def self.caller_files
|
19
|
+
caller_locations.map { |file, line| file }
|
20
|
+
end
|
21
|
+
|
22
|
+
# like caller_files, but containing Arrays rather than strings with the
|
23
|
+
# first element being the file, and the second being the line.
|
24
|
+
def self.caller_locations
|
25
|
+
caller(1).
|
26
|
+
map { |line| line.split(/:(?=\d|in )/)[0,2] }.
|
27
|
+
reject { |file, line| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } }
|
28
|
+
end
|
29
|
+
|
30
|
+
# find the service_file that was used to execute the service
|
31
|
+
#
|
32
|
+
# @return [String] The service file
|
33
|
+
def self.service_file
|
34
|
+
c = caller_files.first
|
35
|
+
c = $0 if !c || c.empty?
|
36
|
+
c
|
37
|
+
end
|
38
|
+
|
39
|
+
# execute the service
|
40
|
+
#
|
41
|
+
# @return [Nil]
|
42
|
+
def self.run!
|
43
|
+
service_class = Object.module_eval(camel_case(File.basename(service_file, '.rb')))
|
44
|
+
Runner.new(ARGV, service_class).run
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# convert a string to camel case
|
50
|
+
#
|
51
|
+
# @param str [String] The string to convert
|
52
|
+
# @return [String] The camel cased string
|
53
|
+
def self.camel_case(str)
|
54
|
+
return str if str !~ /_/ && str =~ /[A-Z]+.*/
|
55
|
+
|
56
|
+
str.split('_').map { |e| e.capitalize }.join
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
at_exit do
|
61
|
+
unless defined?($RAAD_TOTEM_NOT_RUN)
|
62
|
+
if $!.nil? && $0 == RaadTotem::Bootstrap.service_file
|
63
|
+
RaadTotem::Bootstrap.run!
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'rbconfig'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
module RaadTotem
|
5
|
+
|
6
|
+
@custom_options = {}
|
7
|
+
@stopped = false
|
8
|
+
@stop_lock = Mutex.new
|
9
|
+
|
10
|
+
# are we running inside jruby
|
11
|
+
#
|
12
|
+
# @return [Boolean] true if runnig inside jruby
|
13
|
+
def jruby?
|
14
|
+
!!(defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby')
|
15
|
+
end
|
16
|
+
|
17
|
+
# absolute path of current interpreter
|
18
|
+
#
|
19
|
+
# @return [String] absolute path of current interpreter
|
20
|
+
def ruby_path
|
21
|
+
File.join(RbConfig::CONFIG["bindir"], RbConfig::CONFIG["RUBY_INSTALL_NAME"] + RbConfig::CONFIG["EXEEXT"])
|
22
|
+
end
|
23
|
+
|
24
|
+
# ruby interpreter command line options
|
25
|
+
#
|
26
|
+
# @return [Array] command line options list
|
27
|
+
def ruby_options
|
28
|
+
@ruby_options ||= []
|
29
|
+
end
|
30
|
+
|
31
|
+
# set ruby interpreter command line options
|
32
|
+
#
|
33
|
+
# @param [String] options_str space separated options list
|
34
|
+
def ruby_options=(options_str)
|
35
|
+
@ruby_options = options_str.split
|
36
|
+
end
|
37
|
+
|
38
|
+
# 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 RaadTotem.
|
39
|
+
#
|
40
|
+
# @return [Boolean] true is the service has been stopped
|
41
|
+
def stopped?
|
42
|
+
@stop_lock.synchronize{@stopped}
|
43
|
+
end
|
44
|
+
|
45
|
+
# used internally to set the stopped flag
|
46
|
+
#
|
47
|
+
# @param [Boolean] state true to set the stopped flag
|
48
|
+
def stopped=(state)
|
49
|
+
@stop_lock.synchronize{@stopped = !!state}
|
50
|
+
end
|
51
|
+
|
52
|
+
# returns the custom options hash set in the service options_parser class method
|
53
|
+
#
|
54
|
+
# @return [Hash] custom options hash
|
55
|
+
def custom_options
|
56
|
+
@custom_options
|
57
|
+
end
|
58
|
+
|
59
|
+
module_function :jruby?, :ruby_path, :ruby_options, :ruby_options=, :stopped?, :stopped=, :custom_options
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'timeout'
|
3
|
+
require 'thread'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
module RaadTotem
|
7
|
+
class Runner
|
8
|
+
include Daemonizable
|
9
|
+
|
10
|
+
SECOND = 1
|
11
|
+
STOP_TIMEOUT = 60 * SECOND
|
12
|
+
|
13
|
+
attr_accessor :pid_file
|
14
|
+
|
15
|
+
# Create a new Runner
|
16
|
+
#
|
17
|
+
# @param argv [Array] command line arguments
|
18
|
+
# @param service_class [Class] service class
|
19
|
+
def initialize(argv, service_class)
|
20
|
+
@argv = argv.dup # lets keep a copy for jruby double-launch
|
21
|
+
@service_class = service_class
|
22
|
+
@parsed_options = nil
|
23
|
+
|
24
|
+
@stop_lock = Mutex.new
|
25
|
+
@stop_signaled = false
|
26
|
+
|
27
|
+
# parse command line options and set @parsed_options
|
28
|
+
options_parser = service_class.respond_to?(:options_parser) ? service_class.options_parser(create_options_parser, RaadTotem.custom_options) : create_options_parser
|
29
|
+
begin
|
30
|
+
options_parser.parse!(argv)
|
31
|
+
rescue OptionParser::InvalidOption => e
|
32
|
+
puts(">> #{e.message}")
|
33
|
+
exit!(false)
|
34
|
+
end
|
35
|
+
|
36
|
+
# grab what's left after options, which should be the start/stop command
|
37
|
+
@parsed_options[:command] = argv[0].to_s.downcase
|
38
|
+
unless ['start', 'stop', 'post_fork'].include?(@parsed_options[:command])
|
39
|
+
puts(">> start|stop command is required")
|
40
|
+
exit!(false)
|
41
|
+
end
|
42
|
+
|
43
|
+
Totem.component = default_service_name(service_class)
|
44
|
+
|
45
|
+
# @pid_file is required to become Daemonizable
|
46
|
+
@pid_file = @parsed_options.delete(:pid_file) || default_pid_path
|
47
|
+
FileUtils.mkdir_p(File.dirname(@pid_file))
|
48
|
+
|
49
|
+
# default stop timeout
|
50
|
+
@stop_timeout = (@parsed_options.delete(:stop_timeout) || STOP_TIMEOUT).to_i
|
51
|
+
end
|
52
|
+
|
53
|
+
def run
|
54
|
+
# check for stop command, @pid_file must be set
|
55
|
+
if @parsed_options[:command] == 'stop'
|
56
|
+
puts(">> RaadTotem service wrapper v#{VERSION} stopping")
|
57
|
+
# first send the TERM signal which will invoke the daemon wait_or_will method which will timeout after @stop_timeout
|
58
|
+
# if still not stopped afer @stop_timeout + 2 seconds, KILL -9 will be sent.
|
59
|
+
success = send_signal('TERM', @stop_timeout + (2 * SECOND))
|
60
|
+
exit(success)
|
61
|
+
end
|
62
|
+
|
63
|
+
Dir.chdir(File.expand_path(File.dirname("./")))
|
64
|
+
|
65
|
+
if @parsed_options[:command] == 'post_fork'
|
66
|
+
# we've been spawned and re executed, finish setup
|
67
|
+
post_fork_setup(Totem.component, (@parsed_options[:redirect] || default_redirect_path))
|
68
|
+
start_service
|
69
|
+
else
|
70
|
+
puts(">> RaadTotem service wrapper v#{VERSION} starting")
|
71
|
+
@parsed_options[:daemonize] ? daemonize(@argv, Totem.component, @parsed_options[:redirect]) {start_service} : start_service
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def default_redirect_path
|
78
|
+
return File.join(Totem.root, 'log', "#{Totem.process_name}.stdout")
|
79
|
+
end
|
80
|
+
|
81
|
+
def default_pid_path
|
82
|
+
return File.join(Totem.root, 'tmp', 'pid', "#{Totem.process_name}.pid")
|
83
|
+
end
|
84
|
+
|
85
|
+
def start_service
|
86
|
+
Totem.logger.debug("initializing #{Totem.component} service")
|
87
|
+
|
88
|
+
# create service instance
|
89
|
+
service = @service_class.new
|
90
|
+
|
91
|
+
# important to display this after service instantiation.
|
92
|
+
Totem.logger.info("starting #{Totem.component} service")
|
93
|
+
|
94
|
+
at_exit do
|
95
|
+
Totem.logger.info(">> RaadTotem service wrapper stopped")
|
96
|
+
end
|
97
|
+
|
98
|
+
# do not trap :QUIT because its not supported in jruby
|
99
|
+
[:INT, :TERM].each{|sig| SignalTrampoline.trap(sig) {stop_service(service)}}
|
100
|
+
|
101
|
+
# launch the service thread and call start. we expect start not to return
|
102
|
+
# unless it is done or has been stopped.
|
103
|
+
service_thread = Thread.new do
|
104
|
+
Thread.current.abort_on_exception = true
|
105
|
+
service.start
|
106
|
+
stop_service(service)
|
107
|
+
end
|
108
|
+
|
109
|
+
result = wait_or_kill(service_thread)
|
110
|
+
# if not daemonized start a sentinel thread, if still alive after 2 seconds, do arakiri
|
111
|
+
Thread.new{sleep(2 * SECOND); Process.kill(:KILL, Process.pid)} unless @parsed_options[:daemonize]
|
112
|
+
# use exit and not exit! to make sure the at_exit hooks are called, like the pid cleanup, etc.
|
113
|
+
exit(result)
|
114
|
+
end
|
115
|
+
|
116
|
+
def stop_service(service)
|
117
|
+
return if @stop_lock.synchronize{s = @stop_signaled; @stop_signaled = true; s}
|
118
|
+
|
119
|
+
Totem.logger.info("stopping #{Totem.component} service")
|
120
|
+
service.stop if service.respond_to?(:stop)
|
121
|
+
RaadTotem.stopped = true
|
122
|
+
end
|
123
|
+
|
124
|
+
# try to do a timeout join periodically on the given thread. if the join succeed then the stop
|
125
|
+
# sequence is successful and return true.
|
126
|
+
# Otherwise, on timeout if stop has beed signaled, wait a maximum of @stop_timeout on the
|
127
|
+
# thread and kill it if the timeout is reached and return false in that case.
|
128
|
+
#
|
129
|
+
# @return [Boolean] true if the thread normally terminated, false if a kill was necessary
|
130
|
+
def wait_or_kill(thread)
|
131
|
+
while thread.join(SECOND).nil?
|
132
|
+
# the join has timed out, thread is still buzzy.
|
133
|
+
if @stop_lock.synchronize{@stop_signaled}
|
134
|
+
# but if stop has been signalled, start "the final countdown" ♫
|
135
|
+
try = 0; join = nil
|
136
|
+
while (try += 1) <= @stop_timeout && join.nil? do
|
137
|
+
join = thread.join(SECOND)
|
138
|
+
Totem.logger.debug("waiting for service to stop #{try}/#{@stop_timeout}") if join.nil?
|
139
|
+
end
|
140
|
+
if join.nil?
|
141
|
+
Totem.logger.error("stop timeout exhausted, killing service thread")
|
142
|
+
thread.kill
|
143
|
+
return false
|
144
|
+
end
|
145
|
+
return true
|
146
|
+
end
|
147
|
+
end
|
148
|
+
true
|
149
|
+
end
|
150
|
+
|
151
|
+
# convert the service class name from CameCase to underscore
|
152
|
+
#
|
153
|
+
# @return [String] underscored service class name
|
154
|
+
def default_service_name(clazz)
|
155
|
+
clazz.to_s.split('::').last.gsub(/(.)([A-Z])/,'\1_\2').downcase!
|
156
|
+
end
|
157
|
+
|
158
|
+
# Create the options parser
|
159
|
+
#
|
160
|
+
# @return [OptionParser] Creates the options parser for the runner with the default options
|
161
|
+
def create_options_parser
|
162
|
+
@parsed_options ||= {
|
163
|
+
:daemonize => false
|
164
|
+
}
|
165
|
+
|
166
|
+
options_parser ||= OptionParser.new do |opts|
|
167
|
+
opts.banner = "USAGE: ruby <service>.rb [options] start|stop"
|
168
|
+
|
169
|
+
opts.separator ""
|
170
|
+
opts.separator "Service Options:"
|
171
|
+
|
172
|
+
opts.on('-d', '--daemonize', "run daemonized in the background (default: no)") { |v| @parsed_options[:daemonize] = v }
|
173
|
+
opts.on('-P', '--pid FILE', "pid file when daemonized (default: tmp/pids directory)") { |file| @parsed_options[:pid_file] = file }
|
174
|
+
opts.on('-r', '--redirect FILE', "redirect stdout when daemonized (default: log directory)") { |v| @parsed_options[:redirect] = v }
|
175
|
+
opts.on('-e', '--environment ENVIRONMENT', "Totem environment") { |v| Totem.env = v }
|
176
|
+
opts.on('-i', '--instance INSTANCE', "Totem instance") { |v| Totem.instance = v }
|
177
|
+
opts.on(nil, '--timeout SECONDS', "seconds to wait before force stopping the service (default: 60)") { |v| @parsed_options[:stop_timeout] = v }
|
178
|
+
opts.on(nil, '--ruby opts', "daemonized ruby interpreter specifc options") { |v| RaadTotem.ruby_options = v }
|
179
|
+
|
180
|
+
opts.on('-h', '--help', 'display help message') { show_options(options_parser) }
|
181
|
+
end
|
182
|
+
|
183
|
+
options_parser
|
184
|
+
end
|
185
|
+
|
186
|
+
# Output the servers options and exit Ruby
|
187
|
+
#
|
188
|
+
# @param opts [OptionsParser] The options parser
|
189
|
+
def show_options(opts)
|
190
|
+
puts(opts)
|
191
|
+
exit!(false)
|
192
|
+
end
|
193
|
+
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module RaadTotem
|
2
|
+
module ShellCmds
|
3
|
+
class Service < Totem::ShellCmds::Base
|
4
|
+
def run
|
5
|
+
case @args[0]
|
6
|
+
when 'new' then new_s(@args[1])
|
7
|
+
else
|
8
|
+
puts_usage
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def new_s(service)
|
13
|
+
return false unless require_arg(service, :service)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def puts_usage
|
19
|
+
puts "Usage:\n bundle exec totem service <command>"
|
20
|
+
puts
|
21
|
+
puts "Commands:\n"
|
22
|
+
puts " new <service> - Create a new service."
|
23
|
+
end
|
24
|
+
|
25
|
+
def puts_error(message)
|
26
|
+
puts "ERROR: #{message}"
|
27
|
+
puts
|
28
|
+
puts_usage
|
29
|
+
end
|
30
|
+
|
31
|
+
def require_arg(val, name)
|
32
|
+
if val.nil? || val.length == 0
|
33
|
+
puts_error("You must provide a #{name}.")
|
34
|
+
return false
|
35
|
+
end
|
36
|
+
|
37
|
+
return true
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
Totem::Shell.register_cmd(:service, RaadTotem::ShellCmds::Service)
|
@@ -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
|
@@ -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
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'etc'
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
require 'raad/spoon' if RaadTotem.jruby?
|
5
|
+
|
6
|
+
module Process
|
7
|
+
|
8
|
+
def running?(pid)
|
9
|
+
Process.getpgid(pid) != -1
|
10
|
+
rescue Errno::EPERM
|
11
|
+
true
|
12
|
+
rescue Errno::ESRCH
|
13
|
+
false
|
14
|
+
end
|
15
|
+
|
16
|
+
module_function :running?
|
17
|
+
end
|
18
|
+
|
19
|
+
# Raised when the pid file already exist starting as a daemon.
|
20
|
+
class PidFileExist < RuntimeError; end
|
21
|
+
|
22
|
+
# module Daemonizable requires that the including class defines the @pid_file instance variable
|
23
|
+
module Daemonizable
|
24
|
+
|
25
|
+
def pid
|
26
|
+
File.exist?(@pid_file) ? open(@pid_file).read.to_i : nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def daemonize(argv, name, stdout_file = nil)
|
30
|
+
remove_stale_pid_file
|
31
|
+
pwd = Dir.pwd
|
32
|
+
|
33
|
+
if RaadTotem.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
|
+
spanw_options = [RaadTotem.ruby_path].concat(RaadTotem.ruby_options)
|
37
|
+
spanw_options << $0
|
38
|
+
spanw_options.concat(argv.map{|arg| arg == 'start' ? 'post_fork' : arg})
|
39
|
+
Spoon.spawnp(*spanw_options)
|
40
|
+
else
|
41
|
+
# do the double fork dance
|
42
|
+
Process.fork do
|
43
|
+
Process.setsid
|
44
|
+
exit if fork # exit parent
|
45
|
+
|
46
|
+
Dir.chdir(pwd)
|
47
|
+
post_fork_setup(name, stdout_file)
|
48
|
+
|
49
|
+
yield
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def post_fork_setup(name, stdout_file = nil)
|
55
|
+
$0 = name # set process name, does not work with jruby
|
56
|
+
|
57
|
+
File.umask(0000) # file mode creation mask to 000 to allow creation of files with any required permission late
|
58
|
+
write_pid_file
|
59
|
+
|
60
|
+
# redirect stdin, stdout, stderr
|
61
|
+
STDIN.reopen('/dev/null')
|
62
|
+
stdout_file ? STDOUT.reopen(stdout_file, "a") : STDOUT.reopen('/dev/null', 'a')
|
63
|
+
STDERR.reopen(STDOUT)
|
64
|
+
|
65
|
+
at_exit do
|
66
|
+
remove_pid_file
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def send_signal(signal, timeout = 60)
|
71
|
+
if pid = read_pid_file
|
72
|
+
puts(">> sending #{signal} signal to process #{pid}")
|
73
|
+
Process.kill(signal, pid)
|
74
|
+
Timeout.timeout(timeout) do
|
75
|
+
sleep 0.1 while Process.running?(pid)
|
76
|
+
end
|
77
|
+
true
|
78
|
+
else
|
79
|
+
puts(">> can't stop process, no pid found in #{@pid_file}")
|
80
|
+
false
|
81
|
+
end
|
82
|
+
rescue Timeout::Error
|
83
|
+
force_kill_and_remove_pid_file(pid)
|
84
|
+
rescue Interrupt
|
85
|
+
force_kill_and_remove_pid_file(pid)
|
86
|
+
rescue Errno::ESRCH # No such process
|
87
|
+
puts(">> can't stop process, no such process #{pid}")
|
88
|
+
remove_pid_file
|
89
|
+
false
|
90
|
+
end
|
91
|
+
|
92
|
+
def force_kill_and_remove_pid_file(pid)
|
93
|
+
puts(">> sending KILL signal to process #{pid}")
|
94
|
+
Process.kill("KILL", pid)
|
95
|
+
remove_pid_file
|
96
|
+
true
|
97
|
+
rescue Errno::ESRCH # No such process
|
98
|
+
puts(">> can't send KILL, no such process #{pid}")
|
99
|
+
remove_pid_file
|
100
|
+
false
|
101
|
+
end
|
102
|
+
|
103
|
+
def read_pid_file
|
104
|
+
if File.file?(@pid_file) && pid = File.read(@pid_file)
|
105
|
+
pid.to_i
|
106
|
+
else
|
107
|
+
nil
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def remove_pid_file
|
112
|
+
File.delete(@pid_file) if @pid_file && File.exists?(@pid_file)
|
113
|
+
end
|
114
|
+
|
115
|
+
def write_pid_file
|
116
|
+
open(@pid_file,"w") { |f| f.write(Process.pid) }
|
117
|
+
File.chmod(0644, @pid_file)
|
118
|
+
end
|
119
|
+
|
120
|
+
def remove_stale_pid_file
|
121
|
+
if File.exist?(@pid_file)
|
122
|
+
if pid && Process.running?(pid)
|
123
|
+
raise PidFileExist, "#{@pid_file} exists and process #{pid} is runnig. stop the process or delete #{@pid_file}"
|
124
|
+
else
|
125
|
+
puts(">> deleting stale pid file #{@pid_file}")
|
126
|
+
remove_pid_file
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
data/lib/raad_totem.rb
ADDED
data/raad.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'raad_totem/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'raad_totem'
|
8
|
+
spec.version = RaadTotem::VERSION
|
9
|
+
spec.authors = ['Colin Surprenant', 'Chad Remesch']
|
10
|
+
spec.email = ['colin.surprenant@gmail.com', 'chad@remesch.com']
|
11
|
+
spec.summary = %q{Easily create daemons in Totem projects (fork of Raad gem)}
|
12
|
+
spec.description = %q{Easily create daemons in Totem projects (fork of Raad gem)}
|
13
|
+
spec.homepage = 'https://github.com/chadrem/raad_totem'
|
14
|
+
spec.license = 'Apache'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_dependency('totem', '>= 0.0.7')
|
22
|
+
|
23
|
+
spec.add_development_dependency('bundler', '~> 1.5')
|
24
|
+
spec.add_development_dependency('rake')
|
25
|
+
spec.add_development_dependency "rspec", ['~> 2.8.0']
|
26
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# @TODO workaround to bug with Kernel.caller between 1.8 and 1.9 need to dig in this.
|
4
|
+
$RAAD_TOTEM_NOT_RUN=true
|
5
|
+
require 'raad_totem/bootstrap'
|
6
|
+
|
7
|
+
describe RaadTotem::Bootstrap do
|
8
|
+
it "should work" do
|
9
|
+
true.should be_true
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'raad_totem/env'
|
3
|
+
|
4
|
+
describe "RaadTotem env" do
|
5
|
+
it "should test for jruby" do
|
6
|
+
[true, false].should include(RaadTotem.jruby?)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should report ruby path" do
|
10
|
+
File.exist?(RaadTotem.ruby_path).should be_true
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should default to empty ruby_options" do
|
14
|
+
RaadTotem.ruby_options.should == []
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should set ruby_options" do
|
18
|
+
RaadTotem.ruby_options = "a b"
|
19
|
+
RaadTotem.ruby_options.should == ['a', 'b']
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'raad_totem/signal_trampoline'
|
3
|
+
require 'thread'
|
4
|
+
require 'timeout'
|
5
|
+
|
6
|
+
describe SignalTrampoline do
|
7
|
+
it "should trap signal" do
|
8
|
+
t = Thread.new{Thread.stop}
|
9
|
+
SignalTrampoline.trap(:USR2) {t.run}
|
10
|
+
t.alive?.should be_true
|
11
|
+
Timeout.timeout(5) {sleep(0.1) while !t.stop?} # avoid race condition
|
12
|
+
t.stop?.should be_true
|
13
|
+
Process.kill(:USR2, Process.pid)
|
14
|
+
Timeout.timeout(5) {sleep(0.1) while t.alive?}
|
15
|
+
t.alive?.should be_false
|
16
|
+
t.join(5).should == t
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should raise on bad signal" do
|
20
|
+
lambda{SignalTrampoline.trap(:WAGADOUDOU) {}}.should raise_exception
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,201 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'raad_totem/env'
|
3
|
+
require 'raad_totem/unix_daemon'
|
4
|
+
require 'timeout'
|
5
|
+
|
6
|
+
class TestService
|
7
|
+
include Daemonizable
|
8
|
+
|
9
|
+
attr_accessor :pid_file
|
10
|
+
|
11
|
+
def initialize(pid_file)
|
12
|
+
@pid_file = pid_file
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module Spoon
|
17
|
+
end unless RaadTotem.jruby?
|
18
|
+
|
19
|
+
describe 'UnixDaemon' do
|
20
|
+
|
21
|
+
before :all do
|
22
|
+
@l = 'test.log'
|
23
|
+
@p = 'test.pid'
|
24
|
+
File.delete(@l) if File.exist?(@l)
|
25
|
+
File.delete(@p) if File.exist?(@p)
|
26
|
+
end
|
27
|
+
|
28
|
+
before :each do
|
29
|
+
File.delete(@l) if File.exist?(@l) rescue nil
|
30
|
+
File.delete(@p) if File.exist?(@p) rescue nil
|
31
|
+
@service = TestService.new(@p)
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "non jruby daemonize" do
|
35
|
+
|
36
|
+
it 'should create a pid file' do
|
37
|
+
@service.should_receive(:remove_stale_pid_file).once
|
38
|
+
@service.daemonize([], 'test') do
|
39
|
+
Thread.new{Thread.stop}.join(5)
|
40
|
+
end
|
41
|
+
|
42
|
+
Timeout.timeout(5) do
|
43
|
+
while !File.exist?(@service.pid_file); sleep(0.1); end
|
44
|
+
true
|
45
|
+
end.should == true
|
46
|
+
|
47
|
+
(pid = @service.pid).should > 0
|
48
|
+
Process.kill(:TERM, pid)
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should redirect stdio to a log file' do
|
52
|
+
@service.daemonize([], 'test', @l) do
|
53
|
+
puts("puts"); STDOUT.flush
|
54
|
+
STDERR.puts("STDERR.puts"); STDERR.flush
|
55
|
+
STDOUT.puts("STDOUT.puts"); STDOUT.flush
|
56
|
+
end
|
57
|
+
|
58
|
+
Timeout.timeout(5) do
|
59
|
+
while !File.exist?(@l); sleep(0.1); end
|
60
|
+
true
|
61
|
+
end.should == true
|
62
|
+
|
63
|
+
f = File.new(@l, "r")
|
64
|
+
Timeout.timeout(5) do
|
65
|
+
while (l = f.readlines) != ["puts\n", "STDERR.puts\n", "STDOUT.puts\n"]; sleep(0.1); f.rewind; end
|
66
|
+
true
|
67
|
+
end.should == true
|
68
|
+
end
|
69
|
+
end unless RaadTotem.jruby?
|
70
|
+
|
71
|
+
describe "jruby daemonize (from any ruby)" do
|
72
|
+
|
73
|
+
# we don't have to use jruby for this test, Spoon is mocked when not jruby.
|
74
|
+
|
75
|
+
it "should swap start for post_fork and call spawnp with args" do
|
76
|
+
@service.should_receive(:remove_stale_pid_file).once
|
77
|
+
RaadTotem.should_receive(:jruby?).and_return(true)
|
78
|
+
Spoon.should_receive(:spawnp).with(RaadTotem.ruby_path, "-JXmx=256m", $0, "test", "post_fork")
|
79
|
+
|
80
|
+
RaadTotem.ruby_options = "-JXmx=256m"
|
81
|
+
@service.daemonize(["test", "start"], 'test')
|
82
|
+
RaadTotem.ruby_options = ""
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "jruby daemonize (only in jruby)" do
|
87
|
+
|
88
|
+
it "should daemonize" do
|
89
|
+
false.should == false
|
90
|
+
end
|
91
|
+
end if RaadTotem.jruby?
|
92
|
+
|
93
|
+
describe "post_fork_setup" do
|
94
|
+
|
95
|
+
it 'should create a pid file' do
|
96
|
+
STDIN.should_receive(:reopen)
|
97
|
+
STDOUT.should_receive(:reopen)
|
98
|
+
STDERR.should_receive(:reopen)
|
99
|
+
@service.post_fork_setup('test', nil)
|
100
|
+
File.exist?(@service.pid_file).should be_true
|
101
|
+
@service.pid.should == Process.pid
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'should redirect stdio to a log file' do
|
105
|
+
@service = TestService.new(@p)
|
106
|
+
|
107
|
+
STDIN.should_receive(:reopen).with("/dev/null")
|
108
|
+
STDOUT.should_receive(:reopen).with(@l, 'a')
|
109
|
+
STDERR.should_receive(:reopen).with(STDOUT)
|
110
|
+
|
111
|
+
@service.post_fork_setup('test', @l)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe 'read/write/remove pid file' do
|
116
|
+
|
117
|
+
it 'should write pid file' do
|
118
|
+
@service.write_pid_file
|
119
|
+
File.exist?(@service.pid_file).should be_true
|
120
|
+
File.read(@p).to_i.should == Process.pid
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'should read pid file' do
|
124
|
+
@service.write_pid_file
|
125
|
+
@service.read_pid_file.should == Process.pid
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'should remove pid file' do
|
129
|
+
@service.write_pid_file
|
130
|
+
File.exist?(@service.pid_file).should be_true
|
131
|
+
@service.remove_pid_file
|
132
|
+
File.exist?(@service.pid_file).should be_false
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
describe 'send_signal' do
|
137
|
+
|
138
|
+
it 'should send signal and terminate process' do
|
139
|
+
@service.write_pid_file
|
140
|
+
t = Thread.new{Thread.stop}
|
141
|
+
Kernel.trap(:USR2) {Thread.new{t.run}}
|
142
|
+
Process.should_receive(:running?).once.and_return(false)
|
143
|
+
$stdout.should_receive(:write).twice # mute trace
|
144
|
+
@service.send_signal(:USR2, 5).should be_true
|
145
|
+
Timeout.timeout(5) {t.join}
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'should force kill on Timeout::Error exception' do
|
149
|
+
@service.write_pid_file
|
150
|
+
Process.should_receive(:kill).and_raise(Timeout::Error)
|
151
|
+
@service.should_receive(:force_kill_and_remove_pid_file).and_return(true)
|
152
|
+
$stdout.should_receive(:write).twice # mute trace
|
153
|
+
@service.send_signal(:USR2, 5).should be_true
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'should force kill on Interrupt exception' do
|
157
|
+
@service.write_pid_file
|
158
|
+
Process.should_receive(:kill).and_raise(Interrupt)
|
159
|
+
@service.should_receive(:force_kill_and_remove_pid_file).and_return(true)
|
160
|
+
$stdout.should_receive(:write).twice # mute trace
|
161
|
+
@service.send_signal(:USR2, 5).should be_true
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'should remove pid file on Errno::ESRCH exception' do
|
165
|
+
@service.write_pid_file
|
166
|
+
Process.should_receive(:kill).and_raise(Errno::ESRCH)
|
167
|
+
$stdout.should_receive(:write).exactly(4).times # mute trace
|
168
|
+
@service.should_receive(:remove_pid_file)
|
169
|
+
@service.send_signal(:USR2, 5).should be_false
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def force_kill_and_remove_pid_file(pid)
|
174
|
+
puts(">> sending KILL signal to process #{pid}")
|
175
|
+
Process.kill("KILL", pid)
|
176
|
+
remove_pid_file
|
177
|
+
true
|
178
|
+
rescue Errno::ESRCH # No such process
|
179
|
+
puts(">> can't send KILL, no such process #{pid}")
|
180
|
+
remove_pid_file
|
181
|
+
false
|
182
|
+
end
|
183
|
+
|
184
|
+
describe 'force_kill_and_remove_pid_file' do
|
185
|
+
|
186
|
+
it 'should send KILL signal and terminate process' do
|
187
|
+
@service.write_pid_file
|
188
|
+
Process.should_receive(:kill).with("KILL", 666).once
|
189
|
+
$stdout.should_receive(:write).twice # mute trace
|
190
|
+
@service.force_kill_and_remove_pid_file(666).should be_true
|
191
|
+
end
|
192
|
+
|
193
|
+
it 'should remove pid file on no such process exception' do
|
194
|
+
@service.write_pid_file
|
195
|
+
$stdout.should_receive(:write).exactly(4).times # mute trace
|
196
|
+
Process.should_receive(:kill).with("KILL", 666).once.and_raise(Errno::ESRCH)
|
197
|
+
@service.should_receive(:remove_pid_file)
|
198
|
+
@service.force_kill_and_remove_pid_file(666).should be_false
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: raad_totem
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Colin Surprenant
|
8
|
+
- Chad Remesch
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-03-15 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: totem
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: 0.0.7
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: 0.0.7
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: bundler
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '1.5'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '1.5'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: rake
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: rspec
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 2.8.0
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 2.8.0
|
70
|
+
description: Easily create daemons in Totem projects (fork of Raad gem)
|
71
|
+
email:
|
72
|
+
- colin.surprenant@gmail.com
|
73
|
+
- chad@remesch.com
|
74
|
+
executables: []
|
75
|
+
extensions: []
|
76
|
+
extra_rdoc_files: []
|
77
|
+
files:
|
78
|
+
- ".gitignore"
|
79
|
+
- ".travis.yml"
|
80
|
+
- CHANGELOG.md
|
81
|
+
- Gemfile
|
82
|
+
- Gemfile.lock
|
83
|
+
- LICENSE.md
|
84
|
+
- README.md
|
85
|
+
- Rakefile
|
86
|
+
- lib/raad_totem.rb
|
87
|
+
- lib/raad_totem/bootstrap.rb
|
88
|
+
- lib/raad_totem/env.rb
|
89
|
+
- lib/raad_totem/runner.rb
|
90
|
+
- lib/raad_totem/shell_cmds/service.rb
|
91
|
+
- lib/raad_totem/signal_trampoline.rb
|
92
|
+
- lib/raad_totem/spoon.rb
|
93
|
+
- lib/raad_totem/unix_daemon.rb
|
94
|
+
- lib/raad_totem/version.rb
|
95
|
+
- raad.gemspec
|
96
|
+
- spec/raad_totem/unit/bootstrap_spec.rb
|
97
|
+
- spec/raad_totem/unit/env_spec.rb
|
98
|
+
- spec/raad_totem/unit/signal_trampoline_spec.rb
|
99
|
+
- spec/raad_totem/unit/spoon_spec.rb
|
100
|
+
- spec/raad_totem/unit/unix_daemon_spec.rb
|
101
|
+
- spec/raad_totem/unit/version_spec.rb
|
102
|
+
- spec/spec_helper.rb
|
103
|
+
homepage: https://github.com/chadrem/raad_totem
|
104
|
+
licenses:
|
105
|
+
- Apache
|
106
|
+
metadata: {}
|
107
|
+
post_install_message:
|
108
|
+
rdoc_options: []
|
109
|
+
require_paths:
|
110
|
+
- lib
|
111
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0'
|
116
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
requirements: []
|
122
|
+
rubyforge_project:
|
123
|
+
rubygems_version: 2.2.2
|
124
|
+
signing_key:
|
125
|
+
specification_version: 4
|
126
|
+
summary: Easily create daemons in Totem projects (fork of Raad gem)
|
127
|
+
test_files:
|
128
|
+
- spec/raad_totem/unit/bootstrap_spec.rb
|
129
|
+
- spec/raad_totem/unit/env_spec.rb
|
130
|
+
- spec/raad_totem/unit/signal_trampoline_spec.rb
|
131
|
+
- spec/raad_totem/unit/spoon_spec.rb
|
132
|
+
- spec/raad_totem/unit/unix_daemon_spec.rb
|
133
|
+
- spec/raad_totem/unit/version_spec.rb
|
134
|
+
- spec/spec_helper.rb
|