kennethkalmer-daemon-kit 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +7 -0
- data/Manifest.txt +58 -0
- data/PostInstall.txt +6 -0
- data/README.textile +77 -0
- data/Rakefile +30 -0
- data/TODO.txt +24 -0
- data/app_generators/daemon_kit/USAGE +7 -0
- data/app_generators/daemon_kit/daemon_kit_generator.rb +121 -0
- data/app_generators/daemon_kit/templates/README +48 -0
- data/app_generators/daemon_kit/templates/Rakefile +4 -0
- data/app_generators/daemon_kit/templates/bin/daemon.erb +7 -0
- data/app_generators/daemon_kit/templates/config/boot.rb +52 -0
- data/app_generators/daemon_kit/templates/config/environment.rb +19 -0
- data/app_generators/daemon_kit/templates/config/environments/development.rb +0 -0
- data/app_generators/daemon_kit/templates/config/environments/production.rb +0 -0
- data/app_generators/daemon_kit/templates/config/environments/test.rb +0 -0
- data/app_generators/daemon_kit/templates/config/initializers/readme +11 -0
- data/app_generators/daemon_kit/templates/libexec/daemon.erb +18 -0
- data/bin/daemon_kit +19 -0
- data/daemon_generators/jabber/USAGE +5 -0
- data/daemon_generators/jabber/jabber_generator.rb +65 -0
- data/daemon_generators/jabber/templates/config/initializers/jabber.rb +8 -0
- data/daemon_generators/jabber/templates/config/jabber.yml +26 -0
- data/daemon_generators/jabber/templates/libexec/daemon.rb +27 -0
- data/lib/daemon_kit/application.rb +32 -0
- data/lib/daemon_kit/initializer.rb +249 -0
- data/lib/daemon_kit/jabber.rb +172 -0
- data/lib/daemon_kit/patches/force_kill_wait.rb +120 -0
- data/lib/daemon_kit/tasks/framework.rake +75 -0
- data/lib/daemon_kit/tasks.rb +2 -0
- data/lib/daemon_kit.rb +11 -0
- data/rubygems_generators/install_rspec/USAGE +5 -0
- data/rubygems_generators/install_rspec/install_rspec_generator.rb +57 -0
- data/rubygems_generators/install_rspec/templates/spec/spec.opts +1 -0
- data/rubygems_generators/install_rspec/templates/spec/spec_helper.rb +10 -0
- data/rubygems_generators/install_rspec/templates/spec.rb +11 -0
- data/rubygems_generators/install_rspec/templates/tasks/rspec.rake +21 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/script/txt2html +71 -0
- data/spec/daemon_kit_spec.rb +7 -0
- data/spec/initializer_spec.rb +31 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +30 -0
- data/tasks/rspec.rake +21 -0
- data/test/test_daemon-kit_generator.rb +67 -0
- data/test/test_generator_helper.rb +29 -0
- data/test/test_jabber_generator.rb +49 -0
- metadata +144 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'daemons'
|
2
|
+
|
3
|
+
module DaemonKit
|
4
|
+
|
5
|
+
# Class responsible for making the daemons run and keep them running.
|
6
|
+
class Application
|
7
|
+
|
8
|
+
class << self
|
9
|
+
|
10
|
+
# Run the file as a daemon
|
11
|
+
def run( file )
|
12
|
+
raise DaemonNotFound.new( file ) unless File.exist?( file )
|
13
|
+
|
14
|
+
app_name = DaemonKit.configuration.daemon_name || File.basename( file )
|
15
|
+
options = { :backtrace => true, :log_output => true, :app_name => app_name }
|
16
|
+
|
17
|
+
options[:multiple] = DaemonKit.configuration.multiple
|
18
|
+
options[:force_kill_wait] = DaemonKit.configuration.force_kill_wait if DaemonKit.configuration.force_kill_wait
|
19
|
+
|
20
|
+
Daemons.run( file, options )
|
21
|
+
end
|
22
|
+
|
23
|
+
# Call this from inside a daemonized process to complete the
|
24
|
+
# initialization process
|
25
|
+
def running!
|
26
|
+
DaemonKit::Initializer.continue!
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,249 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
DAEMON_ENV = (ENV['DAEMON_ENV'] || 'development').dup unless defined?(DAEMON_ENV)
|
5
|
+
|
6
|
+
$:.unshift File.dirname(__FILE__) + '/..'
|
7
|
+
require 'daemon_kit'
|
8
|
+
|
9
|
+
module DaemonKit
|
10
|
+
|
11
|
+
class << self
|
12
|
+
|
13
|
+
def logger
|
14
|
+
@logger
|
15
|
+
end
|
16
|
+
|
17
|
+
def logger=( logger )
|
18
|
+
@logger = logger
|
19
|
+
end
|
20
|
+
|
21
|
+
def configuration
|
22
|
+
@configuration
|
23
|
+
end
|
24
|
+
|
25
|
+
def configuration=( configuration )
|
26
|
+
@configuration = configuration
|
27
|
+
end
|
28
|
+
|
29
|
+
def trap( *args, &block )
|
30
|
+
self.configuration.trap( *args, &block )
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
# This class does all the nightmare work of setting up a working
|
37
|
+
# environment for your daemon.
|
38
|
+
class Initializer
|
39
|
+
|
40
|
+
attr_reader :configuration
|
41
|
+
|
42
|
+
def self.run( configuration = Configuration.new )
|
43
|
+
yield configuration if block_given?
|
44
|
+
initializer = new configuration
|
45
|
+
initializer.before_daemonize
|
46
|
+
initializer
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.continue!
|
50
|
+
initializer = new DaemonKit.configuration
|
51
|
+
initializer.after_daemonize
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.shutdown
|
55
|
+
DaemonKit.logger.warn "Shutting down"
|
56
|
+
exit
|
57
|
+
end
|
58
|
+
|
59
|
+
def initialize( configuration )
|
60
|
+
@configuration = configuration
|
61
|
+
end
|
62
|
+
|
63
|
+
def before_daemonize
|
64
|
+
DaemonKit.configuration = @configuration
|
65
|
+
|
66
|
+
set_load_path
|
67
|
+
load_gems
|
68
|
+
load_patches
|
69
|
+
load_environment
|
70
|
+
end
|
71
|
+
|
72
|
+
def after_daemonize
|
73
|
+
initialize_logger
|
74
|
+
initialize_signal_traps
|
75
|
+
end
|
76
|
+
|
77
|
+
def set_load_path
|
78
|
+
configuration.load_paths.each do |d|
|
79
|
+
$:.unshift( "#{DAEMON_ROOT}/#{d}" ) if File.directory?( "#{DAEMON_ROOT}/#{d}" )
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def load_gems
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
def load_patches
|
88
|
+
if !!configuration.force_kill_wait
|
89
|
+
require 'daemon_kit/patches/force_kill_wait'
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def load_environment
|
94
|
+
return if @environment_loaded
|
95
|
+
@environment_loaded = true
|
96
|
+
|
97
|
+
config = configuration
|
98
|
+
|
99
|
+
eval(IO.read(configuration.environment_path), binding, configuration.environment_path)
|
100
|
+
|
101
|
+
eval(IO.read(configuration.daemon_initializer), binding, configuration.daemon_initializer) if File.exist?( configuration.daemon_initializer )
|
102
|
+
end
|
103
|
+
|
104
|
+
def initialize_logger
|
105
|
+
return if DaemonKit.logger
|
106
|
+
|
107
|
+
unless logger = configuration.logger
|
108
|
+
logger = Logger.new( configuration.log_path )
|
109
|
+
logger.level = configuration.log_level
|
110
|
+
end
|
111
|
+
|
112
|
+
DaemonKit.logger = logger
|
113
|
+
|
114
|
+
configuration.trap("USR1") {
|
115
|
+
DaemonKit.logger.level = DaemonKit.logger.debug? ? Logger::INFO : Logger::DEBUG
|
116
|
+
DaemonKit.logger.info "Log level changed to #{DaemonKit.logger.debug? ? 'DEBUG' : 'INFO' }"
|
117
|
+
}
|
118
|
+
configuration.trap("USR2") {
|
119
|
+
DaemonKit.logger.level = Logger::DEBUG
|
120
|
+
DaemonKit.logger.info "Log level changed to DEBUG"
|
121
|
+
}
|
122
|
+
|
123
|
+
DaemonKit.logger.info "DaemonKit up and running in #{DAEMON_ENV} mode"
|
124
|
+
end
|
125
|
+
|
126
|
+
def initialize_signal_traps
|
127
|
+
term_proc = Proc.new { DaemonKit::Initializer.shutdown }
|
128
|
+
configuration.trap( 'INT', term_proc )
|
129
|
+
configuration.trap( 'TERM', term_proc )
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
# Holds our various configuration values
|
135
|
+
class Configuration
|
136
|
+
# Root to the daemon
|
137
|
+
attr_reader :root_path
|
138
|
+
|
139
|
+
# List of load paths
|
140
|
+
attr_accessor :load_paths
|
141
|
+
|
142
|
+
# The log level to use, defaults to DEBUG
|
143
|
+
attr_accessor :log_level
|
144
|
+
|
145
|
+
# Path to the log file, defaults to 'log/<environment>.log'
|
146
|
+
attr_accessor :log_path
|
147
|
+
|
148
|
+
# Provide a custom logger to use
|
149
|
+
attr_accessor :logger
|
150
|
+
|
151
|
+
# The application name
|
152
|
+
attr_accessor :daemon_name
|
153
|
+
|
154
|
+
# Allow multiple copies to run?
|
155
|
+
attr_accessor :multiple
|
156
|
+
|
157
|
+
# Use the force kill patch? Give the number of seconds
|
158
|
+
attr_accessor :force_kill_wait
|
159
|
+
|
160
|
+
# Collection of signal traps
|
161
|
+
attr_reader :signal_traps
|
162
|
+
|
163
|
+
def initialize
|
164
|
+
set_root_path!
|
165
|
+
|
166
|
+
self.load_paths = default_load_paths
|
167
|
+
self.log_level = default_log_level
|
168
|
+
self.log_path = default_log_path
|
169
|
+
|
170
|
+
self.multiple = false
|
171
|
+
self.force_kill_wait = false
|
172
|
+
|
173
|
+
@signal_traps = {}
|
174
|
+
end
|
175
|
+
|
176
|
+
def environment
|
177
|
+
::DAEMON_ENV
|
178
|
+
end
|
179
|
+
|
180
|
+
# The path to the current environment's file (<tt>development.rb</tt>, etc.). By
|
181
|
+
# default the file is at <tt>config/environments/#{environment}.rb</tt>.
|
182
|
+
def environment_path
|
183
|
+
"#{root_path}/config/environments/#{environment}.rb"
|
184
|
+
end
|
185
|
+
|
186
|
+
def daemon_initializer
|
187
|
+
"#{root_path}/config/initializers/#{self.daemon_name}.rb"
|
188
|
+
end
|
189
|
+
|
190
|
+
# Add a trap for the specified signal, can be code block or a proc
|
191
|
+
def trap( signal, proc = nil, &block )
|
192
|
+
return if proc.nil? && !block_given?
|
193
|
+
|
194
|
+
unless @signal_traps.has_key?( signal )
|
195
|
+
set_trap( signal )
|
196
|
+
end
|
197
|
+
|
198
|
+
@signal_traps[signal].unshift( proc || block )
|
199
|
+
end
|
200
|
+
|
201
|
+
protected
|
202
|
+
|
203
|
+
def run_traps( signal )
|
204
|
+
DaemonKit.logger.info "Running signal traps for #{signal}"
|
205
|
+
self.signal_traps[ signal ].each { |trap| trap.call }
|
206
|
+
end
|
207
|
+
|
208
|
+
private
|
209
|
+
|
210
|
+
def set_trap( signal )
|
211
|
+
DaemonKit.logger.info "Setting up trap for #{signal}"
|
212
|
+
@signal_traps[ signal ] = []
|
213
|
+
Signal.trap( signal, Proc.new { self.run_traps( signal ) } )
|
214
|
+
end
|
215
|
+
|
216
|
+
def set_root_path!
|
217
|
+
raise "DAEMON_ROOT is not set" unless defined?(::DAEMON_ROOT)
|
218
|
+
raise "DAEMON_ROOT is not a directory" unless defined?(::DAEMON_ROOT)
|
219
|
+
|
220
|
+
@root_path =
|
221
|
+
# Pathname is incompatible with Windows, but Windows doesn't have
|
222
|
+
# real symlinks so File.expand_path is safe.
|
223
|
+
if RUBY_PLATFORM =~ /(:?mswin|mingw)/
|
224
|
+
File.expand_path(::DAEMON_ROOT)
|
225
|
+
|
226
|
+
# Otherwise use Pathname#realpath which respects symlinks.
|
227
|
+
else
|
228
|
+
Pathname.new(::DAEMON_ROOT).realpath.to_s
|
229
|
+
end
|
230
|
+
|
231
|
+
Object.const_set(:RELATIVE_DAEMON_ROOT, ::DAEMON_ROOT.dup) unless defined?(::RELATIVE_DAEMON_ROOT)
|
232
|
+
::DAEMON_ROOT.replace @root_path
|
233
|
+
end
|
234
|
+
|
235
|
+
def default_load_paths
|
236
|
+
[ 'lib' ]
|
237
|
+
end
|
238
|
+
|
239
|
+
def default_log_path
|
240
|
+
File.join(root_path, 'log', "#{environment}.log")
|
241
|
+
end
|
242
|
+
|
243
|
+
def default_log_level
|
244
|
+
environment == 'production' ? Logger::INFO : Logger::DEBUG
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
|
249
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module DaemonKit
|
4
|
+
# Thin wrapper around xmpp4r-simple, specifically designed to ease
|
5
|
+
# configuration of a jabber daemon and provide some added simplicity.
|
6
|
+
class Jabber
|
7
|
+
|
8
|
+
# Jabber connection
|
9
|
+
attr_reader :connection
|
10
|
+
|
11
|
+
@@instance = nil
|
12
|
+
@@message_handler = nil
|
13
|
+
@@presence_handler = nil
|
14
|
+
@@subscription_handler = nil
|
15
|
+
|
16
|
+
class << self
|
17
|
+
|
18
|
+
# Deliver a message to the specified jid.
|
19
|
+
def deliver( jid, message )
|
20
|
+
instance.connection.deliver( jid, message )
|
21
|
+
end
|
22
|
+
|
23
|
+
# Use this instead of initializing, keeps it singleton
|
24
|
+
def instance
|
25
|
+
@instance ||= (
|
26
|
+
config = YAML.load_file( "#{DAEMON_ROOT}/config/jabber.yml" )[DAEMON_ENV]
|
27
|
+
raise ArgumentError, "Missing Jabber configuration for #{DAEMON_ENV} environment" if config.nil?
|
28
|
+
new( config )
|
29
|
+
)
|
30
|
+
@instance.startup!
|
31
|
+
end
|
32
|
+
private :new
|
33
|
+
|
34
|
+
def run
|
35
|
+
DaemonKit.logger.info "Starting jabber loop"
|
36
|
+
|
37
|
+
loop do
|
38
|
+
process_messages
|
39
|
+
process_updates
|
40
|
+
process_subscriptions
|
41
|
+
|
42
|
+
begin
|
43
|
+
sleep 1
|
44
|
+
rescue Interrupt
|
45
|
+
DaemonKit.logger.warn "Jabber loop interrupted"
|
46
|
+
break
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def process_messages
|
52
|
+
@message_handler ||= Proc.new { |m| DaemonKit.logger.info "Received message from #{m.from}: #{m.body}" }
|
53
|
+
|
54
|
+
instance.valid_messages { |m| @message_handler.call(m) }
|
55
|
+
end
|
56
|
+
|
57
|
+
def process_updates
|
58
|
+
@presence_handler ||= Proc.new { |friend, old_presence, new_presence|
|
59
|
+
DaemonKit.logger.debug "Received presence update: #{friend} went from #{old_presence} to #{new_presence}"
|
60
|
+
}
|
61
|
+
|
62
|
+
instance.connection.presence_updates { |friend, old_presence, new_presence|
|
63
|
+
@presence_handler.call(friend, old_presence, new_presence)
|
64
|
+
}
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
def process_subscriptions
|
69
|
+
@subscription_handler ||= Proc.new { |friend,presence| DaemonKit.logger.debug "Received presence update from #{friend}: #{presence}" }
|
70
|
+
|
71
|
+
instance.connection.subscription_requests { |friend,presence| @subscription_handler.call(friend,presence) }
|
72
|
+
end
|
73
|
+
|
74
|
+
def received_messages(&block)
|
75
|
+
@message_handler = block
|
76
|
+
end
|
77
|
+
|
78
|
+
def presence_updates(&block)
|
79
|
+
@presence_handler = block
|
80
|
+
end
|
81
|
+
|
82
|
+
def subscription_requests(&block)
|
83
|
+
@subscription_handler = block
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
def initialize( options = {} )
|
89
|
+
@jabber_id = options.delete("jabber_id")
|
90
|
+
@password = options.delete("password")
|
91
|
+
@resource = options.delete("resource") || 'daemon_kit'
|
92
|
+
@masters = options.delete("masters") || []
|
93
|
+
@supporters = options.delete("supporters") || []
|
94
|
+
|
95
|
+
raise ArgumentError if [ @jabber_id, @password ].any? { |a| a.nil? }
|
96
|
+
end
|
97
|
+
|
98
|
+
def startup!
|
99
|
+
return self if @booted
|
100
|
+
|
101
|
+
connect!
|
102
|
+
setup_roster!
|
103
|
+
|
104
|
+
DaemonKit.trap( 'INT', Proc.new { self.shutdown! } )
|
105
|
+
DaemonKit.trap( 'TERM', Proc.new { self.shutdown! } )
|
106
|
+
|
107
|
+
@booted = true
|
108
|
+
|
109
|
+
self
|
110
|
+
end
|
111
|
+
|
112
|
+
def shutdown!
|
113
|
+
DaemonKit.logger.warn "Disconnecting jabber connection"
|
114
|
+
self.connection.disconnect
|
115
|
+
end
|
116
|
+
|
117
|
+
def contacts
|
118
|
+
@masters + @supporters
|
119
|
+
end
|
120
|
+
|
121
|
+
def valid_messages(&block)
|
122
|
+
self.connection.received_messages.each do |message|
|
123
|
+
next unless valid_master?( message.from )
|
124
|
+
|
125
|
+
busy do
|
126
|
+
block.call message
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def valid_master?( jid )
|
132
|
+
@masters.include?( jid.strip.to_s )
|
133
|
+
end
|
134
|
+
|
135
|
+
def busy(&block)
|
136
|
+
self.connection.status(:dnd, "Working...")
|
137
|
+
yield
|
138
|
+
self.connection.status(:chat, self.status_line )
|
139
|
+
end
|
140
|
+
|
141
|
+
def status_line
|
142
|
+
"#{DaemonKit.configuration.daemon_name} ready for instructions"
|
143
|
+
end
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
def connect!
|
148
|
+
jid = @jabber_id + '/' + @resource
|
149
|
+
|
150
|
+
@connection = ::Jabber::Simple.new( jid, @password, nil, self.status_line )
|
151
|
+
end
|
152
|
+
|
153
|
+
def setup_roster!
|
154
|
+
# cleanup the roster
|
155
|
+
self.connection.roster.items.each_pair do |jid, roster_item|
|
156
|
+
jid = jid.strip.to_s
|
157
|
+
unless self.contacts.include?( jid )
|
158
|
+
self.connection.remove( jid )
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# add missing contacts
|
163
|
+
self.contacts.each do |jid|
|
164
|
+
unless self.connection.subscribed_to?( jid )
|
165
|
+
self.connection.add( jid )
|
166
|
+
#self.connection.accept_subscription( jid )
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# Shamelessly taken from http://blog.rapleaf.com/dev/?p=19
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'daemons'
|
5
|
+
require 'timeout'
|
6
|
+
|
7
|
+
module Daemons
|
8
|
+
|
9
|
+
class ApplicationGroup
|
10
|
+
|
11
|
+
# We want to redefine find_applications to not rely on
|
12
|
+
# pidfiles (e.g. find application if pidfile is gone)
|
13
|
+
# We recreate the pid files if they're not there.
|
14
|
+
def find_applications(dir)
|
15
|
+
# Find pid_files, like original implementation
|
16
|
+
pid_files = PidFile.find_files(dir, app_name)
|
17
|
+
@monitor = Monitor.find(dir, app_name + '_monitor')
|
18
|
+
pid_files.reject! {|f| f =~ /_monitor.pid$/}
|
19
|
+
|
20
|
+
# Find the missing pids based on the UNIX pids
|
21
|
+
pidfile_pids = pid_files.map {|pf| PidFile.existing(pf).pid}
|
22
|
+
missing_pids = unix_pids - pidfile_pids
|
23
|
+
|
24
|
+
# Create pidfiles that are gone
|
25
|
+
if missing_pids.size > 0
|
26
|
+
puts "[daemons_ext]: #{missing_pids.size} missing pidfiles: " +
|
27
|
+
"#{missing_pids.inspect}... creating pid file(s)."
|
28
|
+
missing_pids.each do |pid|
|
29
|
+
pidfile = PidFile.new(dir, app_name, multiple)
|
30
|
+
pidfile.pid = pid # Doesn't seem to matter if it's a string or Fixnum
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Now get all the pid file again
|
35
|
+
pid_files = PidFile.find_files(dir, app_name)
|
36
|
+
|
37
|
+
return pid_files.map {|f|
|
38
|
+
app = Application.new(self, {}, PidFile.existing(f))
|
39
|
+
setup_app(app)
|
40
|
+
app
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
# Specify :force_kill_wait => (seconds to wait) and this method will
|
45
|
+
# block until the process is dead. It first sends a TERM signal, then
|
46
|
+
# a KILL signal (-9) if the process hasn't died after the wait time.
|
47
|
+
def stop_all(force = false)
|
48
|
+
@monitor.stop if @monitor
|
49
|
+
|
50
|
+
wait = options[:force_kill_wait].to_i
|
51
|
+
if wait > 0
|
52
|
+
puts "[daemons_ext]: Killing #{app_name} with force after #{wait} secs."
|
53
|
+
|
54
|
+
# Send term first, don't delete PID files.
|
55
|
+
@applications.each {|a| a.send_sig('TERM')}
|
56
|
+
|
57
|
+
begin
|
58
|
+
started_at = Time.now
|
59
|
+
Timeout::timeout(wait) do
|
60
|
+
num_pids = unix_pids.size
|
61
|
+
while num_pids > 0
|
62
|
+
time_left = wait - (Time.now - started_at)
|
63
|
+
puts "[daemons_ext]: Waiting #{time_left.round} secs on " +
|
64
|
+
"#{num_pids} #{app_name}(s)..."
|
65
|
+
sleep 1
|
66
|
+
num_pids = unix_pids.size
|
67
|
+
end
|
68
|
+
end
|
69
|
+
rescue Timeout::Error
|
70
|
+
@applications.each {|a| a.send_sig('KILL')}
|
71
|
+
ensure
|
72
|
+
# Delete Pidfiles
|
73
|
+
@applications.each {|a| a.zap!}
|
74
|
+
end
|
75
|
+
|
76
|
+
puts "[daemons_ext]: All #{app_name}(s) dead."
|
77
|
+
else
|
78
|
+
@applications.each {|a|
|
79
|
+
if force
|
80
|
+
begin; a.stop; rescue ::Exception; end
|
81
|
+
else
|
82
|
+
a.stop
|
83
|
+
end
|
84
|
+
}
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
# Find UNIX pids based on app_name. CAUTION: This has only been tested on
|
91
|
+
# Mac OS X and CentOS.
|
92
|
+
def unix_pids
|
93
|
+
pids = []
|
94
|
+
x = `ps auxw | grep -v grep | awk '{print $2, $11}' | grep #{app_name}`
|
95
|
+
if x && x.chomp!
|
96
|
+
processes = x.split(/\n/).compact
|
97
|
+
processes = processes.delete_if do |p|
|
98
|
+
pid, name = p.split(/\s/)
|
99
|
+
# We want to make sure that the first part of the process name matches
|
100
|
+
# so that app_name matches app_name_22
|
101
|
+
app_name != name[0..(app_name.length - 1)]
|
102
|
+
end
|
103
|
+
pids = processes.map {|p| p.split(/\s/)[0].to_i}
|
104
|
+
end
|
105
|
+
|
106
|
+
pids
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
class Application
|
112
|
+
|
113
|
+
# Send signal to the process, rescue if process deson't exist
|
114
|
+
def send_sig(sig)
|
115
|
+
Process.kill(sig, @pid.pid) rescue Errno::ESRCH
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
namespace :daemon_kit do
|
2
|
+
namespace :freeze do
|
3
|
+
desc "Lock this application to the current gem (by unpacking it into vendor/daemon_kit)"
|
4
|
+
task :gems do
|
5
|
+
deps = %w()
|
6
|
+
require 'rubygems'
|
7
|
+
require 'rubygems/gem_runner'
|
8
|
+
|
9
|
+
kit = (version = ENV['VERSION']) ?
|
10
|
+
Gem.cache.find_name('daemon-kit', "= #{version}").first :
|
11
|
+
Gem.cache.find_name('daemon-kit').sort_by { |g| g.version }.last
|
12
|
+
|
13
|
+
version ||= kit.version
|
14
|
+
|
15
|
+
unless kit
|
16
|
+
puts "No daemon_kit gem #{version} is installed. Do 'gem list daemon_kit' to see what you have available."
|
17
|
+
exit
|
18
|
+
end
|
19
|
+
|
20
|
+
puts "Freezing the gem for DaemonKit #{kit.version}"
|
21
|
+
rm_rf "vendor/daemon_kit"
|
22
|
+
mkdir_p "vendor/daemon_kit"
|
23
|
+
|
24
|
+
begin
|
25
|
+
chdir("vendor/daemon_kit") do
|
26
|
+
kit.dependencies.select { |g| deps.include? g.name }.each do |g|
|
27
|
+
Gem::GemRunner.new.run(["unpack", g.name, "--version", g.version_requirements.to_s])
|
28
|
+
mv(Dir.glob("#{g.name}*").first, g.name)
|
29
|
+
end
|
30
|
+
|
31
|
+
Gem::GemRunner.new.run(["unpack", "daemon-kit", "--version", "=#{version}"])
|
32
|
+
FileUtils.mv(Dir.glob("daemon-kit*").first, "daemon-kit")
|
33
|
+
end
|
34
|
+
rescue Exception
|
35
|
+
rm_rf "vendor/daemon_kit"
|
36
|
+
raise
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
desc 'Lock to latest edge daemon_kit'
|
41
|
+
task :edge do
|
42
|
+
require 'open-uri'
|
43
|
+
#version = ENV["RELEASE"] || "edge"
|
44
|
+
commits = "http://github.com/api/v1/yaml/kennethkalmer/daemon-kit/commits/master"
|
45
|
+
url = "http://github.com/kennethkalmer/daemon-kit/zipball/master"
|
46
|
+
|
47
|
+
rm_rf "vendor/daemon_kit"
|
48
|
+
mkdir_p "vendor/daemon_kit"
|
49
|
+
|
50
|
+
chdir 'vendor/daemon_kit' do
|
51
|
+
latest_revision = YAML.load(open(commits))["commits"].first["id"]
|
52
|
+
|
53
|
+
puts "Downloading DaemonKit from #{url}"
|
54
|
+
File.open('daemon-kit.zip', 'wb') do |dst|
|
55
|
+
open url do |src|
|
56
|
+
while chunk = src.read(4096)
|
57
|
+
dst << chunk
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
puts 'Unpacking DaemonKit'
|
63
|
+
rm_rf 'daemon-kit'
|
64
|
+
`unzip daemon-kit.zip`
|
65
|
+
FileUtils.mv(Dir.glob("kennethkalmer-daemon-kit*").first, "daemon-kit")
|
66
|
+
%w(daemon-kit.zip).each do |goner|
|
67
|
+
rm_f goner
|
68
|
+
end
|
69
|
+
|
70
|
+
touch "REVISION_#{latest_revision}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
data/lib/daemon_kit.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
|
6
|
+
require 'daemon_kit/initializer'
|
7
|
+
require 'daemon_kit/application'
|
8
|
+
|
9
|
+
module DaemonKit
|
10
|
+
VERSION = '0.1.0'
|
11
|
+
end
|