resque-sliders 0.0.4
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.
- data/Gemfile +9 -0
- data/MIT-LICENSE +20 -0
- data/README.md +88 -0
- data/Rakefile +35 -0
- data/bin/kewatcher +68 -0
- data/config/config_example.yml +11 -0
- data/lib/resque-sliders/commander.rb +73 -0
- data/lib/resque-sliders/helpers.rb +72 -0
- data/lib/resque-sliders/kewatcher.rb +345 -0
- data/lib/resque-sliders/server/public/css/jqueryui-1.8.16/blitzer/jquery-ui.custom.css +568 -0
- data/lib/resque-sliders/server/public/css/sliders.css +30 -0
- data/lib/resque-sliders/server/public/images/ui-bg_diagonals-thick_75_f3d8d8_40x40.png +0 -0
- data/lib/resque-sliders/server/public/images/ui-bg_dots-small_65_a6a6a6_2x2.png +0 -0
- data/lib/resque-sliders/server/public/images/ui-bg_flat_0_333333_40x100.png +0 -0
- data/lib/resque-sliders/server/public/images/ui-bg_flat_65_ffffff_40x100.png +0 -0
- data/lib/resque-sliders/server/public/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/lib/resque-sliders/server/public/images/ui-bg_glass_55_fbf8ee_1x400.png +0 -0
- data/lib/resque-sliders/server/public/images/ui-bg_highlight-hard_100_eeeeee_1x100.png +0 -0
- data/lib/resque-sliders/server/public/images/ui-bg_highlight-hard_100_f6f6f6_1x100.png +0 -0
- data/lib/resque-sliders/server/public/images/ui-bg_highlight-soft_15_cc0000_1x100.png +0 -0
- data/lib/resque-sliders/server/public/images/ui-icons_004276_256x240.png +0 -0
- data/lib/resque-sliders/server/public/images/ui-icons_cc0000_256x240.png +0 -0
- data/lib/resque-sliders/server/public/images/ui-icons_ffffff_256x240.png +0 -0
- data/lib/resque-sliders/server/public/js/jquery-ui-1.8.16.custom.min.js +791 -0
- data/lib/resque-sliders/server/public/js/sliders.js +64 -0
- data/lib/resque-sliders/server/views/index.erb +59 -0
- data/lib/resque-sliders/server.rb +71 -0
- data/lib/resque-sliders/version.rb +7 -0
- data/lib/resque-sliders.rb +9 -0
- data/resque-sliders.gemspec +29 -0
- data/test/redis-test.conf +115 -0
- data/test/resque-sliders_test.rb +12 -0
- data/test/test_helper.rb +74 -0
- metadata +117 -0
@@ -0,0 +1,345 @@
|
|
1
|
+
require 'resque'
|
2
|
+
require 'timeout'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
require 'resque-sliders/helpers'
|
6
|
+
|
7
|
+
module Resque
|
8
|
+
module Plugins
|
9
|
+
module ResqueSliders
|
10
|
+
# KEWatcher class provides a daemon to run on host that are running resque workers.
|
11
|
+
class KEWatcher
|
12
|
+
include Helpers
|
13
|
+
|
14
|
+
# Verbosity level (Integer)
|
15
|
+
attr_accessor :verbosity
|
16
|
+
|
17
|
+
# Initialize daemon with options from command-line.
|
18
|
+
def initialize(options={})
|
19
|
+
@verbosity = (options[:verbosity] || 0).to_i
|
20
|
+
@hostile_takeover = options[:force]
|
21
|
+
@rakefile = File.expand_path(options[:rakefile]) rescue nil
|
22
|
+
@rakefile = File.exists?(@rakefile) ? @rakefile : nil if @rakefile
|
23
|
+
@pidfile = File.expand_path(options[:pidfile]) rescue nil
|
24
|
+
@pidfile = @pidfile =~ /\.pid/ ? @pidfile : @pidfile + '.pid' if @pidfile
|
25
|
+
save_pid!
|
26
|
+
|
27
|
+
@max_children = (options[:max_children] || 5).to_i
|
28
|
+
@hostname = `hostname -s`.chomp.downcase
|
29
|
+
@pids = Hash.new # init pids array to track running children
|
30
|
+
@need_queues = Array.new # keep track of pids that are needed
|
31
|
+
@dead_queues = Array.new # keep track of pids that are dead
|
32
|
+
@zombie_pids = Array.new # keep track of zombie's we kill and dont watch()
|
33
|
+
|
34
|
+
Resque.redis = options[:config]
|
35
|
+
end
|
36
|
+
|
37
|
+
# run the daemon
|
38
|
+
def run!(interval=0.1)
|
39
|
+
interval = Float(interval)
|
40
|
+
if running?
|
41
|
+
(puts "Already running. Restart Not Forced exiting..."; exit) unless @hostile_takeover
|
42
|
+
restart_running!
|
43
|
+
end
|
44
|
+
$0 = "KEWatcher: Starting"
|
45
|
+
startup
|
46
|
+
|
47
|
+
count = 0
|
48
|
+
old = 0 # know when to tell redis we have new different current pids
|
49
|
+
loop do
|
50
|
+
break if shutdown?
|
51
|
+
count += 1
|
52
|
+
log! ["watching:", @pids.keys.join(', '), "(#{@pids.keys.length})"].delete_if { |x| x == (nil || '') }.join(' ') if count % (10 / interval) == 1
|
53
|
+
|
54
|
+
if not (paused? || shutdown?)
|
55
|
+
if count % (20 / interval) == 1
|
56
|
+
# do first and also about every 20 seconds so we can throttle calls to redis
|
57
|
+
queue_diff!
|
58
|
+
signal_hup if check_reload and not count == 1
|
59
|
+
procline @pids.keys.join(', ')
|
60
|
+
end
|
61
|
+
|
62
|
+
while @pids.keys.length < @max_children && (@need_queues.length > 0 || @dead_queues.length > 0)
|
63
|
+
queue = @dead_queues.shift || @need_queues.shift
|
64
|
+
@pids.store(fork { exec("rake#{' -f ' + @rakefile if @rakefile}#{' environment' if ENV['RAILS_ENV']} resque:work QUEUE=#{queue}") }, queue) # store offset if linux fork() ?
|
65
|
+
procline @pids.keys.join(', ')
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
register_setting('current_children', @pids.keys.length) if old != @pids.length
|
70
|
+
old = @pids.length
|
71
|
+
|
72
|
+
sleep(interval) # microsleep
|
73
|
+
kill_zombies! # need to cleanup ones we've killed
|
74
|
+
@pids.keys.each do |pid|
|
75
|
+
begin
|
76
|
+
# check to see if pid is running, by waiting for it, with a timeout
|
77
|
+
# Im sure Ruby 1.9 has some better helpers here
|
78
|
+
Timeout::timeout(interval / 100) { Process.wait(pid) }
|
79
|
+
rescue Timeout::Error
|
80
|
+
# Timeout expired, goto next pid
|
81
|
+
next
|
82
|
+
rescue Errno::ECHILD
|
83
|
+
# if no pid exists to wait for, remove it
|
84
|
+
log! (paused? || shutdown?) ? "#{pid} (#{@pids[pid]}) child died; no one cares..." : "#{pid} (#{@pids[pid]}) child died; spawning another..."
|
85
|
+
remove pid
|
86
|
+
break
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Returns PID if already running, false otherwise
|
93
|
+
def running?
|
94
|
+
pid = `ps x -o pid,command|grep [K]EWatcher|awk '{print $1}'`.to_i
|
95
|
+
pid == 0 ? false : pid
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def check_reload
|
101
|
+
(unregister_setting('reload'); return true) if reload?(@hostname)
|
102
|
+
false
|
103
|
+
end
|
104
|
+
|
105
|
+
def unregister_setting(setting)
|
106
|
+
redis_del_hash(host_config_key, "#{@hostname}:#{setting}")
|
107
|
+
end
|
108
|
+
|
109
|
+
# Forces (via signal QUIT) any KEWatcher process running, located by ps and grep
|
110
|
+
def restart_running!
|
111
|
+
count = 0
|
112
|
+
while pid = running?
|
113
|
+
(puts "#{pid} wont die; giving up"; exit 2) if count > 6
|
114
|
+
count += 1
|
115
|
+
if count % 5 == 1
|
116
|
+
puts "Killing running KEWatcher: #{pid}"
|
117
|
+
Process.kill('QUIT', pid)
|
118
|
+
end
|
119
|
+
s = 3 * count
|
120
|
+
puts "Waiting #{s}s for it to die..."
|
121
|
+
sleep(s)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def startup
|
126
|
+
log! "Found RAILS_ENV=#{ENV['RAILS_ENV']}" if ENV['RAILS_ENV']
|
127
|
+
enable_gc_optimizations
|
128
|
+
register_signal_handlers
|
129
|
+
register_setting('max_children', @max_children)
|
130
|
+
log! "Registered Max Children with Redis"
|
131
|
+
$stdout.sync = true
|
132
|
+
end
|
133
|
+
|
134
|
+
def enable_gc_optimizations
|
135
|
+
if GC.respond_to?(:copy_on_write_friendly=)
|
136
|
+
GC.copy_on_write_friendly = true
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def register_signal_handlers
|
141
|
+
trap('TERM') { shutdown! }
|
142
|
+
trap('INT') { shutdown! }
|
143
|
+
|
144
|
+
begin
|
145
|
+
trap('QUIT') { shutdown! }
|
146
|
+
trap('HUP') { signal_hup }
|
147
|
+
trap('USR1') { signal_usr1 }
|
148
|
+
trap('USR2') { signal_usr2 }
|
149
|
+
trap('CONT') { signal_cont }
|
150
|
+
rescue ArgumentError
|
151
|
+
warn "Signals QUIT, USR1, USR2, and/or CONT not supported."
|
152
|
+
end
|
153
|
+
|
154
|
+
log! "Registered signals"
|
155
|
+
end
|
156
|
+
|
157
|
+
def procline(string)
|
158
|
+
$0 = "KEWatcher (#{@pids.keys.length}): #{string}"
|
159
|
+
log! $0
|
160
|
+
end
|
161
|
+
|
162
|
+
def queue_diff!
|
163
|
+
# Forces queue diff
|
164
|
+
# Overrides what needs to start from Redis
|
165
|
+
diff = queue_diff
|
166
|
+
to_start = diff.first
|
167
|
+
to_kill = diff.last
|
168
|
+
to_kill.each { |pid| remove! pid }
|
169
|
+
@need_queues = to_start # authoritative answer from redis of what needs to be running
|
170
|
+
end
|
171
|
+
|
172
|
+
def queue_diff
|
173
|
+
# Queries Redis to get Hash of what should running
|
174
|
+
# figures what is running and does a diff
|
175
|
+
# returns an Array of 2 Arrays: to_start, to_kill
|
176
|
+
|
177
|
+
goal, to_start, to_kill = [], [], []
|
178
|
+
queue_values(@hostname).each_pair { |queue,count| goal += [queue] * count.to_i }
|
179
|
+
# to sort or not to sort?
|
180
|
+
# goal.sort!
|
181
|
+
|
182
|
+
running_queues = @pids.values # check list
|
183
|
+
goal.each do |q|
|
184
|
+
if running_queues.include?(q)
|
185
|
+
# delete from checklist cause its already running
|
186
|
+
running_queues.delete_at(running_queues.index(q))
|
187
|
+
else
|
188
|
+
# not included in running queue, need to start
|
189
|
+
to_start << q
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
@pids.dup.each_pair do |k,v|
|
194
|
+
if running_queues.include?(v)
|
195
|
+
# whatever is left over in this checklist shouldn't be running
|
196
|
+
to_kill << k
|
197
|
+
running_queues.delete_at(running_queues.index(v))
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
if (to_start.length + @pids.keys.length - to_kill.length) > @max_children
|
202
|
+
# if to_start with existing minus whats to be killed is still greater than max children
|
203
|
+
log "WARN: need to start too many children, please raise max children"
|
204
|
+
end
|
205
|
+
|
206
|
+
kill_queues = to_kill.map { |x| @pids[x] }
|
207
|
+
log! ["GOTTA START:", to_start.map { |x| "#{x} (#{to_start.count(x)})" }.uniq.join(', '), "= #{to_start.length}"].delete_if { |x| x == (nil || '') }.join(' ')
|
208
|
+
log! ["GOTTA KILL:", kill_queues.map { |x| "#{x} (#{kill_queues.count(x)})" }.uniq.join(', '), "= #{to_kill.length}"].delete_if { |x| x == (nil || '') }.join(' ')
|
209
|
+
|
210
|
+
[to_start, to_kill] # return whats left
|
211
|
+
end
|
212
|
+
|
213
|
+
def remove!(pid)
|
214
|
+
# removes pid completely, ignores its queues
|
215
|
+
kill_child pid
|
216
|
+
@pids.delete(pid)
|
217
|
+
procline @pids.keys.join(', ')
|
218
|
+
end
|
219
|
+
|
220
|
+
def remove(pid)
|
221
|
+
# remove pid, and respawn same queues
|
222
|
+
@dead_queues.unshift(@pids[pid]) # keep track of queues that pid was running, put it at front of list
|
223
|
+
@pids.delete(pid)
|
224
|
+
procline @pids.keys.join(', ')
|
225
|
+
end
|
226
|
+
|
227
|
+
def shutdown!
|
228
|
+
log "Exiting..."
|
229
|
+
@shutdown = true
|
230
|
+
kill_children
|
231
|
+
%w(current max).each { |x| unregister_setting("#{x}_children") }
|
232
|
+
log! "Unregistered Max Children"
|
233
|
+
Process.waitall()
|
234
|
+
remove_pidfile!
|
235
|
+
end
|
236
|
+
|
237
|
+
def shutdown?
|
238
|
+
@shutdown
|
239
|
+
end
|
240
|
+
|
241
|
+
def paused?
|
242
|
+
@paused
|
243
|
+
end
|
244
|
+
|
245
|
+
def signal_hup
|
246
|
+
log "HUP received; purging children..."
|
247
|
+
kill_children
|
248
|
+
@paused = false # unpause after kill (restart child)
|
249
|
+
end
|
250
|
+
|
251
|
+
def signal_usr1
|
252
|
+
log "USR1 received; killing little children..."
|
253
|
+
kill_children
|
254
|
+
@paused = true # pause after kill cause we're paused
|
255
|
+
end
|
256
|
+
|
257
|
+
def signal_usr2
|
258
|
+
log "USR2 received; not making babies"
|
259
|
+
@paused = true # paused again
|
260
|
+
end
|
261
|
+
|
262
|
+
def signal_cont
|
263
|
+
log "CONT received; making babies..."
|
264
|
+
@paused = false # unpause
|
265
|
+
end
|
266
|
+
|
267
|
+
def kill_zombies!
|
268
|
+
@zombie_pids.dup.each do |pid|
|
269
|
+
begin
|
270
|
+
log! "Waiting for Zombie: #{pid}"
|
271
|
+
# Issue wait() to make sure pid isn't forgotten
|
272
|
+
Process.wait(@zombie_pids.pop)
|
273
|
+
rescue Errno::ECHILD
|
274
|
+
next
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
def kill_child(pid)
|
280
|
+
begin
|
281
|
+
Process.kill("QUIT", pid) # try graceful shutdown
|
282
|
+
log! "Child #{pid} killed. (#{@pids.keys.length-1})"
|
283
|
+
rescue Object => e # dunno what this does but it works; dont know exception
|
284
|
+
log! "Child #{pid} already dead, sad day. (#{@pids.keys.length-1}) #{e}"
|
285
|
+
ensure
|
286
|
+
# Keep track of ones we issued QUIT to
|
287
|
+
@zombie_pids << pid
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
def kill_children
|
292
|
+
@pids.dup.keys.each do |pid|
|
293
|
+
kill_child pid
|
294
|
+
remove pid
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
|
299
|
+
def log(message)
|
300
|
+
if verbosity == 1
|
301
|
+
puts "* #{message}"
|
302
|
+
elsif verbosity > 1
|
303
|
+
time = Time.now.strftime('%H:%M:%S %Y-%m-%d')
|
304
|
+
puts "*** [#{time}] #$$: #{message}"
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
def log!(message)
|
309
|
+
log message if verbosity > 1
|
310
|
+
end
|
311
|
+
|
312
|
+
def save_pid!
|
313
|
+
if @pidfile
|
314
|
+
begin
|
315
|
+
log "Saving pid to => #{@pidfile}"
|
316
|
+
File.open(@pidfile, 'w') { |f| f.write(Process.pid) }
|
317
|
+
rescue Errno::EACCES => e
|
318
|
+
puts "Cannot write pidfile => #{e}"
|
319
|
+
exit 1
|
320
|
+
rescue Errno::ENOENT => e
|
321
|
+
dir = File.dirname(@pidfile)
|
322
|
+
begin
|
323
|
+
log! "#{dir} doesnt exist; Creating it..."
|
324
|
+
FileUtils.mkdir_p(dir)
|
325
|
+
rescue Errno::EACCES => e
|
326
|
+
puts "Cannot create directory => #{e}"
|
327
|
+
exit 1
|
328
|
+
end
|
329
|
+
begin
|
330
|
+
save_pid! # after creating dir, do save again
|
331
|
+
rescue # rescue anything else to stop loop
|
332
|
+
exit 2
|
333
|
+
end
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
def remove_pidfile!
|
339
|
+
File.exists?(@pidfile) && File.delete(@pidfile) if @pidfile
|
340
|
+
end
|
341
|
+
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|