feedupdater 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +2 -0
- data/README +34 -0
- data/bin/feed_updater +309 -0
- data/config/feed_updater.pid +1 -0
- data/config/feed_updater.yml +12 -0
- data/example/custom_updater.rb +14 -0
- data/lib/feed_updater/vendor/daemons/application.rb +298 -0
- data/lib/feed_updater/vendor/daemons/application_group.rb +150 -0
- data/lib/feed_updater/vendor/daemons/cmdline.rb +106 -0
- data/lib/feed_updater/vendor/daemons/controller.rb +134 -0
- data/lib/feed_updater/vendor/daemons/daemonize.rb +265 -0
- data/lib/feed_updater/vendor/daemons/exceptions.rb +28 -0
- data/lib/feed_updater/vendor/daemons/monitor.rb +126 -0
- data/lib/feed_updater/vendor/daemons/pid.rb +61 -0
- data/lib/feed_updater/vendor/daemons/pidfile.rb +99 -0
- data/lib/feed_updater/vendor/daemons/pidmem.rb +10 -0
- data/lib/feed_updater/vendor/daemons.rb +274 -0
- data/lib/feed_updater/version.rb +9 -0
- data/lib/feed_updater.rb +401 -0
- data/rakefile +121 -0
- metadata +80 -0
data/CHANGELOG
ADDED
data/README
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
The FeedUpdater is a simple daemon for handling the automatic updating of
|
2
|
+
FeedTools feed objects.
|
3
|
+
|
4
|
+
USAGE: feed_updater <subcommand>
|
5
|
+
|
6
|
+
Subcommands:
|
7
|
+
start
|
8
|
+
stop
|
9
|
+
restart
|
10
|
+
install
|
11
|
+
|
12
|
+
The FeedUpdater daemon is very easy to use, especially if you're using it
|
13
|
+
with a Rails application. If you're going to be using it with a Rails app,
|
14
|
+
simply install the feedupdater gem, navigate to your Rails app, and run
|
15
|
+
the 'feed_updater install' command. This will install FeedUpdater. Then
|
16
|
+
edit the feed_updater.yml config file as necessary. You will probably need
|
17
|
+
to define a custom updater class, which should probably go in your lib
|
18
|
+
directory. It's recommended that you use the example/custom_updater.rb file
|
19
|
+
as a starting point. You should using the updater events to copy any
|
20
|
+
required fields to feed subscription tables or item tables as necessary so
|
21
|
+
that a full feed parse isn't required every time the feed is displayed.
|
22
|
+
|
23
|
+
The config file also has a start delay option. This should almost always be
|
24
|
+
set to true. You should only turn it off when you are debugging a custom
|
25
|
+
updater class, and it should probably never be turned off in production.
|
26
|
+
Keeping it on prevents FeedUpdater from hitting the CPU too hard immediately
|
27
|
+
after a server reboot, and also prevents hundreds of update processes from
|
28
|
+
kicking off simultaneously on a shared server.
|
29
|
+
|
30
|
+
If you chose to use FeedUpdater outside a Rails application, you should try
|
31
|
+
to use a similar directory structure to what would be found in a Rails
|
32
|
+
setup. FeedUpdater will usually look in the current directory if it cannot
|
33
|
+
find a file its looking for though, so if FeedUpdater can't find a file it
|
34
|
+
needs, changing directories will probably fix the problem.
|
data/bin/feed_updater
ADDED
@@ -0,0 +1,309 @@
|
|
1
|
+
#!/usr/local/bin/ruby
|
2
|
+
|
3
|
+
#--
|
4
|
+
# Copyright (c) 2005 Robert Aman
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
7
|
+
# a copy of this software and associated documentation files (the
|
8
|
+
# "Software"), to deal in the Software without restriction, including
|
9
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
# the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be
|
15
|
+
# included in all copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
21
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
22
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
23
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
24
|
+
#++
|
25
|
+
|
26
|
+
if RUBY_PLATFORM =~ /mswin/
|
27
|
+
puts "FeedUpdater is not supported on Windows due to a lack of fork()."
|
28
|
+
exit
|
29
|
+
end
|
30
|
+
|
31
|
+
# :stopdoc:
|
32
|
+
# ROFLCOPTERS?
|
33
|
+
# This mainly exists because of the approximately 1 billion different
|
34
|
+
# ways to deploy this script.
|
35
|
+
class FileStalker
|
36
|
+
def self.hunt(paths)
|
37
|
+
for path in paths
|
38
|
+
if File.exists?(File.expand_path(path))
|
39
|
+
return File.expand_path(path)
|
40
|
+
elsif File.exists?(File.expand_path(
|
41
|
+
File.dirname(__FILE__) + path))
|
42
|
+
return File.expand_path(File.dirname(__FILE__) + path)
|
43
|
+
elsif File.exists?(File.expand_path(
|
44
|
+
File.dirname(__FILE__) + "/" + path))
|
45
|
+
return File.expand_path(File.dirname(__FILE__) + "/" + path)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
return nil
|
49
|
+
end
|
50
|
+
end
|
51
|
+
# :startdoc:
|
52
|
+
|
53
|
+
$:.unshift(FileStalker.hunt([
|
54
|
+
"/../vendor/feedupdater/lib",
|
55
|
+
"vendor/feedupdater/lib",
|
56
|
+
"../lib"]))
|
57
|
+
$:.uniq!
|
58
|
+
|
59
|
+
require 'yaml'
|
60
|
+
require 'feed_updater'
|
61
|
+
|
62
|
+
if ARGV[0].nil?
|
63
|
+
# Exit quickly if no command line arguments given.
|
64
|
+
|
65
|
+
feed_updater_version = FeedTools::FEED_UPDATER_VERSION::STRING
|
66
|
+
feed_tools_version = "<error: not loaded>"
|
67
|
+
if defined?(FeedTools::FEED_TOOLS_VERSION::STRING)
|
68
|
+
feed_tools_version = FeedTools::FEED_TOOLS_VERSION::STRING
|
69
|
+
elsif defined?(FEED_TOOLS_VERSION)
|
70
|
+
feed_tools_version = FEED_TOOLS_VERSION
|
71
|
+
end
|
72
|
+
puts ""
|
73
|
+
puts "FeedUpdater #{feed_updater_version} / FeedTools #{feed_tools_version}"
|
74
|
+
puts "USAGE: feed_updater <subcommand>"
|
75
|
+
puts ""
|
76
|
+
puts "Subcommands:"
|
77
|
+
puts " start"
|
78
|
+
puts " This will start the daemon if it hasn't already been started."
|
79
|
+
puts ""
|
80
|
+
puts " stop"
|
81
|
+
puts " This will stop the daemon if it's already running."
|
82
|
+
puts ""
|
83
|
+
puts " restart"
|
84
|
+
puts " This will restart the daemon."
|
85
|
+
puts ""
|
86
|
+
puts " install"
|
87
|
+
puts " If the current directory is a Rails installation, this will"
|
88
|
+
puts " install FeedUpdater into the vendor folder and add a new script"
|
89
|
+
puts " to the scripts/ directory for running FeedUpdater. Rerunning"
|
90
|
+
puts " the install subcommand will update the files against the latest"
|
91
|
+
puts " version of the FeedUpdater gem file you have installed. Rails"
|
92
|
+
puts " is NOT required to use FeedUpdater."
|
93
|
+
puts ""
|
94
|
+
exit
|
95
|
+
elsif ARGV[0] == "install"
|
96
|
+
rails_root = nil
|
97
|
+
install_vendor_directory = nil
|
98
|
+
install_config_directory = nil
|
99
|
+
install_script_directory = nil
|
100
|
+
|
101
|
+
current_directory = File.expand_path(".")
|
102
|
+
loop do
|
103
|
+
entries = Dir.entries(current_directory)
|
104
|
+
if entries.include?("vendor") &&
|
105
|
+
entries.include?("config") &&
|
106
|
+
entries.include?("script")
|
107
|
+
rails_root = current_directory
|
108
|
+
break
|
109
|
+
else
|
110
|
+
break if current_directory == "/"
|
111
|
+
current_directory = File.expand_path(current_directory + "/..")
|
112
|
+
end
|
113
|
+
end
|
114
|
+
if rails_root.nil?
|
115
|
+
puts "You must run the install command when the current directory is"
|
116
|
+
puts "a Rails application."
|
117
|
+
else
|
118
|
+
install_vendor_directory = File.expand_path(rails_root + "/vendor")
|
119
|
+
install_config_directory = File.expand_path(rails_root + "/config")
|
120
|
+
install_script_directory = File.expand_path(rails_root + "/script")
|
121
|
+
|
122
|
+
# Install FeedUpdater gem to application vendor directory
|
123
|
+
system("rm -rf #{install_vendor_directory}/feedupdater")
|
124
|
+
unpack_string = `gem unpack feedupdater`
|
125
|
+
feed_updater_version =
|
126
|
+
unpack_string.scan(/\'feedupdater\-(.*)\'/).flatten[0]
|
127
|
+
if feed_updater_version.nil? || feed_updater_version == ""
|
128
|
+
puts "You need to have the feedupdater gem installed for the install"
|
129
|
+
puts "subcommand to run correctly."
|
130
|
+
exit
|
131
|
+
end
|
132
|
+
puts "Installing FeedUpdater #{feed_updater_version} " +
|
133
|
+
"to vendor directory..."
|
134
|
+
|
135
|
+
system("cp -rp feedupdater-#{feed_updater_version} " +
|
136
|
+
"#{install_vendor_directory}")
|
137
|
+
system("rm -rf feedupdater-#{feed_updater_version}")
|
138
|
+
system("mv #{install_vendor_directory}/" +
|
139
|
+
"feedupdater-#{feed_updater_version} " +
|
140
|
+
"#{install_vendor_directory}/feedupdater")
|
141
|
+
|
142
|
+
# Install FeedTools gem to application vendor directory
|
143
|
+
system("rm -rf #{install_vendor_directory}/feedtools")
|
144
|
+
unpack_string = `gem unpack feedtools`
|
145
|
+
feed_tools_version =
|
146
|
+
unpack_string.scan(/\'feedtools\-(.*)\'/).flatten[0]
|
147
|
+
if feed_tools_version.nil? || feed_tools_version == ""
|
148
|
+
puts "You need to have the feedtools gem installed for the install"
|
149
|
+
puts "subcommand to run correctly."
|
150
|
+
exit
|
151
|
+
end
|
152
|
+
puts "Installing FeedTools #{feed_tools_version} to vendor directory..."
|
153
|
+
|
154
|
+
system("cp -rp feedtools-#{feed_tools_version} " +
|
155
|
+
"#{install_vendor_directory}")
|
156
|
+
system("rm -rf feedtools-#{feed_tools_version}")
|
157
|
+
system("mv #{install_vendor_directory}/" +
|
158
|
+
"feedtools-#{feed_tools_version} " +
|
159
|
+
"#{install_vendor_directory}/feedtools")
|
160
|
+
|
161
|
+
# Copying the default config file to the config directory
|
162
|
+
if !File.exists?(File.expand_path(install_config_directory +
|
163
|
+
"/feed_updater.yml"))
|
164
|
+
puts "Installing default config file..."
|
165
|
+
config_file =
|
166
|
+
File.expand_path(File.dirname(__FILE__) + '/../config/feed_updater.yml')
|
167
|
+
system("cp -p #{config_file} #{install_config_directory}")
|
168
|
+
else
|
169
|
+
puts "Config file already exists, skipping..."
|
170
|
+
end
|
171
|
+
|
172
|
+
# Copying this executable to the script directory
|
173
|
+
puts "Copying executable to script directory..."
|
174
|
+
system("rm -rf #{install_script_directory}/feed_updater")
|
175
|
+
system("cp -p #{__FILE__} #{install_script_directory}")
|
176
|
+
system("chmod +x #{install_script_directory}/feed_updater")
|
177
|
+
|
178
|
+
puts "Use script/feed_updater to run FeedUpdater."
|
179
|
+
end
|
180
|
+
exit
|
181
|
+
end
|
182
|
+
|
183
|
+
config_file = FileStalker.hunt([
|
184
|
+
"./feed_updater.yml",
|
185
|
+
"../config/feed_updater.yml"
|
186
|
+
])
|
187
|
+
if config_file.nil?
|
188
|
+
puts "Could not locate feed_updater.yml config file."
|
189
|
+
exit
|
190
|
+
end
|
191
|
+
|
192
|
+
config_hash = YAML.load(File.open(config_file, "r").read)
|
193
|
+
load_script_path = config_hash["load_script"]
|
194
|
+
if !load_script_path.nil?
|
195
|
+
result = FileStalker.hunt([
|
196
|
+
load_script_path,
|
197
|
+
File.dirname(config_file) + "/" + load_script_path,
|
198
|
+
File.dirname(config_file) + load_script_path,
|
199
|
+
File.dirname(__FILE__) + "/../" + load_script_path,
|
200
|
+
File.dirname(__FILE__) + "/.." + load_script_path
|
201
|
+
])
|
202
|
+
if result.nil?
|
203
|
+
puts "Could not locate #{load_script_path.inspect} file."
|
204
|
+
exit
|
205
|
+
else
|
206
|
+
load_script_path = result
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
pid_file_path = config_hash["pid_file_path"]
|
211
|
+
if !pid_file_path.nil?
|
212
|
+
result = FileStalker.hunt([
|
213
|
+
pid_file_path,
|
214
|
+
File.dirname(config_file) + "/" + pid_file_path,
|
215
|
+
File.dirname(config_file) + pid_file_path,
|
216
|
+
File.dirname(__FILE__) + "/../" + pid_file_path,
|
217
|
+
File.dirname(__FILE__) + "/.." + pid_file_path,
|
218
|
+
"../" + pid_file_path
|
219
|
+
])
|
220
|
+
pid_file_path = result if !result.nil?
|
221
|
+
else
|
222
|
+
result = FileStalker.hunt([
|
223
|
+
File.dirname(__FILE__) + "/../config",
|
224
|
+
"./config",
|
225
|
+
File.dirname(__FILE__) + "/config"
|
226
|
+
])
|
227
|
+
pid_file_path = result if !result.nil?
|
228
|
+
end
|
229
|
+
|
230
|
+
log_file_path = config_hash["log_file_path"]
|
231
|
+
if !log_file_path.nil?
|
232
|
+
result = FileStalker.hunt([
|
233
|
+
log_file_path,
|
234
|
+
File.dirname(config_file) + "/" + log_file_path,
|
235
|
+
File.dirname(config_file) + log_file_path,
|
236
|
+
File.dirname(__FILE__) + "/../" + log_file_path,
|
237
|
+
File.dirname(__FILE__) + "/.." + log_file_path,
|
238
|
+
"../" + log_file_path
|
239
|
+
])
|
240
|
+
log_file_path = result if !result.nil?
|
241
|
+
else
|
242
|
+
result = FileStalker.hunt([
|
243
|
+
File.dirname(__FILE__) + "/../log",
|
244
|
+
"./log",
|
245
|
+
File.dirname(__FILE__) + "/log",
|
246
|
+
File.dirname(__FILE__) + "/../logs",
|
247
|
+
"./logs",
|
248
|
+
File.dirname(__FILE__) + "/logs"
|
249
|
+
])
|
250
|
+
log_file_path = result if !result.nil?
|
251
|
+
end
|
252
|
+
|
253
|
+
script_class = nil
|
254
|
+
existing_subclasses = []
|
255
|
+
ObjectSpace.each_object do |instance|
|
256
|
+
if instance.class.name == "Class" &&
|
257
|
+
instance.ancestors.include?(FeedTools::FeedUpdater)
|
258
|
+
existing_subclasses << instance
|
259
|
+
end
|
260
|
+
end
|
261
|
+
load(load_script_path)
|
262
|
+
loaded_subclasses = []
|
263
|
+
ObjectSpace.each_object do |instance|
|
264
|
+
if instance.class.name == "Class" &&
|
265
|
+
instance.ancestors.include?(FeedTools::FeedUpdater)
|
266
|
+
loaded_subclasses << instance
|
267
|
+
end
|
268
|
+
end
|
269
|
+
script_classes = (loaded_subclasses - existing_subclasses)
|
270
|
+
if script_classes.size == 0
|
271
|
+
puts "There do not appear to be any subclasses of FeedTools::FeedUpdater."
|
272
|
+
exit
|
273
|
+
elsif script_classes.size > 1
|
274
|
+
puts "There appear to be multiple subclasses of FeedTools::FeedUpdater."
|
275
|
+
exit
|
276
|
+
else
|
277
|
+
script_class = script_classes[0]
|
278
|
+
end
|
279
|
+
|
280
|
+
updater = nil
|
281
|
+
if load_script_path.nil?
|
282
|
+
updater = FeedTools::FeedUpdater.new
|
283
|
+
FeedTools::FeedUpdater.on_update do |feed, seconds|
|
284
|
+
updater.logger.info("Loaded '#{feed.href}'.")
|
285
|
+
updater.logger.info("=> Updated (#{feed.title}) in #{seconds} seconds.")
|
286
|
+
end
|
287
|
+
FeedTools::FeedUpdater.on_error do |href, error|
|
288
|
+
updater.logger.info("Error updating '#{href}':")
|
289
|
+
updater.logger.info(error)
|
290
|
+
end
|
291
|
+
else
|
292
|
+
updater = script_class.new
|
293
|
+
end
|
294
|
+
|
295
|
+
for key in config_hash.keys
|
296
|
+
key_sym = key.to_s.to_sym
|
297
|
+
updater.updater_options[key_sym] = config_hash[key]
|
298
|
+
end
|
299
|
+
updater.pid_file_dir = pid_file_path
|
300
|
+
updater.log_file_dir = log_file_path
|
301
|
+
|
302
|
+
case ARGV[0]
|
303
|
+
when "start"
|
304
|
+
updater.start()
|
305
|
+
when "stop"
|
306
|
+
updater.stop()
|
307
|
+
when "restart"
|
308
|
+
updater.restart()
|
309
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
617
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# This ensures that you don't break shared servers.
|
2
|
+
start_delay: false
|
3
|
+
|
4
|
+
# This is the path to the custom handler for updated feeds.
|
5
|
+
# See the documentation for details.
|
6
|
+
load_script: example/custom_updater.rb
|
7
|
+
|
8
|
+
# This is the path to store the pid files.
|
9
|
+
pid_file_path: config
|
10
|
+
|
11
|
+
# This is the path to store the log files.
|
12
|
+
log_file_path: log
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class CustomUpdater < FeedTools::FeedUpdater
|
2
|
+
on_update do |feed, seconds|
|
3
|
+
logger.info("Loaded '#{feed.href}'.")
|
4
|
+
logger.info("=> Updated (#{feed.title}) in #{seconds} seconds.")
|
5
|
+
end
|
6
|
+
|
7
|
+
on_error do |href, error|
|
8
|
+
logger.info("Error updating '#{href}':")
|
9
|
+
logger.info(error)
|
10
|
+
end
|
11
|
+
|
12
|
+
on_complete do |updated_feed_hrefs|
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,298 @@
|
|
1
|
+
require 'daemons/pidfile'
|
2
|
+
require 'daemons/pidmem'
|
3
|
+
|
4
|
+
|
5
|
+
module Daemons
|
6
|
+
|
7
|
+
class Application
|
8
|
+
|
9
|
+
attr_accessor :app_argv
|
10
|
+
attr_accessor :controller_argv
|
11
|
+
|
12
|
+
# the Pid instance belonging to this application
|
13
|
+
attr_reader :pid
|
14
|
+
|
15
|
+
# the ApplicationGroup the application belongs to
|
16
|
+
attr_reader :group
|
17
|
+
|
18
|
+
# my private options
|
19
|
+
attr_reader :options
|
20
|
+
|
21
|
+
|
22
|
+
def initialize(group, add_options = {}, pid = nil)
|
23
|
+
@group = group
|
24
|
+
@options = group.options.dup
|
25
|
+
@options.update(add_options)
|
26
|
+
|
27
|
+
unless @pid = pid
|
28
|
+
if dir = pidfile_dir
|
29
|
+
@pid = PidFile.new(pidfile_dir(), @group.app_name, @group.multiple)
|
30
|
+
else
|
31
|
+
@pid = PidMem.new
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def script
|
37
|
+
@script || @group.script
|
38
|
+
end
|
39
|
+
|
40
|
+
def pidfile_dir
|
41
|
+
Pid.dir(@dir_mode || @group.dir_mode, @dir || @group.dir, @script || @group.script)
|
42
|
+
end
|
43
|
+
|
44
|
+
def logfile
|
45
|
+
(options[:log_output] && pidfile_dir()) ? File.join(pidfile_dir(), @group.app_name + '.output') : nil
|
46
|
+
end
|
47
|
+
|
48
|
+
def start_none
|
49
|
+
unless options[:ontop]
|
50
|
+
Daemonize.daemonize #(logfile)
|
51
|
+
else
|
52
|
+
Daemonize.simulate
|
53
|
+
end
|
54
|
+
|
55
|
+
@pid.pid = Process.pid
|
56
|
+
|
57
|
+
|
58
|
+
# We need this to remove the pid-file if the applications exits by itself.
|
59
|
+
# Note that <tt>at_text</tt> will only be run if the applications exits by calling
|
60
|
+
# <tt>exit</tt>, and not if it calls <tt>exit!</tt> (so please don't call <tt>exit!</tt>
|
61
|
+
# in your application!
|
62
|
+
#
|
63
|
+
at_exit {
|
64
|
+
@pid.cleanup rescue nil
|
65
|
+
|
66
|
+
# If the option <tt>:backtrace</tt> is used and the application did exit by itself
|
67
|
+
# create a exception log.
|
68
|
+
if options[:backtrace] and not options[:ontop] and not $daemons_sigterm
|
69
|
+
exception_log() rescue nil
|
70
|
+
end
|
71
|
+
|
72
|
+
}
|
73
|
+
|
74
|
+
# This part is needed to remove the pid-file if the application is killed by
|
75
|
+
# daemons or manually by the user.
|
76
|
+
# Note that the applications is not supposed to overwrite the signal handler for
|
77
|
+
# 'TERM'.
|
78
|
+
#
|
79
|
+
trap('TERM') {
|
80
|
+
@pid.cleanup rescue nil
|
81
|
+
$daemons_sigterm = true
|
82
|
+
|
83
|
+
exit
|
84
|
+
}
|
85
|
+
end
|
86
|
+
|
87
|
+
def start_exec
|
88
|
+
unless options[:ontop]
|
89
|
+
Daemonize.daemonize(logfile)
|
90
|
+
else
|
91
|
+
Daemonize.simulate(logfile)
|
92
|
+
end
|
93
|
+
|
94
|
+
@pid.pid = Process.pid
|
95
|
+
|
96
|
+
ENV['DAEMONS_ARGV'] = @controller_argv.join(' ')
|
97
|
+
# haven't tested yet if this is really passed to the exec'd process...
|
98
|
+
|
99
|
+
Kernel.exec(script(), *ARGV)
|
100
|
+
end
|
101
|
+
|
102
|
+
def start_load
|
103
|
+
unless options[:ontop]
|
104
|
+
Daemonize.daemonize(logfile)
|
105
|
+
else
|
106
|
+
Daemonize.simulate(logfile)
|
107
|
+
end
|
108
|
+
|
109
|
+
@pid.pid = Process.pid
|
110
|
+
|
111
|
+
|
112
|
+
# We need this to remove the pid-file if the applications exits by itself.
|
113
|
+
# Note that <tt>at_text</tt> will only be run if the applications exits by calling
|
114
|
+
# <tt>exit</tt>, and not if it calls <tt>exit!</tt> (so please don't call <tt>exit!</tt>
|
115
|
+
# in your application!
|
116
|
+
#
|
117
|
+
at_exit {
|
118
|
+
@pid.cleanup rescue nil
|
119
|
+
|
120
|
+
# If the option <tt>:backtrace</tt> is used and the application did exit by itself
|
121
|
+
# create a exception log.
|
122
|
+
if options[:backtrace] and not options[:ontop] and not $daemons_sigterm
|
123
|
+
exception_log() rescue nil
|
124
|
+
end
|
125
|
+
|
126
|
+
}
|
127
|
+
|
128
|
+
# This part is needed to remove the pid-file if the application is killed by
|
129
|
+
# daemons or manually by the user.
|
130
|
+
# Note that the applications is not supposed to overwrite the signal handler for
|
131
|
+
# 'TERM'.
|
132
|
+
#
|
133
|
+
trap('TERM') {
|
134
|
+
@pid.cleanup rescue nil
|
135
|
+
$daemons_sigterm = true
|
136
|
+
|
137
|
+
exit
|
138
|
+
}
|
139
|
+
|
140
|
+
# Know we really start the script...
|
141
|
+
$DAEMONS_ARGV = @controller_argv
|
142
|
+
ENV['DAEMONS_ARGV'] = @controller_argv.join(' ')
|
143
|
+
|
144
|
+
ARGV.clear
|
145
|
+
ARGV.concat @app_argv if @app_argv
|
146
|
+
|
147
|
+
# TODO: begin - rescue - end around this and exception logging
|
148
|
+
load script()
|
149
|
+
end
|
150
|
+
|
151
|
+
def start_proc
|
152
|
+
return unless options[:proc]
|
153
|
+
|
154
|
+
unless options[:ontop]
|
155
|
+
@pid.pid = Daemonize.call_as_daemon(options[:proc], logfile)
|
156
|
+
else
|
157
|
+
# Daemonize.simulate(logfile)
|
158
|
+
#
|
159
|
+
# @pid.pid = Process.pid
|
160
|
+
#
|
161
|
+
# Thread.new(&options[:proc])
|
162
|
+
unless @pid.pid = Process.fork
|
163
|
+
Daemonize.simulate(logfile)
|
164
|
+
options[:proc].call
|
165
|
+
exit
|
166
|
+
else
|
167
|
+
Process.detach(@pid.pid)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
def start
|
174
|
+
@group.create_monitor(@group.applications[0] || self)
|
175
|
+
|
176
|
+
case options[:mode]
|
177
|
+
when :none
|
178
|
+
start_none
|
179
|
+
when :exec
|
180
|
+
start_exec
|
181
|
+
when :load
|
182
|
+
start_load
|
183
|
+
when :proc
|
184
|
+
start_proc
|
185
|
+
else
|
186
|
+
start_load
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# def run
|
191
|
+
# if @group.controller.options[:exec]
|
192
|
+
# run_via_exec()
|
193
|
+
# else
|
194
|
+
# run_via_load()
|
195
|
+
# end
|
196
|
+
# end
|
197
|
+
#
|
198
|
+
# def run_via_exec
|
199
|
+
#
|
200
|
+
# end
|
201
|
+
#
|
202
|
+
# def run_via_load
|
203
|
+
#
|
204
|
+
# end
|
205
|
+
|
206
|
+
|
207
|
+
# This is a nice little function for debugging purposes:
|
208
|
+
# In case a multi-threaded ruby script exits due to an uncaught exception
|
209
|
+
# it may be difficult to find out where the exception came from because
|
210
|
+
# one cannot catch exceptions that are thrown in threads other than the main
|
211
|
+
# thread.
|
212
|
+
#
|
213
|
+
# This function searches for all exceptions in memory and outputs them to STDERR
|
214
|
+
# (if it is connected) and to a log file in the pid-file directory.
|
215
|
+
#
|
216
|
+
def exception_log
|
217
|
+
require 'logger'
|
218
|
+
|
219
|
+
l_file = Logger.new(File.join(pidfile_dir(), @group.app_name + '.log'))
|
220
|
+
|
221
|
+
|
222
|
+
# the code below only logs the last exception
|
223
|
+
# e = nil
|
224
|
+
#
|
225
|
+
# ObjectSpace.each_object {|o|
|
226
|
+
# if ::Exception === o
|
227
|
+
# e = o
|
228
|
+
# end
|
229
|
+
# }
|
230
|
+
#
|
231
|
+
# l_file.error e
|
232
|
+
# l_file.close
|
233
|
+
|
234
|
+
# this code logs every exception found in memory
|
235
|
+
ObjectSpace.each_object {|o|
|
236
|
+
if ::Exception === o
|
237
|
+
l_file.error o
|
238
|
+
end
|
239
|
+
}
|
240
|
+
|
241
|
+
l_file.close
|
242
|
+
end
|
243
|
+
|
244
|
+
|
245
|
+
def stop
|
246
|
+
if options[:force] and not running?
|
247
|
+
self.zap
|
248
|
+
return
|
249
|
+
end
|
250
|
+
|
251
|
+
# Catch errors when trying to kill a process that doesn't
|
252
|
+
# exist. This happens when the process quits and hasn't been
|
253
|
+
# restarted by the monitor yet. By catching the error, we allow the
|
254
|
+
# pid file clean-up to occur.
|
255
|
+
begin
|
256
|
+
Process.kill('TERM', @pid.pid)
|
257
|
+
rescue Errno::ESRCH => e
|
258
|
+
puts "#{e} #{@pid.pid}"
|
259
|
+
puts "deleting pid-file."
|
260
|
+
end
|
261
|
+
|
262
|
+
# We try to remove the pid-files by ourselves, in case the application
|
263
|
+
# didn't clean it up.
|
264
|
+
@pid.cleanup rescue nil
|
265
|
+
|
266
|
+
end
|
267
|
+
|
268
|
+
def zap
|
269
|
+
@pid.cleanup
|
270
|
+
end
|
271
|
+
|
272
|
+
def zap!
|
273
|
+
@pid.cleanup rescue nil
|
274
|
+
end
|
275
|
+
|
276
|
+
def show_status
|
277
|
+
running = self.running?
|
278
|
+
|
279
|
+
puts "#{self.group.app_name}: #{running ? '' : 'not '}running#{(running and @pid.exists?) ? ' [pid ' + @pid.pid.to_s + ']' : ''}#{(@pid.exists? and not running) ? ' (but pid-file exists: ' + @pid.pid.to_s + ')' : ''}"
|
280
|
+
end
|
281
|
+
|
282
|
+
# This function implements a (probably too simle) method to detect
|
283
|
+
# whether the program with the pid found in the pid-file is still running.
|
284
|
+
# It just searches for the pid in the output of <tt>ps ax</tt>, which
|
285
|
+
# is probably not a good idea in some cases.
|
286
|
+
# Alternatives would be to use a direct access method the unix process control
|
287
|
+
# system.
|
288
|
+
#
|
289
|
+
def running?
|
290
|
+
if @pid.exists?
|
291
|
+
return Pid.running?(@pid.pid)
|
292
|
+
end
|
293
|
+
|
294
|
+
return false
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
end
|