raad_totem 0.0.2

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.
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
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.2
5
+ - ree
6
+ - jruby-18mode
7
+ - jruby-19mode
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
@@ -0,0 +1,2 @@
1
+ source "http://rubygems.org"
2
+ gemspec
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
@@ -0,0 +1,3 @@
1
+ module RaadTotem
2
+ VERSION = '0.0.2'
3
+ end unless defined?(RaadTotem::VERSION)
data/lib/raad_totem.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'totem'
2
+
3
+ require 'raad_totem/version'
4
+ require 'raad_totem/env'
5
+ require 'raad_totem/unix_daemon'
6
+ require 'raad_totem/signal_trampoline'
7
+ require 'raad_totem/runner'
8
+ require 'raad_totem/bootstrap'
9
+ require 'raad_totem/shell_cmds/service'
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,13 @@
1
+ require 'spec_helper'
2
+ require 'raad_totem/env'
3
+ require 'raad_totem/spoon' if RaadTotem.jruby?
4
+
5
+ if RaadTotem.jruby?
6
+
7
+ describe Spoon do
8
+ it "should work" do
9
+ true.should be_true
10
+ end
11
+ end
12
+
13
+ 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
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+ require 'raad_totem/version'
3
+
4
+ describe "RaadTotem VERSION" do
5
+ it "should work" do
6
+ true.should be_true
7
+ end
8
+ end
@@ -0,0 +1,4 @@
1
+ $:.unshift File.dirname(__FILE__) + '/../lib/'
2
+ $:.unshift File.dirname(__FILE__) + '/../spec'
3
+
4
+ require 'rspec'
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