resque_manager 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README.markdown +417 -0
  4. data/Rakefile +41 -0
  5. data/app/assets/images/resque_manager/idle.png +0 -0
  6. data/app/assets/images/resque_manager/poll.png +0 -0
  7. data/app/assets/images/resque_manager/working.png +0 -0
  8. data/app/assets/javascripts/resque_manager/application.js +15 -0
  9. data/app/assets/javascripts/resque_manager/jquery-1.3.2.min.js +19 -0
  10. data/app/assets/javascripts/resque_manager/jquery.relatize_date.js +95 -0
  11. data/app/assets/javascripts/resque_manager/ranger.js +24 -0
  12. data/app/assets/stylesheets/resque_manager/application.css +14 -0
  13. data/app/assets/stylesheets/resque_manager/resque/resque.css +93 -0
  14. data/app/assets/stylesheets/resque_manager/resque/resque_reset.css +48 -0
  15. data/app/assets/stylesheets/resque_manager/resque_cleaner/cleaner.css +62 -0
  16. data/app/controllers/resque_manager/resque_controller.rb +313 -0
  17. data/app/helpers/resque_manager/application_helper.rb +4 -0
  18. data/app/helpers/resque_manager/resque_helper.rb +142 -0
  19. data/app/models/resque_manager/paginate.rb +54 -0
  20. data/app/views/layouts/resque_manager/application.html.erb +37 -0
  21. data/app/views/resque_manager/resque/_key.html.erb +17 -0
  22. data/app/views/resque_manager/resque/_limiter.html.erb +12 -0
  23. data/app/views/resque_manager/resque/_next_more.html.erb +10 -0
  24. data/app/views/resque_manager/resque/_paginate.html.erb +53 -0
  25. data/app/views/resque_manager/resque/_queues.html.erb +59 -0
  26. data/app/views/resque_manager/resque/_status_styles.erb +98 -0
  27. data/app/views/resque_manager/resque/_workers.html.erb +138 -0
  28. data/app/views/resque_manager/resque/_working.html.erb +69 -0
  29. data/app/views/resque_manager/resque/cleaner.html.erb +41 -0
  30. data/app/views/resque_manager/resque/cleaner_exec.html.erb +6 -0
  31. data/app/views/resque_manager/resque/cleaner_list.html.erb +172 -0
  32. data/app/views/resque_manager/resque/delayed.html.erb +35 -0
  33. data/app/views/resque_manager/resque/delayed_timestamp.html.erb +26 -0
  34. data/app/views/resque_manager/resque/error.erb +1 -0
  35. data/app/views/resque_manager/resque/overview.html.erb +4 -0
  36. data/app/views/resque_manager/resque/schedule.html.erb +96 -0
  37. data/app/views/resque_manager/resque/stats.html.erb +62 -0
  38. data/app/views/resque_manager/resque/status.html.erb +57 -0
  39. data/app/views/resque_manager/resque/statuses.html.erb +72 -0
  40. data/app/views/resque_manager/resque/workers.html.erb +1 -0
  41. data/config/routes.rb +38 -0
  42. data/config/sample_redis.yml +43 -0
  43. data/config/sample_resque_manager.yml +23 -0
  44. data/lib/resque_manager/engine.rb +9 -0
  45. data/lib/resque_manager/overrides/resque/failure/redis.rb +11 -0
  46. data/lib/resque_manager/overrides/resque/job.rb +69 -0
  47. data/lib/resque_manager/overrides/resque/resque.rb +8 -0
  48. data/lib/resque_manager/overrides/resque/worker.rb +291 -0
  49. data/lib/resque_manager/overrides/resque_scheduler/resque_scheduler.rb +58 -0
  50. data/lib/resque_manager/overrides/resque_status/chained_status.rb +46 -0
  51. data/lib/resque_manager/overrides/resque_status/hash.rb +12 -0
  52. data/lib/resque_manager/overrides/resque_status/status.rb +161 -0
  53. data/lib/resque_manager/recipes.rb +185 -0
  54. data/lib/resque_manager/version.rb +3 -0
  55. data/lib/resque_manager.rb +47 -0
  56. data/lib/tasks/failure.rake +8 -0
  57. data/lib/tasks/scheduler.rake +11 -0
  58. data/lib/tasks/worker.rake +129 -0
  59. data/test/dummy/README.rdoc +261 -0
  60. data/test/dummy/Rakefile +7 -0
  61. data/test/dummy/app/assets/javascripts/application.js +15 -0
  62. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  63. data/test/dummy/app/controllers/application_controller.rb +3 -0
  64. data/test/dummy/app/helpers/application_helper.rb +2 -0
  65. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  66. data/test/dummy/config/application.rb +65 -0
  67. data/test/dummy/config/boot.rb +10 -0
  68. data/test/dummy/config/environment.rb +5 -0
  69. data/test/dummy/config/environments/development.rb +37 -0
  70. data/test/dummy/config/environments/production.rb +67 -0
  71. data/test/dummy/config/environments/test.rb +37 -0
  72. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  73. data/test/dummy/config/initializers/inflections.rb +15 -0
  74. data/test/dummy/config/initializers/mime_types.rb +5 -0
  75. data/test/dummy/config/initializers/secret_token.rb +7 -0
  76. data/test/dummy/config/initializers/session_store.rb +8 -0
  77. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  78. data/test/dummy/config/locales/en.yml +5 -0
  79. data/test/dummy/config/routes.rb +4 -0
  80. data/test/dummy/config.ru +4 -0
  81. data/test/dummy/log/development.log +5045 -0
  82. data/test/dummy/public/404.html +26 -0
  83. data/test/dummy/public/422.html +26 -0
  84. data/test/dummy/public/500.html +25 -0
  85. data/test/dummy/public/favicon.ico +0 -0
  86. data/test/dummy/script/rails +6 -0
  87. data/test/dummy/tmp/cache/assets/C2A/A10/sprockets%2Fb2e622954654f415590723e9b882063e +0 -0
  88. data/test/dummy/tmp/cache/assets/C60/1D0/sprockets%2F8ed12e4193473760f95b973567a8c206 +0 -0
  89. data/test/dummy/tmp/cache/assets/CA1/970/sprockets%2Fc387148880e015d1eab0dc838b326022 +0 -0
  90. data/test/dummy/tmp/cache/assets/CAE/930/sprockets%2Fe227278d3c65d8aa1159da720263f771 +0 -0
  91. data/test/dummy/tmp/cache/assets/CD8/370/sprockets%2F357970feca3ac29060c1e3861e2c0953 +0 -0
  92. data/test/dummy/tmp/cache/assets/CDC/E30/sprockets%2Fe1207380d69eeee3284e02636c26f24a +0 -0
  93. data/test/dummy/tmp/cache/assets/CF1/720/sprockets%2Fd91a5918f5aa43a43c8135a67c78e989 +0 -0
  94. data/test/dummy/tmp/cache/assets/D0E/820/sprockets%2F00c6cc9dc46bf64347b3775d7d15541b +0 -0
  95. data/test/dummy/tmp/cache/assets/D16/180/sprockets%2F73d6fa09352cb76ac81e1683e832b93f +0 -0
  96. data/test/dummy/tmp/cache/assets/D27/170/sprockets%2Fec164819553e2e5b28f1efc9bd970978 +0 -0
  97. data/test/dummy/tmp/cache/assets/D2B/DA0/sprockets%2F989465d3ea8575dd0b54981a9e8add38 +0 -0
  98. data/test/dummy/tmp/cache/assets/D32/A10/sprockets%2F13fe41fee1fe35b49d145bcc06610705 +0 -0
  99. data/test/dummy/tmp/cache/assets/D37/1F0/sprockets%2F97119b908ebed2633edfd00ac90d9011 +0 -0
  100. data/test/dummy/tmp/cache/assets/D38/FB0/sprockets%2F74e5ba1cca7a1470d53c54fb60368b78 +0 -0
  101. data/test/dummy/tmp/cache/assets/D42/4E0/sprockets%2F0fa6e3c14356aa527d68a8d56fa37f28 +0 -0
  102. data/test/dummy/tmp/cache/assets/D43/C20/sprockets%2F1efd074fd1074b3dc88145b480ff961f +0 -0
  103. data/test/dummy/tmp/cache/assets/D46/CD0/sprockets%2F67f1ef70e7ede542318b8d55e25b16c3 +0 -0
  104. data/test/dummy/tmp/cache/assets/D4E/1B0/sprockets%2Ff7cbd26ba1d28d48de824f0e94586655 +0 -0
  105. data/test/dummy/tmp/cache/assets/D5A/EA0/sprockets%2Fd771ace226fc8215a3572e0aa35bb0d6 +0 -0
  106. data/test/dummy/tmp/cache/assets/D68/080/sprockets%2Fa26f2ae225aa4b87c462d540c7cf43f9 +0 -0
  107. data/test/dummy/tmp/cache/assets/D9A/B20/sprockets%2F0eddc19d46318e2e286cc171ae4cc73e +0 -0
  108. data/test/dummy/tmp/cache/assets/DA4/900/sprockets%2F515bf984438c6ec4b8a515fcc13baf8e +0 -0
  109. data/test/dummy/tmp/cache/assets/DBD/070/sprockets%2F60ffef45ddefd5c7746d17977fff0717 +0 -0
  110. data/test/dummy/tmp/cache/assets/DD7/AC0/sprockets%2Fc7c983c5c607dbfdb726eecc36146ca9 +0 -0
  111. data/test/dummy/tmp/cache/assets/DDC/400/sprockets%2Fcffd775d018f68ce5dba1ee0d951a994 +0 -0
  112. data/test/dummy/tmp/cache/assets/DF5/480/sprockets%2Fea4f3c726fc1046cad1ad243faf84e7d +0 -0
  113. data/test/dummy/tmp/cache/assets/E04/890/sprockets%2F2f5173deea6c795b8fdde723bb4b63af +0 -0
  114. data/test/dummy/tmp/cache/assets/E2B/7A0/sprockets%2Fd44ef07be0aa6d5b5dea4d37d7f72b4f +0 -0
  115. data/test/functional/resque_manager/resque_controller_test.rb +9 -0
  116. data/test/integration/navigation_test.rb +10 -0
  117. data/test/resque_manager_test.rb +7 -0
  118. data/test/test_helper.rb +15 -0
  119. data/test/unit/helpers/resque_manager/resque_helper_test.rb +6 -0
  120. metadata +307 -0
