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.
Files changed (34) hide show
  1. data/Gemfile +9 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +88 -0
  4. data/Rakefile +35 -0
  5. data/bin/kewatcher +68 -0
  6. data/config/config_example.yml +11 -0
  7. data/lib/resque-sliders/commander.rb +73 -0
  8. data/lib/resque-sliders/helpers.rb +72 -0
  9. data/lib/resque-sliders/kewatcher.rb +345 -0
  10. data/lib/resque-sliders/server/public/css/jqueryui-1.8.16/blitzer/jquery-ui.custom.css +568 -0
  11. data/lib/resque-sliders/server/public/css/sliders.css +30 -0
  12. data/lib/resque-sliders/server/public/images/ui-bg_diagonals-thick_75_f3d8d8_40x40.png +0 -0
  13. data/lib/resque-sliders/server/public/images/ui-bg_dots-small_65_a6a6a6_2x2.png +0 -0
  14. data/lib/resque-sliders/server/public/images/ui-bg_flat_0_333333_40x100.png +0 -0
  15. data/lib/resque-sliders/server/public/images/ui-bg_flat_65_ffffff_40x100.png +0 -0
  16. data/lib/resque-sliders/server/public/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  17. data/lib/resque-sliders/server/public/images/ui-bg_glass_55_fbf8ee_1x400.png +0 -0
  18. data/lib/resque-sliders/server/public/images/ui-bg_highlight-hard_100_eeeeee_1x100.png +0 -0
  19. data/lib/resque-sliders/server/public/images/ui-bg_highlight-hard_100_f6f6f6_1x100.png +0 -0
  20. data/lib/resque-sliders/server/public/images/ui-bg_highlight-soft_15_cc0000_1x100.png +0 -0
  21. data/lib/resque-sliders/server/public/images/ui-icons_004276_256x240.png +0 -0
  22. data/lib/resque-sliders/server/public/images/ui-icons_cc0000_256x240.png +0 -0
  23. data/lib/resque-sliders/server/public/images/ui-icons_ffffff_256x240.png +0 -0
  24. data/lib/resque-sliders/server/public/js/jquery-ui-1.8.16.custom.min.js +791 -0
  25. data/lib/resque-sliders/server/public/js/sliders.js +64 -0
  26. data/lib/resque-sliders/server/views/index.erb +59 -0
  27. data/lib/resque-sliders/server.rb +71 -0
  28. data/lib/resque-sliders/version.rb +7 -0
  29. data/lib/resque-sliders.rb +9 -0
  30. data/resque-sliders.gemspec +29 -0
  31. data/test/redis-test.conf +115 -0
  32. data/test/resque-sliders_test.rb +12 -0
  33. data/test/test_helper.rb +74 -0
  34. 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