resque_stuck_queue 0.4.3 → 0.4.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +11 -5
- data/THOUGHTS +3 -0
- data/lib/resque_stuck_queue.rb +29 -30
- data/lib/resque_stuck_queue/config.rb +1 -1
- data/lib/resque_stuck_queue/signals.rb +7 -4
- data/lib/resque_stuck_queue/version.rb +1 -1
- data/test/test_helper.rb +7 -0
- data/test/test_resque_2.rb +7 -13
- data/test/test_resque_stuck_queue.rb +9 -6
- data/test/test_ver_2.rb +45 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b23678d316199cc108bef89177912ff5eb396d89
|
4
|
+
data.tar.gz: 055f4654e2f966a573fd398f2f55cbc56b21b766
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9f4df0b59b1b092d6ee389ebea5c45a98a27a35c3e12f14a6566f07349f31d7950b9f55b582cc928303d0fbd952ad06cdf2846c9ac2e6ee5e26e8f50db42f1c0
|
7
|
+
data.tar.gz: 315ddbab2de9dbd42bfb803f768da71ad1cb3f34715a8beb974f5d804e9427791dd79cd626c0bf2f3fded698e29ac0141ef764ec655b79a0803b9a314196e296
|
data/README.md
CHANGED
@@ -12,6 +12,12 @@ It will also fire a proc to notify you when it's recovered.
|
|
12
12
|
|
13
13
|
## How it works
|
14
14
|
|
15
|
+
It's a heartbeat mechanism:
|
16
|
+
|
17
|
+
![meme](http://cdn.memegenerator.net/instances/500x/43575729.jpg)
|
18
|
+
|
19
|
+
Ok, seriously:
|
20
|
+
|
15
21
|
When you call `start` you are essentially starting two threads that will continiously run until `stop` is called or until the process shuts down.
|
16
22
|
|
17
23
|
One thread is responsible for pushing a 'heartbeat' job to resque which will essentially refresh a specific key in redis every time that job is processed.
|
@@ -37,20 +43,20 @@ require 'logger'
|
|
37
43
|
# change to decent values that make sense for you
|
38
44
|
Resque::StuckQueue.config[:heartbeat_interval] = 10.seconds
|
39
45
|
Resque::StuckQueue.config[:watcher_interval] = 1.seconds
|
40
|
-
Resque::StuckQueue.config[:trigger_timeout] = 30.seconds
|
46
|
+
Resque::StuckQueue.config[:trigger_timeout] = 30.seconds # acceptable lagtime
|
41
47
|
|
42
48
|
# which queues to monitor
|
43
49
|
Resque::StuckQueue.config[:queues] = [:app, :custom_queue]
|
44
50
|
|
45
51
|
# handler for when a resque queue is being problematic
|
46
52
|
Resque::StuckQueue.config[:triggered_handler] = proc { |bad_queue, lagtime|
|
47
|
-
msg = "[BAD]
|
53
|
+
msg = "[BAD] APPNAME #{Rails.env}'s Resque #{bad_queue} queue lagging job execution by #{lagtime} seconds."
|
48
54
|
send_email(msg)
|
49
55
|
}
|
50
56
|
|
51
57
|
# handler for when a resque queue recovers
|
52
58
|
Resque::StuckQueue.config[:recovered_handler] = proc { |good_queue, lagtime|
|
53
|
-
msg = "[GOOD]
|
59
|
+
msg = "[GOOD] APPNAME #{Rails.env}'s Resque #{good_queue} queue lagging job execution by #{lagtime} seconds."
|
54
60
|
send_email(msg)
|
55
61
|
}
|
56
62
|
|
@@ -137,13 +143,13 @@ queues:
|
|
137
143
|
optional, monitor specific queues you want to send a heartbeat/monitor to. default is [:app]
|
138
144
|
|
139
145
|
abort_on_exception:
|
140
|
-
optional, if you want the resque-stuck-queue threads to explicitly raise, default is
|
146
|
+
optional, if you want the resque-stuck-queue threads to explicitly raise, default is true
|
141
147
|
|
142
148
|
heartbeat_job:
|
143
149
|
optional, your own custom refreshing job. if you are using something other than resque
|
144
150
|
|
145
151
|
enable_signals:
|
146
|
-
optional, allow resque::stuck's signal_handlers which do mostly nothing at this point.
|
152
|
+
optional, allow resque::stuck's signal_handlers which do mostly nothing at this point. possible future plan: log info, reopen log file, etc.
|
147
153
|
|
148
154
|
</pre>
|
149
155
|
|
data/THOUGHTS
CHANGED
data/lib/resque_stuck_queue.rb
CHANGED
@@ -131,8 +131,37 @@ module Resque
|
|
131
131
|
@stopped
|
132
132
|
end
|
133
133
|
|
134
|
+
def trigger_handler(queue_name, type)
|
135
|
+
raise 'Must trigger either the recovered or triggered handler!' unless (type == :recovered || type == :triggered)
|
136
|
+
handler_name = :"#{type}_handler"
|
137
|
+
logger.info("Triggering #{type} handler for #{queue_name} at #{Time.now}.")
|
138
|
+
(config[handler_name] || const_get(handler_name.upcase)).call(queue_name, lag_time(queue_name))
|
139
|
+
manual_refresh(queue_name, type)
|
140
|
+
rescue => e
|
141
|
+
logger.info("handler #{type} for #{queue_name} crashed: #{e.inspect}")
|
142
|
+
logger.info("\n#{e.backtrace.join("\n")}")
|
143
|
+
raise e
|
144
|
+
end
|
145
|
+
|
146
|
+
def log_starting_info
|
147
|
+
logger.info("Starting StuckQueue with config: #{self.config.inspect}")
|
148
|
+
end
|
149
|
+
|
150
|
+
def log_watcher_info(queue_name)
|
151
|
+
logger.info("Lag time for #{queue_name} is #{lag_time(queue_name).inspect} seconds.")
|
152
|
+
if triggered_ago = last_triggered(queue_name)
|
153
|
+
logger.info("Last triggered for #{queue_name} is #{triggered_ago.inspect} seconds.")
|
154
|
+
else
|
155
|
+
logger.info("No last trigger found for #{queue_name}.")
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
134
159
|
private
|
135
160
|
|
161
|
+
def read_from_redis(keyname)
|
162
|
+
redis.get(keyname)
|
163
|
+
end
|
164
|
+
|
136
165
|
def setup_heartbeat_thread
|
137
166
|
@threads << Thread.new do
|
138
167
|
Thread.current.abort_on_exception = abort_on_exception
|
@@ -240,36 +269,6 @@ module Resque
|
|
240
269
|
end
|
241
270
|
end
|
242
271
|
|
243
|
-
def trigger_handler(queue_name, type)
|
244
|
-
raise 'Must trigger either the recovered or triggered handler!' unless (type == :recovered || type == :triggered)
|
245
|
-
handler_name = :"#{type}_handler"
|
246
|
-
logger.info("Triggering #{type} handler for #{queue_name} at #{Time.now}.")
|
247
|
-
(config[handler_name] || const_get(handler_name.upcase)).call(queue_name, lag_time(queue_name))
|
248
|
-
manual_refresh(queue_name, type)
|
249
|
-
rescue => e
|
250
|
-
logger.info("handler #{type} for #{queue_name} crashed: #{e.inspect}")
|
251
|
-
logger.info("\n#{e.backtrace.join("\n")}")
|
252
|
-
force_stop!
|
253
|
-
end
|
254
|
-
|
255
|
-
def log_starting_info
|
256
|
-
logger.info("Starting StuckQueue with config: #{self.config.inspect}")
|
257
|
-
end
|
258
|
-
|
259
|
-
def log_watcher_info(queue_name)
|
260
|
-
logger.info("Lag time for #{queue_name} is #{lag_time(queue_name).inspect} seconds.")
|
261
|
-
if triggered_ago = last_triggered(queue_name)
|
262
|
-
logger.info("Last triggered for #{queue_name} is #{triggered_ago.inspect} seconds.")
|
263
|
-
else
|
264
|
-
logger.info("No last trigger found for #{queue_name}.")
|
265
|
-
end
|
266
|
-
|
267
|
-
end
|
268
|
-
|
269
|
-
def read_from_redis(keyname)
|
270
|
-
redis.get(keyname)
|
271
|
-
end
|
272
|
-
|
273
272
|
def wait_for_it(type)
|
274
273
|
if type == :heartbeat_interval
|
275
274
|
sleep config[:heartbeat_interval] || HEARTBEAT_INTERVAL
|
@@ -31,7 +31,7 @@ module Resque
|
|
31
31
|
:queues => "optional, monitor specific queues you want to send a heartbeat/monitor to. default is [:app]",
|
32
32
|
:abort_on_exception => "optional, if you want the resque-stuck-queue threads to explicitly raise, default is true",
|
33
33
|
:heartbeat_job => "optional, your own custom refreshing job. if you are using something other than resque",
|
34
|
-
:enable_signals => "optional, allow resque::stuck's signal_handlers which do mostly nothing at this point.",
|
34
|
+
:enable_signals => "optional, allow resque::stuck's signal_handlers which do mostly nothing at this point. possible future plan: log info, reopen log file, etc.",
|
35
35
|
}
|
36
36
|
|
37
37
|
OPTIONS = OPTIONS_DESCRIPTIONS.keys
|
@@ -9,13 +9,16 @@ module Resque
|
|
9
9
|
|
10
10
|
trap("SIGUSR1") do
|
11
11
|
ENV['SIGUSR1'] = "done be had"
|
12
|
+
Resque::StuckQueue.logger.info("Inspecting StuckQueue config: #{Resque::StuckQueue.config.inspect}")
|
13
|
+
Resque::StuckQueue.queues.each do |q| Resque::StuckQueue.log_watcher_info(q) end
|
12
14
|
Resque::StuckQueue.logger.info("¯\_(ツ)_/¯ ...")
|
13
15
|
end
|
14
16
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
17
|
+
# do something meaningful
|
18
|
+
#trap("SIGUSR2") do
|
19
|
+
# require 'pry'
|
20
|
+
# binding.pry
|
21
|
+
#end
|
19
22
|
|
20
23
|
end
|
21
24
|
end
|
data/test/test_helper.rb
CHANGED
@@ -31,10 +31,17 @@ module TestHelper
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def start_and_stop_loops_after(secs)
|
34
|
+
abort_or_not = Thread.abort_on_exception
|
35
|
+
Thread.abort_on_exception = Resque::StuckQueue.config[:abort_on_exception]
|
36
|
+
|
34
37
|
ops = []
|
35
38
|
ops << Thread.new { Resque::StuckQueue.start }
|
36
39
|
ops << Thread.new { sleep secs; Resque::StuckQueue.stop }
|
37
40
|
ops.map(&:join)
|
41
|
+
|
42
|
+
ensure
|
43
|
+
Thread.abort_on_exception = abort_or_not
|
44
|
+
Resque::StuckQueue.force_stop!
|
38
45
|
end
|
39
46
|
|
40
47
|
end
|
data/test/test_resque_2.rb
CHANGED
@@ -2,20 +2,15 @@
|
|
2
2
|
# $ RESQUE_2=1 bi; RESQUE_2=1 be ruby -I. -Ilib/ test/test_resque_2.rb
|
3
3
|
if !ENV['RESQUE_2'].nil?
|
4
4
|
|
5
|
-
require
|
6
|
-
require "minitest/autorun"
|
7
|
-
require "pry"
|
8
|
-
require "logger"
|
9
|
-
|
10
|
-
$:.unshift(".")
|
11
|
-
require "resque_stuck_queue"
|
12
|
-
require File.join(File.expand_path(File.dirname(__FILE__)), "resque", "set_redis_key")
|
13
|
-
require File.join(File.expand_path(File.dirname(__FILE__)), "resque", "refresh_latest_timestamp")
|
5
|
+
require File.join(File.expand_path(File.dirname(__FILE__)), "test_helper")
|
14
6
|
|
15
7
|
class TestResque2 < Minitest::Test
|
16
8
|
|
9
|
+
include TestHelper
|
10
|
+
|
17
11
|
def setup
|
18
12
|
assert (Resque::VERSION.match /^2\./), "must run in 2.0"
|
13
|
+
Resque.redis = Redis.new
|
19
14
|
Resque::StuckQueue.config[:redis] = Redis.new
|
20
15
|
Redis.new.flushall
|
21
16
|
end
|
@@ -31,6 +26,7 @@ if !ENV['RESQUE_2'].nil?
|
|
31
26
|
Resque::StuckQueue.config[:logger] = Logger.new($stdout)
|
32
27
|
Resque::StuckQueue.config[:triggered_handler] = proc { Redis.new.incr("test-incr-key") }
|
33
28
|
Resque::StuckQueue.config[:redis] = Redis.new
|
29
|
+
Resque::StuckQueue.config[:queues] = [:app]
|
34
30
|
|
35
31
|
#binding.pry
|
36
32
|
Resque::StuckQueue.start_in_background
|
@@ -38,11 +34,9 @@ if !ENV['RESQUE_2'].nil?
|
|
38
34
|
@r2_pid = fork { Resque::StuckQueue.config[:redis] = Redis.new ; Resque::Worker.new("*", :graceful_term => true).work ; Process.waitall }
|
39
35
|
sleep 10
|
40
36
|
|
41
|
-
#
|
37
|
+
# triggers once
|
42
38
|
assert_equal Redis.new.get("test-incr-key").to_i, 0
|
43
|
-
|
44
|
-
`kill #{@r2_pid}`
|
45
|
-
Process.waitall
|
39
|
+
hax_kill_resque
|
46
40
|
Resque::StuckQueue.force_stop!
|
47
41
|
end
|
48
42
|
|
@@ -41,14 +41,17 @@ class TestResqueStuckQueue < Minitest::Test
|
|
41
41
|
def test_stops_if_handler_raises
|
42
42
|
puts "#{__method__}"
|
43
43
|
Resque::StuckQueue.config[:trigger_timeout] = 1 # wait a short time, will trigger
|
44
|
+
Resque::StuckQueue.config[:abort_on_exception] = true # bubble up the raise
|
44
45
|
last_time_too_old = Time.now.to_i - Resque::StuckQueue::TRIGGER_TIMEOUT
|
45
46
|
Resque::StuckQueue.config[:triggered_handler] = proc { raise "handler had bad sad!" }
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
assert
|
50
|
-
|
51
|
-
|
47
|
+
begin
|
48
|
+
start_and_stop_loops_after(4)
|
49
|
+
sleep 4
|
50
|
+
assert false, "should raise"
|
51
|
+
rescue => e
|
52
|
+
puts e.inspect
|
53
|
+
assert true, "should raise handler bad sad #{e.inspect}"
|
54
|
+
end
|
52
55
|
end
|
53
56
|
|
54
57
|
end
|
data/test/test_ver_2.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# run with
|
2
|
+
# $ RESQUE_2=1 bi; RESQUE_2=1 be ruby -I. -Ilib/ test/test_resque_2.rb
|
3
|
+
if !ENV['RESQUE_2'].nil?
|
4
|
+
|
5
|
+
require File.join(File.expand_path(File.dirname(__FILE__)), "test_helper")
|
6
|
+
|
7
|
+
class TestResque2 < Minitest::Test
|
8
|
+
|
9
|
+
include TestHelper
|
10
|
+
|
11
|
+
def setup
|
12
|
+
assert (Resque::VERSION.match /^2\./), "must run in 2.0"
|
13
|
+
Resque.redis = Redis.new
|
14
|
+
Resque::StuckQueue.config[:redis] = Redis.new
|
15
|
+
Redis.new.flushall
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_works_with_2_point_oh_do_not_trigger_because_key_is_updated
|
19
|
+
|
20
|
+
Resque::StuckQueue.config[:redis] = Redis.new
|
21
|
+
|
22
|
+
Resque::StuckQueue.config[:watcher_interval] = 1
|
23
|
+
Resque::StuckQueue.config[:heartbeat_interval] = 1
|
24
|
+
Resque::StuckQueue.config[:abort_on_exception] = true
|
25
|
+
Resque::StuckQueue.config[:trigger_timeout] = 5
|
26
|
+
Resque::StuckQueue.config[:logger] = Logger.new($stdout)
|
27
|
+
Resque::StuckQueue.config[:triggered_handler] = proc { Redis.new.incr("test-incr-key") }
|
28
|
+
Resque::StuckQueue.config[:redis] = Redis.new
|
29
|
+
Resque::StuckQueue.config[:queues] = [:app]
|
30
|
+
|
31
|
+
#binding.pry
|
32
|
+
Resque::StuckQueue.start_in_background
|
33
|
+
|
34
|
+
@r2_pid = fork { Resque::StuckQueue.config[:redis] = Redis.new ; Resque::Worker.new("*", :graceful_term => true).work ; Process.waitall }
|
35
|
+
sleep 10
|
36
|
+
|
37
|
+
# triggers once
|
38
|
+
assert_equal Redis.new.get("test-incr-key").to_i, 1
|
39
|
+
hax_kill_resque
|
40
|
+
Resque::StuckQueue.force_stop!
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: resque_stuck_queue
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shai Rosenfeld
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2014-02-
|
12
|
+
date: 2014-02-12 00:00:00 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: redis-mutex
|
@@ -81,6 +81,7 @@ files:
|
|
81
81
|
- test/test_resque_2.rb
|
82
82
|
- test/test_resque_stuck_queue.rb
|
83
83
|
- test/test_set_custom_refresh_job.rb
|
84
|
+
- test/test_ver_2.rb
|
84
85
|
homepage: https://github.com/shaiguitar/resque_stuck_queue/
|
85
86
|
licenses:
|
86
87
|
- MIT
|
@@ -115,3 +116,4 @@ test_files:
|
|
115
116
|
- test/test_resque_2.rb
|
116
117
|
- test/test_resque_stuck_queue.rb
|
117
118
|
- test/test_set_custom_refresh_job.rb
|
119
|
+
- test/test_ver_2.rb
|