@@ -0,0 +1,291 @@
1
+ require 'socket'
2
+
3
+ module Resque
4
+ class Worker
5
+ @@local_ip = nil
6
+
7
+ def local_ip
8
+ @@local_ip ||= begin
9
+ UDPSocket.open do |s|
10
+ s.connect 'google.com', 1
11
+ s.addr.last
12
+ end
13
+ end
14
+ end
15
+
16
+ # The string representation is the same as the id for this worker
17
+ # instance. Can be used with `Worker.find`.
18
+ def to_s
19
+ @to_s || "#{hostname}(#{local_ip}):#{Process.pid}:#{Thread.current.object_id}:#{Thread.current[:path]}:#{Thread.current[:queues]}"
20
+ end
21
+
22
+ alias_method :id, :to_s
23
+
24
+ # When the worker gets the -USR2 signal, to_s may give a different value for the thread and queue portion
25
+ def pause_key
26
+ key = to_s.split(':')
27
+ "worker:#{key.first}:#{key.second}:all_workers:paused"
28
+ end
29
+
30
+ def pid
31
+ to_s.split(':').second
32
+ end
33
+
34
+ def thread
35
+ to_s.split(':').third
36
+ end
37
+
38
+ def path
39
+ to_s.split(':').fourth
40
+ end
41
+
42
+ def queue
43
+ to_s.split(':').last
44
+ end
45
+
46
+ def workers_in_pid
47
+ Array(Resque.redis.smembers(:workers)).select { |id| id =~ /\(#{ip}\):#{pid}/ }.map { |id| Resque::Worker.find(id) }.compact
48
+ end
49
+
50
+ def ip
51
+ to_s.split(':').first[/\b(?:\d{1,3}\.){3}\d{1,3}\b/]
52
+ end
53
+
54
+ def queues_in_pid
55
+ workers_in_pid.collect { |w| w.queue }
56
+ end
57
+
58
+ #OVERRIDE for multithreaded workers
59
+ def queues
60
+ Thread.current[:queues] == "*" ? Resque.queues.sort : Thread.current[:queues].split(',')
61
+ end
62
+
63
+ # Runs all the methods needed when a worker begins its lifecycle.
64
+ #OVERRIDE for multithreaded workers
65
+ def startup
66
+ enable_gc_optimizations
67
+ if Thread.current == Thread.main
68
+ register_signal_handlers
69
+ prune_dead_workers
70
+ end
71
+ run_hook :before_first_fork
72
+ register_worker
73
+
74
+ # Fix buffering so we can `rake resque:work > resque.log` and
75
+ # get output from the child in there.
76
+ $stdout.sync = true
77
+ end
78
+
79
+ # Schedule this worker for shutdown. Will finish processing the
80
+ # current job.
81
+ #OVERRIDE for multithreaded workers
82
+ def shutdown
83
+ log 'Exiting...'
84
+ Thread.list.each { |t| t[:shutdown] = true }
85
+ @shutdown = true
86
+ end
87
+
88
+ def paused
89
+ Resque.redis.get pause_key
90
+ end
91
+
92
+ # are we paused?
93
+ # OVERRIDE so UI can tell if we're paused
94
+ def paused?
95
+ @paused || paused.present?
96
+ end
97
+
98
+ # Stop processing jobs after the current one has completed (if we're
99
+ # currently running one).
100
+ #OVERRIDE to set a redis key so UI knows it's paused too
101
+ # Would prefer to call super but get no superclass method error
102
+ def pause_processing
103
+ log "USR2 received; pausing job processing"
104
+ @paused = true
105
+ Resque.redis.set(pause_key, Time.now.to_s)
106
+ end
107
+
108
+ # Start processing jobs again after a pause
109
+ #OVERRIDE to set remove redis key so UI knows it's unpaused too
110
+ # Would prefer to call super but get no superclass method error
111
+ def unpause_processing
112
+ log "CONT received; resuming job processing"
113
+ @paused = false
114
+ Resque.redis.del(pause_key)
115
+ end
116
+
117
+ # Looks for any workers which should be running on this server
118
+ # and, if they're not, removes them from Redis.
119
+ #
120
+ # This is a form of garbage collection. If a server is killed by a
121
+ # hard shutdown, power failure, or something else beyond our
122
+ # control, the Resque workers will not die gracefully and therefor
123
+ # will leave stale state information in Redis.
124
+ #
125
+ # By checking the current Redis state against the actual
126
+ # environment, we can determine if Redis is old and clean it up a bit.
127
+ def prune_dead_workers
128
+ Worker.all.each do |worker|
129
+ host, pid, thread, path, queues = worker.id.split(':')
130
+ next unless host.include?(hostname)
131
+ next if worker_pids.include?(pid)
132
+ log! "Pruning dead worker: #{worker}"
133
+ worker.unregister_worker
134
+ end
135
+ end
136
+
137
+ # Unregisters ourself as a worker. Useful when shutting down.
138
+ # OVERRIDE to also remove the pause key
139
+ # Would prefer to call super but get no superclass method error
140
+ def unregister_worker_with_pause(exception = nil)
141
+ unregister_worker_without_pause(exception)
142
+
143
+ Resque.redis.del(pause_key)
144
+ end
145
+ alias_method_chain :unregister_worker, :pause
146
+
147
+ def all_workers_in_pid_working
148
+ workers_in_pid.select { |w| (hash = w.processing) && !hash.empty? }
149
+ end
150
+
151
+ # This is the main workhorse method. Called on a Worker instance,
152
+ # it begins the worker life cycle.
153
+ #
154
+ # The following events occur during a worker's life cycle:
155
+ #
156
+ # 1. Startup: Signals are registered, dead workers are pruned,
157
+ # and this worker is registered.
158
+ # 2. Work loop: Jobs are pulled from a queue and processed.
159
+ # 3. Teardown: This worker is unregistered.
160
+ #
161
+ # Can be passed an integer representing the polling frequency.
162
+ # The default is 5 seconds, but for a semi-active site you may
163
+ # want to use a smaller value.
164
+ #
165
+ # Also accepts a block which will be passed the job as soon as it
166
+ # has completed processing. Useful for testing.
167
+ #OVERRIDE for multithreaded workers
168
+ def work(interval = 5.0, &block)
169
+ interval = Float(interval)
170
+ $0 = "resque: Starting"
171
+ startup
172
+
173
+ loop do
174
+ break if shutdown? || Thread.current[:shutdown]
175
+
176
+ if not paused? and job = reserve
177
+ log "got: #{job.inspect}"
178
+ job.worker = self
179
+ working_on job
180
+
181
+ procline "Processing #{job.queue} since #{Time.now.to_i} [#{job.payload_class}]"
182
+ if @child = fork(job) do
183
+ unregister_signal_handlers if term_child
184
+ reconnect
185
+ perform(job, &block)
186
+ exit! unless run_at_exit_hooks
187
+ end
188
+
189
+ srand # Reseeding
190
+ procline "Forked #{@child} at #{Time.now.to_i}"
191
+ begin
192
+ Process.waitpid(@child)
193
+ rescue SystemCallError
194
+ nil
195
+ end
196
+ job.fail(DirtyExit.new($?.to_s)) if $?.signaled?
197
+ else
198
+ reconnect
199
+ perform(job, &block)
200
+ end
201
+ done_working
202
+ @child = nil
203
+ else
204
+ break if interval.zero?
205
+ log! "Sleeping for #{interval} seconds"
206
+ procline paused? ? "Paused" : "Waiting for #{@queues.join(',')}"
207
+ sleep interval
208
+ end
209
+ end
210
+
211
+ unregister_worker
212
+ loop do
213
+ #hang onto the process until all threads are done
214
+ break if all_workers_in_pid_working.blank?
215
+ sleep interval.to_i
216
+ end
217
+ rescue Exception => exception
218
+ log "Failed to start worker : #{exception.inspect}"
219
+
220
+ unregister_worker(exception)
221
+ end
222
+
223
+ # logic for mappged_mget changed where it returns keys with nil values in latest redis gem.
224
+ def self.working
225
+ names = all
226
+ return [] unless names.any?
227
+ names.map! { |name| "worker:#{name}" }
228
+ Resque.redis.mapped_mget(*names).map do |key, value|
229
+ find key.sub("worker:", '') unless value.nil?
230
+ end.compact
231
+ end
232
+
233
+ def overview_message=(message)
234
+ data = encode(job.merge('overview_message' => message))
235
+ Resque.redis.set("worker:#{self}", data)
236
+ end
237
+
238
+ def overview_message
239
+ job['overview_message']
240
+ end
241
+
242
+ def self.start(options)
243
+ ips = options[:hosts]
244
+ application_path = options[:application_path]
245
+ queues = options[:queues]
246
+ if Rails.env =~ /development|test/
247
+ Thread.new(application_path, queues) { |application_path, queue| system("cd #{application_path || '.'}; bundle exec #{ResqueManager.resque_worker_rake || 'rake'} RAILS_ENV=#{Rails.env} QUEUE=#{queue} resque:work") }
248
+ else
249
+ Thread.new(ips, application_path, queues) { |ip_list, application_path, queue| system("cd #{Rails.root}; bundle exec cap #{Rails.env} resque:work host=#{ip_list} application_path=#{application_path} queue=#{queue}") }
250
+ end
251
+ end
252
+
253
+ def quit
254
+ if Rails.env =~ /development|test/
255
+ if RUBY_PLATFORM =~ /java/
256
+ #jruby doesn't trap the -QUIT signal
257
+ #-TERM gracefully kills the main pid and does a -9 on the child if there is one.
258
+ #Since jruby doesn't fork a child, the main worker is gracefully killed.
259
+ system("kill -TERM #{self.pid}")
260
+ else
261
+ system("kill -QUIT #{self.pid}")
262
+ end
263
+ else
264
+ system("cd #{Rails.root}; bundle exec cap #{Rails.env} resque:quit_worker pid=#{self.pid} host=#{self.ip} application_path=#{self.path}")
265
+ end
266
+ end
267
+
268
+ def pause
269
+ if Rails.env =~ /development|test/
270
+ system("kill -USR2 #{self.pid}")
271
+ else
272
+ system("cd #{Rails.root}; bundle exec cap #{Rails.env} resque:pause_worker pid=#{self.pid} host=#{self.ip}")
273
+ end
274
+ end
275
+
276
+ def continue
277
+ if Rails.env =~ /development|test/
278
+ system("kill -CONT #{self.pid}")
279
+ else
280
+ system("cd #{Rails.root}; bundle exec cap #{Rails.env} resque:continue_worker pid=#{self.pid} host=#{self.ip}")
281
+ end
282
+ end
283
+
284
+ def restart
285
+ queues = self.queues_in_pid.join('#')
286
+ quit
287
+ self.class.start(hosts: self.ip, queues: queues, application_path: self.path)
288
+ end
289
+
290
+ end
291
+ end
@@ -0,0 +1,58 @@
1
+ module ResqueScheduler
2
+ def schedule=(schedule_hash)
3
+ raise 'not implemented'
4
+ end
5
+
6
+ # Returns the schedule hash
7
+ def schedule
8
+ #the scheduler gem expects a hash, but it's now stored in
9
+ #redis as an array.
10
+ hash = {}
11
+ Resque.list_range(:scheduled, 0, -0).each do |job|
12
+ hash.merge! job
13
+ end
14
+ hash
15
+ end
16
+
17
+ def self.start(ips)
18
+ if Rails.env =~ /development|test/
19
+ Thread.new{system("rake resque:scheduler")}
20
+ else
21
+ Thread.new(ips){|ip_list|system("cd #{Rails.root}; #{ResqueManager::Cap.path} #{Rails.env} resque:scheduler host=#{ip_list}")}
22
+ end
23
+ end
24
+
25
+ def self.quit(ips)
26
+ if Rails.env =~ /development|test/
27
+ system("rake resque:quit_scheduler")
28
+ else
29
+ system("cd #{Rails.root}; #{ResqueManager::Cap.path} #{Rails.env} resque:quit_scheduler host=#{ips}")
30
+ end
31
+ end
32
+
33
+ def self.restart(ips)
34
+ quit(ips)
35
+ start(ips)
36
+ end
37
+
38
+ def self.farm_status
39
+ status = {}
40
+ if Rails.env =~ /development|test/
41
+ status['localhost'] = pids.present? ? 'Running' : 'Stopped'
42
+ else
43
+ Resque.schedule.values.collect{|job| job['ip']}.each do |ip|
44
+ cap = `cd #{Rails.root}; #{ResqueManager::Cap.path} #{Rails.env} resque:scheduler_status hosts=#{ip}`
45
+ status[ip] = cap =~ /resque:scheduler is up/ ? 'Running' : 'Stopped'
46
+ end
47
+ end
48
+ status
49
+ end
50
+
51
+ # Returns an array of string pids of all the other workers on this
52
+ # machine. Useful when pruning dead workers on startup.
53
+ def self.pids
54
+ `ps -A -o pid,command | grep [r]esque:scheduler`.split("\n").map do |line|
55
+ line.split(' ')[0]
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,46 @@
1
+ module Resque
2
+ module Plugins
3
+ module ChainedStatus
4
+
5
+ def self.included(base)
6
+ base.class_eval do
7
+ include Resque::Plugins::Status
8
+ extend ClassOverrides
9
+ include InstanceOverrides
10
+ end
11
+ end
12
+
13
+ module InstanceOverrides
14
+ # OVERRIDE to just use the name of it's parent job.
15
+ def name
16
+ status.name rescue nil
17
+ end
18
+
19
+ def completed(*messages)
20
+ super(*messages)
21
+ # "You must override this method to provide your own logic of when to actually call complete."
22
+ # if counter(:processed) >= options['total']
23
+ # super
24
+ # end
25
+ end
26
+ end
27
+
28
+ module ClassOverrides
29
+ # OVERRIDE to grab the uuid out of options so it can be chained to the calling worker
30
+ # instead of creating a new uuid.
31
+ def enqueue_to(queue, klass, options = {})
32
+ #tie this job to the status of the calling job
33
+ opts = HashWithIndifferentAccess.new(options)
34
+ raise ArgumentError, "You must supply a :uuid attribute in your call to create." unless opts['uuid']
35
+ uuid = opts['uuid']
36
+ if Resque.enqueue_to(queue, klass, uuid, options)
37
+ uuid
38
+ else
39
+ Resque::Plugins::Status::Hash.remove(uuid)
40
+ nil
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,12 @@
1
+ module Resque
2
+ module Plugins
3
+ module Status
4
+ class Hash < ::Hash
5
+ # The STATUSES constant is frozen, so we'll just manually add the paused? method here
6
+ def paused?
7
+ self['status'] === 'paused'
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,161 @@
1
+ module Resque
2
+ module Plugins
3
+ module Status
4
+
5
+
6
+ #OVERRIDE so we can add OverridesAndExtensionsClassMethods
7
+ def self.included(base)
8
+ attr_reader :worker
9
+
10
+ # can't call super, so add ClassMethods here that resque-status was doing
11
+ base.extend(ClassMethods) #add the methods in the resque-status gem
12
+ base.extend(ClassOverridesAndExtensions)
13
+ end
14
+
15
+ module ClassOverridesAndExtensions
16
+
17
+ #OVERRIDE to set the name that will be displayed on the status page for this job when it is first queued.
18
+ #The name will be changed when set_status is called, which is called on #tick, to the value set in your name method,
19
+ #but the UI name field is blank when it is first queued, so setting it here so we have something.
20
+ def enqueue_to(queue, klass, options = {})
21
+ uuid = Resque::Plugins::Status::Hash.generate_uuid
22
+ Resque::Plugins::Status::Hash.create uuid, {name: "#{self.name}: #{options.inspect}"}.merge(options)
23
+
24
+ if Resque.enqueue_to(queue, klass, uuid, options)
25
+ uuid
26
+ else
27
+ Resque::Plugins::Status::Hash.remove(uuid)
28
+ nil
29
+ end
30
+ end
31
+
32
+ # This is the method called by Resque::Worker when processing jobs. It
33
+ # creates a new instance of the job class and populates it with the uuid and
34
+ # options.
35
+ #
36
+ # You should not override this method, rather the <tt>perform</tt> instance method.
37
+ # OVERRIDE to get the worker and set when initializing the class
38
+ def perform(uuid=nil, options = {})
39
+ uuid ||= Resque::Plugins::Status::Hash.generate_uuid
40
+ worker = yield if block_given?
41
+ instance = new(uuid, worker, options)
42
+ instance.safe_perform!
43
+ instance
44
+ end
45
+
46
+ # OVERRIDE to clear all the keys that have the UUI. status, counters, etc.
47
+ def remove(uuid)
48
+ Resque.redis.zrem(set_key, uuid)
49
+ Resque.redis.keys("*#{uuid}").each do |key|
50
+ Resque.redis.del(key)
51
+ end
52
+ end
53
+
54
+ #If multiple workers are running at once and you need an incrementer, you can't use the status' num attribute because of race conditions.
55
+ #You can use a counter and call incr on it instead
56
+ def counter_key(counter, uuid)
57
+ "#{counter}:#{uuid}"
58
+ end
59
+
60
+ def counter(counter, uuid)
61
+ Resque.redis[counter_key(counter, uuid)].to_i
62
+ end
63
+
64
+ def incr_counter(counter, uuid)
65
+ key = counter_key(counter, uuid)
66
+ n = Resque.redis.incr(key)
67
+ if Resque::Plugins::Status::Hash.expire_in
68
+ Resque.redis.expire(key, Resque::Plugins::Status::Hash.expire_in)
69
+ end
70
+ n
71
+ end
72
+ end
73
+
74
+ # sets the status of the job for the current iteration. You should use
75
+ # the <tt>at</tt> method if you have actual numbers to track the iteration count.
76
+ # This will kill the job if it has been added to the kill list with
77
+ # <tt>Resque::Status.kill()</tt>
78
+ def tick(*messages)
79
+ kill! if should_kill? || status.killed?
80
+ set_status({'status' => 'working'}, *messages)
81
+ # check to see if the worker doing the job has been paused, pause the job if so
82
+ if self.worker && self.worker.paused?
83
+ loop do
84
+ # Set the status to paused.
85
+ # May need to do this repeatedly because there could be workers in a chained job still doing work.
86
+ pause! unless status.paused?
87
+ break unless self.worker.paused?
88
+ sleep 60
89
+ end
90
+ set_status({'status' => 'working'}, *messages) unless status && (status.completed? || status.paused? || status.killed?)
91
+ end
92
+ end
93
+
94
+ # Pause the current job, setting the status to 'paused'
95
+ def pause!
96
+ set_status({
97
+ 'status' => 'paused',
98
+ 'message' => "#{worker} paused at #{Time.now}"
99
+ })
100
+ end
101
+
102
+ # Create a new instance with <tt>uuid</tt> and <tt>options</tt>
103
+ # OVERRIDE to add the worker attr
104
+ def initialize(uuid, worker = nil, options = {})
105
+ @uuid = uuid
106
+ @options = options
107
+ @worker = worker
108
+ end
109
+
110
+ # Run by the Resque::Worker when processing this job. It wraps the <tt>perform</tt>
111
+ # method ensuring that the final status of the job is set regardless of error.
112
+ # If an error occurs within the job's work, it will set the status as failed and
113
+ # re-raise the error.
114
+ #OVERRIDE to kill it. The parent job may have been killed, so all child jobs should die as well.
115
+ def safe_perform!
116
+ k = should_kill?
117
+ kill! if k
118
+ unless k || (status && status.killed?)
119
+ set_status({'status' => 'working'})
120
+ perform
121
+ if status && status.failed?
122
+ on_failure(status.message) if respond_to?(:on_failure)
123
+ return
124
+ elsif status && !status.completed?
125
+ completed
126
+ end
127
+ on_success if respond_to?(:on_success)
128
+ end
129
+ rescue Killed
130
+ Rails.logger.info "Job #{self} Killed at #{Time.now}"
131
+ Resque::Plugins::Status::Hash.killed(uuid)
132
+ on_killed if respond_to?(:on_killed)
133
+ rescue => e
134
+ Rails.logger.error e
135
+ failed("The task failed because of an error: #{e}")
136
+ if respond_to?(:on_failure)
137
+ on_failure(e)
138
+ else
139
+ raise e
140
+ end
141
+ end
142
+
143
+ # sets a message for the job on the overview page
144
+ # it can be set repeatedly durring the job's processing to
145
+ # indicate the status of the job.
146
+ def overview_message=(message)
147
+ # there is no worker when run inline
148
+ self.worker.overview_message = message if self.worker
149
+ end
150
+
151
+ def incr_counter(counter)
152
+ self.class.incr_counter(counter, uuid)
153
+ end
154
+
155
+ def counter(counter)
156
+ self.class.counter(counter, uuid)
157
+ end
158
+
159
+ end
160
+ end
161
+ end