raad_totem 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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