resque_stuck_queue 0.0.9 → 0.0.10
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.
- checksums.yaml +4 -4
- data/README.md +6 -5
- data/lib/resque_stuck_queue/version.rb +1 -1
- data/lib/resque_stuck_queue.rb +34 -26
- data/test/test_helper.rb +2 -2
- data/test/test_integration.rb +5 -7
- data/test/test_named_queues.rb +68 -0
- metadata +3 -3
- data/test/test_named_queue.rb +0 -35
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
data.tar.gz:
|
4
|
-
metadata.gz:
|
3
|
+
data.tar.gz: 984e71835c73d5f071caca892a28cb91ac6efb8f
|
4
|
+
metadata.gz: 0f1326d639f3e02c6710b18bb4ba16f25edd30f9
|
5
5
|
SHA512:
|
6
|
-
data.tar.gz:
|
7
|
-
metadata.gz:
|
6
|
+
data.tar.gz: c21a0aab9167fc5dbf9e9d95f7625165bc306df82f509db512198eefc79cf51a1801a65af658239b44d5959a52ad68406a4527bd14a4b953d4816b2f9eb59f05
|
7
|
+
metadata.gz: 7f5674fd8a47fd21f08ba0114aa6f8fde2fb75e0237fa8e9cce5d1462b5cb8be9dcf8427d05c9cb19499a55835d52733c82849e7b60323b06b5d5bacfd05026d
|
data/README.md
CHANGED
@@ -6,7 +6,7 @@ This is to be used to satisfy an ops problem. There have been cases resque proce
|
|
6
6
|
|
7
7
|
## What is it?
|
8
8
|
|
9
|
-
If resque doesn't run jobs within a certain timeframe, it will trigger a pre-defined handler of your choice. You can use this to send an email, pager duty, add more resque workers, restart resque, send you a txt...whatever suits you.
|
9
|
+
If resque doesn't run jobs in specific queues (defaults to `@queue = :app`) within a certain timeframe, it will trigger a pre-defined handler of your choice. You can use this to send an email, pager duty, add more resque workers, restart resque, send you a txt...whatever suits you.
|
10
10
|
|
11
11
|
## How it works
|
12
12
|
|
@@ -39,10 +39,11 @@ Resque::StuckQueue.config[:trigger_timeout] = 10.hours
|
|
39
39
|
Resque::StuckQueue.config[:handler] = proc { send_email }
|
40
40
|
|
41
41
|
# optional, in case you want to set your own name for the key that will be used as the last good hearbeat time
|
42
|
+
# note this will be namespaced under the specific queue it's monitoring, for eg "app:name-the-refresh-key-as-you-please"
|
42
43
|
Resque::StuckQueue.config[:global_key] = "name-the-refresh-key-as-you-please"
|
43
44
|
|
44
|
-
# optional, monitor
|
45
|
-
Resque::StuckQueue.config[:
|
45
|
+
# optional, monitor specific queues you want to send a heartbeat/monitor
|
46
|
+
Resque::StuckQueue.config[:queues] = [:app, :high, :my_custom_queue_name]
|
46
47
|
|
47
48
|
# optional, if you want the resque-stuck-queue threads to explicitly raise, default is false
|
48
49
|
Resque::StuckQueue.config[:abort_on_exception] = true
|
@@ -109,7 +110,7 @@ $ bundle exec rake --trace resque:stuck_queue
|
|
109
110
|
|
110
111
|
## Sidekiq/Other redis-based job queues
|
111
112
|
|
112
|
-
If you have trouble with other queues you can use this lib by setting your own custom refresh job (aka, the job that refreshes
|
113
|
+
If you have trouble with other queues you can use this lib by setting your own custom refresh job (aka, the job that refreshes your queue specific global_key). The one thing you need to take care of is ensure whatever and however you enque your own custom job, it sets the global_key to Time.now:
|
113
114
|
|
114
115
|
<pre>
|
115
116
|
|
@@ -117,7 +118,7 @@ class CustomJob
|
|
117
118
|
include Sidekiq::Worker
|
118
119
|
def perform
|
119
120
|
# ensure you're setting the key in the redis the job queue is using
|
120
|
-
$redis.set(Resque::StuckQueue.
|
121
|
+
$redis.set(Resque::StuckQueue.global_key_for(queue_name), Time.now.to_i)
|
121
122
|
end
|
122
123
|
end
|
123
124
|
|
data/lib/resque_stuck_queue.rb
CHANGED
@@ -50,6 +50,18 @@ module Resque
|
|
50
50
|
@redis ||= (config[:redis] || Resque.redis)
|
51
51
|
end
|
52
52
|
|
53
|
+
def global_key_for(under_queue)
|
54
|
+
"#{under_queue}:#{config[:global_key] || GLOBAL_KEY}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def global_keys
|
58
|
+
queues.map{|q| global_key_for(q) }
|
59
|
+
end
|
60
|
+
|
61
|
+
def queues
|
62
|
+
@queues ||= (config[:queues] || [:app])
|
63
|
+
end
|
64
|
+
|
53
65
|
def start_in_background
|
54
66
|
Thread.new do
|
55
67
|
Thread.current.abort_on_exception = config[:abort_on_exception]
|
@@ -94,6 +106,7 @@ module Resque
|
|
94
106
|
def reset!
|
95
107
|
# clean state so we can stop and start in the same process.
|
96
108
|
@config = config.dup #unfreeze
|
109
|
+
@queues = nil
|
97
110
|
@running = false
|
98
111
|
@logger = nil
|
99
112
|
end
|
@@ -102,15 +115,6 @@ module Resque
|
|
102
115
|
@stopped
|
103
116
|
end
|
104
117
|
|
105
|
-
def global_key
|
106
|
-
# public, for use in custom heartbeat job
|
107
|
-
"#{named_queue}:#{config[:global_key] || GLOBAL_KEY}"
|
108
|
-
end
|
109
|
-
|
110
|
-
def named_queue
|
111
|
-
config[:named_queue] || :app
|
112
|
-
end
|
113
|
-
|
114
118
|
private
|
115
119
|
|
116
120
|
def enqueue_repeating_refresh_job
|
@@ -121,17 +125,20 @@ module Resque
|
|
121
125
|
# we want to go through resque jobs, because that's what we're trying to test here:
|
122
126
|
# ensure that jobs get executed and the time is updated!
|
123
127
|
logger.info("Sending refresh job")
|
124
|
-
|
128
|
+
enqueue_jobs
|
125
129
|
wait_for_it
|
126
130
|
end
|
127
131
|
end
|
128
132
|
end
|
129
133
|
|
130
|
-
def
|
134
|
+
def enqueue_jobs
|
131
135
|
if config[:refresh_job]
|
136
|
+
# FIXME config[:refresh_job] with mutliple queues is bad semantics
|
132
137
|
config[:refresh_job].call
|
133
138
|
else
|
134
|
-
|
139
|
+
queues.each do |queue_name|
|
140
|
+
Resque.enqueue_to(queue_name, RefreshLatestTimestamp, [global_key_for(queue_name), redis.client.host, redis.client.port])
|
141
|
+
end
|
135
142
|
end
|
136
143
|
end
|
137
144
|
|
@@ -143,9 +150,11 @@ module Resque
|
|
143
150
|
mutex = Redis::Mutex.new('resque_stuck_queue_lock', block: 0)
|
144
151
|
if mutex.lock
|
145
152
|
begin
|
146
|
-
|
147
|
-
|
148
|
-
|
153
|
+
queues.each do |queue_name|
|
154
|
+
if Time.now.to_i - last_time_worked(queue_name) > max_wait_time
|
155
|
+
logger.info("Triggering handler for #{queue_name} at #{Time.now} (pid: #{Process.pid})")
|
156
|
+
trigger_handler(queue_name)
|
157
|
+
end
|
149
158
|
end
|
150
159
|
ensure
|
151
160
|
mutex.unlock
|
@@ -156,31 +165,31 @@ module Resque
|
|
156
165
|
end
|
157
166
|
end
|
158
167
|
|
159
|
-
def last_time_worked
|
160
|
-
time_set = read_from_redis
|
168
|
+
def last_time_worked(queue_name)
|
169
|
+
time_set = read_from_redis(queue_name)
|
161
170
|
if time_set
|
162
171
|
time_set
|
163
172
|
else
|
164
|
-
manual_refresh
|
173
|
+
manual_refresh(queue_name)
|
165
174
|
end.to_i
|
166
175
|
end
|
167
176
|
|
168
|
-
def manual_refresh
|
177
|
+
def manual_refresh(queue_name)
|
169
178
|
time = Time.now.to_i
|
170
|
-
redis.set(
|
179
|
+
redis.set(global_key_for(queue_name), time)
|
171
180
|
time
|
172
181
|
end
|
173
182
|
|
174
|
-
def trigger_handler
|
183
|
+
def trigger_handler(queue_name)
|
175
184
|
(config[:handler] || HANDLER).call
|
176
|
-
manual_refresh
|
185
|
+
manual_refresh(queue_name)
|
177
186
|
rescue => e
|
178
|
-
logger.info("handler crashed: #{e.inspect}")
|
187
|
+
logger.info("handler for #{queue_name} crashed: #{e.inspect}")
|
179
188
|
force_stop!
|
180
189
|
end
|
181
190
|
|
182
|
-
def read_from_redis
|
183
|
-
redis.get(
|
191
|
+
def read_from_redis(queue_name)
|
192
|
+
redis.get(global_key_for(queue_name))
|
184
193
|
end
|
185
194
|
|
186
195
|
def wait_for_it
|
@@ -195,7 +204,6 @@ module Resque
|
|
195
204
|
end
|
196
205
|
|
197
206
|
class RefreshLatestTimestamp
|
198
|
-
@queue = Resque::StuckQueue.named_queue
|
199
207
|
def self.perform(args)
|
200
208
|
timestamp_key = args[0]
|
201
209
|
host = args[1]
|
data/test/test_helper.rb
CHANGED
@@ -11,8 +11,8 @@ require File.join(File.expand_path(File.dirname(__FILE__)), "resque", "refresh_l
|
|
11
11
|
|
12
12
|
module TestHelper
|
13
13
|
|
14
|
-
def run_resque
|
15
|
-
pid = fork { exec("QUEUE
|
14
|
+
def run_resque(queue_name = "*")
|
15
|
+
pid = fork { exec("QUEUE=#{queue_name} bundle exec rake --trace resque:work") }
|
16
16
|
sleep 3 # wait for resque to boot up
|
17
17
|
pid
|
18
18
|
end
|
data/test/test_integration.rb
CHANGED
@@ -2,13 +2,16 @@ require 'minitest'
|
|
2
2
|
require "minitest/autorun"
|
3
3
|
require 'pry'
|
4
4
|
|
5
|
+
|
5
6
|
$:.unshift(".")
|
6
7
|
require 'resque_stuck_queue'
|
7
8
|
require File.join(File.expand_path(File.dirname(__FILE__)), "resque", "set_redis_key")
|
8
9
|
require File.join(File.expand_path(File.dirname(__FILE__)), "resque", "refresh_latest_timestamp")
|
10
|
+
require File.join(File.expand_path(File.dirname(__FILE__)), "test_helper")
|
9
11
|
|
10
12
|
class TestIntegration < Minitest::Test
|
11
13
|
|
14
|
+
include TestHelper
|
12
15
|
# TODODS there's a better way to do this.
|
13
16
|
#
|
14
17
|
#
|
@@ -30,12 +33,6 @@ class TestIntegration < Minitest::Test
|
|
30
33
|
Process.waitpid(@resque_pid)
|
31
34
|
end
|
32
35
|
|
33
|
-
def run_resque
|
34
|
-
pid = fork { exec("QUEUE=* bundle exec rake --trace resque:work") }
|
35
|
-
sleep 3 # wait for resque to boot up
|
36
|
-
pid
|
37
|
-
end
|
38
|
-
|
39
36
|
def test_resque_enqueues_a_job_does_not_trigger
|
40
37
|
puts "#{__method__}"
|
41
38
|
Resque::StuckQueue.config[:trigger_timeout] = 100 # wait a while so we don't trigger
|
@@ -84,7 +81,7 @@ class TestIntegration < Minitest::Test
|
|
84
81
|
Resque::StuckQueue.config[:heartbeat] = 1
|
85
82
|
|
86
83
|
begin
|
87
|
-
Resque::StuckQueue.config[:refresh_job] = proc { Resque.enqueue(RefreshLatestTimestamp, Resque::StuckQueue.
|
84
|
+
Resque::StuckQueue.config[:refresh_job] = proc { Resque.enqueue(RefreshLatestTimestamp, Resque::StuckQueue.global_key_for(:app)) }
|
88
85
|
@triggered = false
|
89
86
|
Resque::StuckQueue.config[:handler] = proc { @triggered = true }
|
90
87
|
Thread.new { Resque::StuckQueue.start }
|
@@ -98,4 +95,5 @@ class TestIntegration < Minitest::Test
|
|
98
95
|
end
|
99
96
|
end
|
100
97
|
|
98
|
+
|
101
99
|
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require File.join(File.expand_path(File.dirname(__FILE__)), "test_helper")
|
2
|
+
|
3
|
+
class TestNamedQueues < Minitest::Test
|
4
|
+
|
5
|
+
include TestHelper
|
6
|
+
|
7
|
+
def setup
|
8
|
+
Resque::StuckQueue.config[:trigger_timeout] = 1
|
9
|
+
Resque::StuckQueue.config[:heartbeat] = 1
|
10
|
+
Resque::StuckQueue.config[:abort_on_exception] = true
|
11
|
+
Resque.redis.flushall
|
12
|
+
end
|
13
|
+
|
14
|
+
def teardown
|
15
|
+
`kill -9 #{@resque_pid}` if @resque_pid
|
16
|
+
Resque::StuckQueue.stop
|
17
|
+
Process.waitpid(@resque_pid) if @resque_pid
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_no_custom_queues_defaults_to_app
|
21
|
+
puts "#{__method__}"
|
22
|
+
Resque::StuckQueue.config[:queues] = nil
|
23
|
+
start_and_stop_loops_after(2)
|
24
|
+
assert Resque::StuckQueue.global_keys.include?("app:resque-stuck-queue"), 'has global keys'
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_has_custom_queues
|
28
|
+
puts "#{__method__}"
|
29
|
+
Resque::StuckQueue.config[:queues] = [:foo,:bar]
|
30
|
+
start_and_stop_loops_after(2)
|
31
|
+
assert Resque::StuckQueue.global_keys.include?("foo:resque-stuck-queue"), 'has global keys'
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_resque_enqueues_a_job_with_resqueue_running_but_on_that_queue_does_trigger
|
35
|
+
puts "#{__method__}"
|
36
|
+
Resque::StuckQueue.config[:trigger_timeout] = 2 # won't allow waiting too much and will complain (eg trigger) sooner than later
|
37
|
+
Resque::StuckQueue.config[:heartbeat] = 1
|
38
|
+
@triggered = false
|
39
|
+
Resque::StuckQueue.config[:handler] = proc { @triggered = true }
|
40
|
+
Resque::StuckQueue.start_in_background
|
41
|
+
|
42
|
+
# job gets enqueued successfully
|
43
|
+
@resque_pid = run_resque("no-such-jobs-for-this-queue")
|
44
|
+
sleep 2 # allow timeout to trigger
|
45
|
+
|
46
|
+
# check handler did get called
|
47
|
+
assert_equal @triggered, true
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_resque_enqueues_a_job_correct_queue_does_not_trigger
|
51
|
+
puts "#{__method__}"
|
52
|
+
Resque::StuckQueue.config[:trigger_timeout] = 2 # won't allow waiting too much and will complain (eg trigger) sooner than later
|
53
|
+
Resque::StuckQueue.config[:heartbeat] = 1
|
54
|
+
Resque::StuckQueue.config[:queues] = [:custom_queue_name, :diff_one]
|
55
|
+
assert Resque::StuckQueue.global_keys.include?("custom_queue_name:resque-stuck-queue"), 'has global keys'
|
56
|
+
@triggered = false
|
57
|
+
Resque::StuckQueue.config[:handler] = proc { @triggered = true }
|
58
|
+
@resque_pid = run_resque("custom_queue_name")
|
59
|
+
Resque::StuckQueue.start_in_background
|
60
|
+
sleep 2 # allow timeout to trigger
|
61
|
+
|
62
|
+
# check handler did not get called
|
63
|
+
assert_equal @triggered, false
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
|
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.0.
|
4
|
+
version: 0.0.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shai Rosenfeld
|
@@ -66,7 +66,7 @@ files:
|
|
66
66
|
- test/test_helper.rb
|
67
67
|
- test/test_integration.rb
|
68
68
|
- test/test_logger.rb
|
69
|
-
- test/
|
69
|
+
- test/test_named_queues.rb
|
70
70
|
- test/test_resque_2.rb
|
71
71
|
- test/test_resque_stuck_queue.rb
|
72
72
|
- test/test_set_custom_refresh_job.rb
|
@@ -100,7 +100,7 @@ test_files:
|
|
100
100
|
- test/test_helper.rb
|
101
101
|
- test/test_integration.rb
|
102
102
|
- test/test_logger.rb
|
103
|
-
- test/
|
103
|
+
- test/test_named_queues.rb
|
104
104
|
- test/test_resque_2.rb
|
105
105
|
- test/test_resque_stuck_queue.rb
|
106
106
|
- test/test_set_custom_refresh_job.rb
|
data/test/test_named_queue.rb
DELETED
@@ -1,35 +0,0 @@
|
|
1
|
-
require File.join(File.expand_path(File.dirname(__FILE__)), "test_helper")
|
2
|
-
|
3
|
-
class TestNamedQueue < Minitest::Test
|
4
|
-
|
5
|
-
include TestHelper
|
6
|
-
|
7
|
-
def setup
|
8
|
-
Resque::StuckQueue.config[:trigger_timeout] = 1
|
9
|
-
Resque::StuckQueue.config[:heartbeat] = 1
|
10
|
-
Resque::StuckQueue.config[:abort_on_exception] = true
|
11
|
-
end
|
12
|
-
|
13
|
-
def teardown
|
14
|
-
Resque::StuckQueue.reset!
|
15
|
-
end
|
16
|
-
|
17
|
-
def test_no_custom_named_queue
|
18
|
-
puts "#{__method__}"
|
19
|
-
Resque::StuckQueue.config[:named_queue] = nil
|
20
|
-
start_and_stop_loops_after(2)
|
21
|
-
assert_equal Resque::StuckQueue.global_key, "app:resque-stuck-queue"
|
22
|
-
assert_equal Resque::StuckQueue.named_queue, :app
|
23
|
-
end
|
24
|
-
|
25
|
-
def test_has_custom_named_queue
|
26
|
-
puts "#{__method__}"
|
27
|
-
Resque::StuckQueue.config[:named_queue] = :foo
|
28
|
-
start_and_stop_loops_after(2)
|
29
|
-
assert_equal Resque::StuckQueue.global_key, "foo:resque-stuck-queue"
|
30
|
-
assert_equal Resque::StuckQueue.named_queue, :foo
|
31
|
-
end
|
32
|
-
|
33
|
-
end
|
34
|
-
|
35
|
-
|