resque_stuck_queue 0.0.1 → 0.0.2
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 +8 -8
- data/Gemfile +1 -0
- data/Gemfile.lock +5 -0
- data/README.md +3 -0
- data/Rakefile +6 -0
- data/THOUGHTS +10 -0
- data/lib/resque_stuck_queue/version.rb +1 -1
- data/lib/resque_stuck_queue.rb +26 -20
- data/resque_stuck_queue.gemspec +2 -0
- data/test/test_collision.rb +41 -0
- data/test/test_helper.rb +26 -0
- data/test/test_integration.rb +4 -5
- data/test/test_resque_stuck_queue.rb +13 -65
- metadata +20 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
YWNkZWJjMzVlMTZlNmI4ZDNiZjk5MTk2ZjFiOTVhZTIxYjA4ZDY1Ng==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
NDlmNDlmZGI3OThiMDk2NjJiZGZhZDlmY2YxNzZhOGY0ZmU1YzMxNQ==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
ODgyNjJhMjNiYjNhNWRjNjBhYzhiNGUxODNmZDhiNWJiNDViODQ1ZTBlNTdk
|
10
|
+
MzU2ZjU3M2MyMWY0OGE5ZWIzYTBlMGI4OTEzZGFkY2RlNjkyNWU2MmI0NjYy
|
11
|
+
NDQ3M2IyY2E3YTAwYzUyODExZDNkY2E2ZmI3ZDVjYzAwYWVlN2Q=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
NzM4ZjQyMDQzOTI2MDQyNTExMzQyNzEwNTAxM2M0NmRiYzM2YWJmMmQyYmFk
|
14
|
+
MTMxZWM1YmY3Nzc2ODM0N2M3MTM3NjI2ZmYxNDFjNDc5ZmUyNmVmZmIxMGQ5
|
15
|
+
MjkyNWNhOWZkNTNmNWVmM2ExNjg5ZGNkOGRmOTk0ZGU5NGQwYjA=
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -21,6 +21,10 @@ GEM
|
|
21
21
|
rack
|
22
22
|
rake (10.1.0)
|
23
23
|
redis (3.0.6)
|
24
|
+
redis-classy (1.2.0)
|
25
|
+
redis-namespace (~> 1.0)
|
26
|
+
redis-mutex (2.1.1)
|
27
|
+
redis-classy (~> 1.2)
|
24
28
|
redis-namespace (1.4.1)
|
25
29
|
redis (~> 3.0.4)
|
26
30
|
resque (1.25.1)
|
@@ -56,6 +60,7 @@ DEPENDENCIES
|
|
56
60
|
mocha
|
57
61
|
pry
|
58
62
|
rake
|
63
|
+
redis-mutex
|
59
64
|
resque
|
60
65
|
resque-mock
|
61
66
|
resque-scheduler
|
data/README.md
CHANGED
@@ -31,6 +31,9 @@ Resque::StuckQueue.config[:heartbeat] = 5.minutes
|
|
31
31
|
Resque::StuckQueue.config[:trigger_timeout] = 10.hours
|
32
32
|
|
33
33
|
# what gets triggered when resque-stuck-queue will detect the latest heartbeat is older than the trigger_timeout time set above.
|
34
|
+
#
|
35
|
+
# triggering will update the key, so you'll have to wait the trigger_timeout again
|
36
|
+
# in order for it to trigger again even if workers are still stale.
|
34
37
|
Resque::StuckQueue.config[:handler] = proc { send_email }
|
35
38
|
|
36
39
|
# optional, in case you want to set your own name for the key that will be used as the last good hearbeat time
|
data/Rakefile
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
require 'rake/testtask'
|
2
2
|
|
3
3
|
task :default => :test
|
4
|
+
#task :test do
|
5
|
+
## forking and what not. keep containted in each own process?
|
6
|
+
#Dir['./test/test_*.rb'].each do |file|
|
7
|
+
#system("ruby -I. -I lib/ #{file}")
|
8
|
+
#end
|
9
|
+
#end
|
4
10
|
Rake::TestTask.new do |t|
|
5
11
|
t.pattern = "test/test_*rb"
|
6
12
|
end
|
data/THOUGHTS
CHANGED
@@ -19,3 +19,13 @@ reproduce the error and integrate test like that
|
|
19
19
|
## TODOS
|
20
20
|
|
21
21
|
verify @config options, raise if no handler, etc.
|
22
|
+
|
23
|
+
- use locks to only notify/trigger once in case this is used in a server-cluster type environment adn you only want the handler triggered once
|
24
|
+
|
25
|
+
- resque in stuck (t state process) SIGSTOP sticks up the checker thread?
|
26
|
+
or if not running
|
27
|
+
|
28
|
+
- ensure it only runs from the server box and not the resque box??
|
29
|
+
(the deploy restarts the server but not resque workers)
|
30
|
+
|
31
|
+
|
data/lib/resque_stuck_queue.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
require "resque_stuck_queue/version"
|
2
2
|
|
3
|
+
# TODO rm redis-mutex dep and just do the setnx locking here
|
4
|
+
require 'redis-mutex'
|
5
|
+
Redis::Classy.db = Resque.redis
|
6
|
+
|
3
7
|
# TODO move this require into a configurable?
|
4
8
|
require 'resque'
|
5
9
|
|
@@ -7,8 +11,7 @@ module Resque
|
|
7
11
|
module StuckQueue
|
8
12
|
|
9
13
|
GLOBAL_KEY = "resque-stuck-queue"
|
10
|
-
|
11
|
-
HEARTBEAT = 60 * 60 # check/refresh every hour
|
14
|
+
HEARTBEAT = 60 * 60 # check/refresh every hour
|
12
15
|
TRIGGER_TIMEOUT = 5 * 60 * 60 # warn/trigger 5 hours
|
13
16
|
HANDLER = proc { $stderr.puts("Shit gone bad with them queues.") }
|
14
17
|
|
@@ -54,8 +57,6 @@ module Resque
|
|
54
57
|
@threads = []
|
55
58
|
config.freeze
|
56
59
|
|
57
|
-
mark_first_use
|
58
|
-
|
59
60
|
Thread.abort_on_exception = config[:abort_on_exception]
|
60
61
|
|
61
62
|
enqueue_repeating_refresh_job
|
@@ -101,8 +102,15 @@ module Resque
|
|
101
102
|
@threads << Thread.new do
|
102
103
|
while @running
|
103
104
|
wait_for_it
|
104
|
-
|
105
|
-
|
105
|
+
mutex = Redis::Mutex.new('resque_stuck_queue_lock', block: 0)
|
106
|
+
if mutex.lock
|
107
|
+
begin
|
108
|
+
if Time.now.to_i - last_time_worked > max_wait_time
|
109
|
+
trigger_handler
|
110
|
+
end
|
111
|
+
ensure
|
112
|
+
mutex.unlock
|
113
|
+
end
|
106
114
|
end
|
107
115
|
end
|
108
116
|
end
|
@@ -110,30 +118,28 @@ module Resque
|
|
110
118
|
|
111
119
|
def last_time_worked
|
112
120
|
time_set = read_from_redis
|
113
|
-
if
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
121
|
+
if time_set
|
122
|
+
time_set
|
123
|
+
else
|
124
|
+
manual_refresh
|
125
|
+
end.to_i
|
126
|
+
end
|
127
|
+
|
128
|
+
def manual_refresh
|
129
|
+
time = Time.now.to_i
|
130
|
+
Resque.redis.set(global_key, time)
|
131
|
+
time
|
119
132
|
end
|
120
133
|
|
121
134
|
def trigger_handler
|
122
135
|
(config[:handler] || HANDLER).call
|
136
|
+
manual_refresh
|
123
137
|
end
|
124
138
|
|
125
139
|
def read_from_redis
|
126
140
|
Resque.redis.get(global_key)
|
127
141
|
end
|
128
142
|
|
129
|
-
def has_been_used?
|
130
|
-
Resque.redis.get(VERIFIED_KEY)
|
131
|
-
end
|
132
|
-
|
133
|
-
def mark_first_use
|
134
|
-
Resque.redis.set(VERIFIED_KEY, "true")
|
135
|
-
end
|
136
|
-
|
137
143
|
def wait_for_it
|
138
144
|
sleep config[:heartbeat] || HEARTBEAT
|
139
145
|
end
|
data/resque_stuck_queue.gemspec
CHANGED
@@ -18,6 +18,8 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
+
spec.add_runtime_dependency "redis-mutex"
|
22
|
+
|
21
23
|
spec.add_development_dependency "bundler", "~> 1.5"
|
22
24
|
spec.add_development_dependency "rake"
|
23
25
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require File.join(File.expand_path(File.dirname(__FILE__)), "test_helper")
|
2
|
+
|
3
|
+
class TestCollision < Minitest::Test
|
4
|
+
|
5
|
+
include TestHelper
|
6
|
+
|
7
|
+
def test_two_processes_interacting
|
8
|
+
puts "#{__method__}"
|
9
|
+
# no resque should be running here so timeouts will be reached + trigger
|
10
|
+
Resque.redis.del("test-incr-key")
|
11
|
+
|
12
|
+
p1 = fork { Resque.redis.client.reconnect; run_resque_stuck_daemon; }
|
13
|
+
p2 = fork { Resque.redis.client.reconnect; run_resque_stuck_daemon; }
|
14
|
+
p3 = fork { Resque.redis.client.reconnect; run_resque_stuck_daemon; }
|
15
|
+
p4 = fork { Resque.redis.client.reconnect; run_resque_stuck_daemon; }
|
16
|
+
|
17
|
+
Thread.new {
|
18
|
+
sleep 5 # let test run and trigger once occur (according to time below)
|
19
|
+
`kill -9 #{p1}`
|
20
|
+
`kill -9 #{p2}`
|
21
|
+
`kill -9 #{p3}`
|
22
|
+
`kill -9 #{p4}`
|
23
|
+
Process.waitpid # reap
|
24
|
+
}
|
25
|
+
|
26
|
+
Process.waitall
|
27
|
+
|
28
|
+
assert_equal 1, Resque.redis.get("test-incr-key").to_i
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def run_resque_stuck_daemon
|
34
|
+
Resque::StuckQueue.config[:heartbeat] = 1
|
35
|
+
Resque::StuckQueue.config[:abort_on_exception] = true
|
36
|
+
Resque::StuckQueue.config[:trigger_timeout] = 4
|
37
|
+
Resque::StuckQueue.config[:handler] = proc { Resque.redis.incr("test-incr-key") }
|
38
|
+
Resque::StuckQueue.start
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'minitest'
|
2
|
+
require "minitest/autorun"
|
3
|
+
require 'pry'
|
4
|
+
require 'mocha'
|
5
|
+
require 'resque/mock'
|
6
|
+
$:.unshift(".")
|
7
|
+
require 'resque_stuck_queue'
|
8
|
+
require File.join(File.expand_path(File.dirname(__FILE__)), "resque", "set_redis_key")
|
9
|
+
require File.join(File.expand_path(File.dirname(__FILE__)), "resque", "refresh_latest_timestamp")
|
10
|
+
|
11
|
+
module TestHelper
|
12
|
+
|
13
|
+
def run_resque
|
14
|
+
pid = fork { exec("QUEUE=* bundle exec rake --trace resque:work") }
|
15
|
+
sleep 3 # wait for resque to boot up
|
16
|
+
pid
|
17
|
+
end
|
18
|
+
|
19
|
+
def start_and_stop_loops_after(secs)
|
20
|
+
ops = []
|
21
|
+
ops << Thread.new { Resque::StuckQueue.start }
|
22
|
+
ops << Thread.new { sleep secs; Resque::StuckQueue.stop }
|
23
|
+
ops.map(&:join)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
data/test/test_integration.rb
CHANGED
@@ -25,7 +25,7 @@ class TestIntegration < Minitest::Test
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def teardown
|
28
|
-
|
28
|
+
`kill -9 #{@resque_pid}` # CONT falls throughs sometimes? hax, rm this and SIGSTOP/SIGCONT
|
29
29
|
Resque::StuckQueue.stop
|
30
30
|
Process.waitpid(@resque_pid)
|
31
31
|
end
|
@@ -37,8 +37,7 @@ class TestIntegration < Minitest::Test
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def test_resque_enqueues_a_job_does_not_trigger
|
40
|
-
|
41
|
-
puts '1'
|
40
|
+
puts "#{__method__}"
|
42
41
|
Resque::StuckQueue.config[:trigger_timeout] = 100 # wait a while so we don't trigger
|
43
42
|
Resque::StuckQueue.config[:heartbeat] = 2
|
44
43
|
@triggered = false
|
@@ -57,8 +56,7 @@ class TestIntegration < Minitest::Test
|
|
57
56
|
end
|
58
57
|
|
59
58
|
def test_resque_does_not_enqueues_a_job_does_trigger
|
60
|
-
|
61
|
-
puts '2'
|
59
|
+
puts "#{__method__}"
|
62
60
|
Resque::StuckQueue.config[:trigger_timeout] = 2 # won't allow waiting too much and will complain (eg trigger) sooner than later
|
63
61
|
Resque::StuckQueue.config[:heartbeat] = 1
|
64
62
|
@triggered = false
|
@@ -71,6 +69,7 @@ class TestIntegration < Minitest::Test
|
|
71
69
|
Process.kill("SIGSTOP", @resque_pid) # jic, do not process jobs so we definitely trigger
|
72
70
|
Resque.enqueue(SetRedisKey)
|
73
71
|
assert_equal Resque.redis.get(SetRedisKey::NAME), nil
|
72
|
+
sleep 2 # allow timeout to trigger
|
74
73
|
|
75
74
|
# check handler did get called
|
76
75
|
assert_equal @triggered, true
|
@@ -1,105 +1,53 @@
|
|
1
|
-
require
|
2
|
-
require "minitest/autorun"
|
3
|
-
require 'mocha'
|
4
|
-
|
5
|
-
require 'resque/mock'
|
6
|
-
|
7
|
-
$:.unshift(".")
|
8
|
-
require 'resque_stuck_queue'
|
1
|
+
require File.join(File.expand_path(File.dirname(__FILE__)), "test_helper")
|
9
2
|
|
10
3
|
class TestResqueStuckQueue < Minitest::Test
|
11
4
|
|
5
|
+
include TestHelper
|
6
|
+
|
12
7
|
def teardown
|
13
|
-
puts
|
14
|
-
Resque::StuckQueue.unstub(:has_been_used?)
|
8
|
+
puts "#{__method__}"
|
15
9
|
Resque::StuckQueue.unstub(:read_from_redis)
|
16
10
|
end
|
17
11
|
|
18
12
|
def setup
|
19
|
-
puts
|
13
|
+
puts "#{__method__}"
|
20
14
|
# clean previous test runs
|
21
15
|
Resque.redis.flushall
|
22
16
|
Resque.mock!
|
23
17
|
Resque::StuckQueue.config[:heartbeat] = 1 # seconds
|
24
|
-
Resque::StuckQueue.config[:trigger_timeout] = 2
|
25
18
|
Resque::StuckQueue.config[:abort_on_exception] = true
|
26
19
|
end
|
27
20
|
|
28
|
-
# usually the key will be set from previous runs since it will persist (redis) between deploys etc.
|
29
|
-
# so you shouldn't be running into this scenario (nil key) other than
|
30
|
-
# 0) test setup clearing out this key
|
31
|
-
# 1) the VERY first time you use this lib when it first gets set.
|
32
|
-
# 2) redis gets wiped out
|
33
|
-
# 3) resque jobs never get run!
|
34
|
-
# this has the unfortunate meaning that if no jobs are *ever* enqueued, this lib won't catch that problem.
|
35
|
-
# so we split the funcationaliy to raise if no key is there, unless it's the first time it's being used since being started.
|
36
|
-
def test_thread_does_not_trigger_when_no_key_exists_on_first_use
|
37
|
-
puts '1'
|
38
|
-
|
39
|
-
# lib never ran, and key is not there
|
40
|
-
Resque::StuckQueue.stubs(:has_been_used?).returns(nil)
|
41
|
-
Resque::StuckQueue.stubs(:read_from_redis).returns(nil)
|
42
|
-
@triggered = false
|
43
|
-
Resque::StuckQueue.config[:handler] = proc { @triggered = true }
|
44
|
-
start_and_stop_loops_after(2)
|
45
|
-
assert_equal false, @triggered # "handler should not be called"
|
46
|
-
end
|
47
|
-
|
48
|
-
def test_thread_does_trigger_when_no_key_exists_on_any_other_use
|
49
|
-
|
50
|
-
puts '2'
|
51
|
-
# lib already ran, but key is not there
|
52
|
-
Resque::StuckQueue.stubs(:has_been_used?).returns(true)
|
53
|
-
Resque::StuckQueue.stubs(:read_from_redis).returns(nil)
|
54
|
-
|
55
|
-
@triggered = false
|
56
|
-
Resque::StuckQueue.config[:handler] = proc { @triggered = true }
|
57
|
-
start_and_stop_loops_after(2)
|
58
|
-
assert_equal true, @triggered # "handler should be called"
|
59
|
-
end
|
60
|
-
|
61
21
|
def test_configure_global_key
|
62
|
-
puts
|
22
|
+
puts "#{__method__}"
|
63
23
|
assert_nil Resque.redis.get("it-is-configurable"), "global key should not be set"
|
64
24
|
Resque::StuckQueue.config[:global_key] = "it-is-configurable"
|
65
25
|
start_and_stop_loops_after(2)
|
66
26
|
refute_nil Resque.redis.get("it-is-configurable"), "global key should be set"
|
67
27
|
end
|
68
28
|
|
69
|
-
def test_it_sets_a_verified_key_to_indicate_first_use
|
70
|
-
puts '4'
|
71
|
-
assert_nil Resque.redis.get(Resque::StuckQueue::VERIFIED_KEY), "should be nil before lib is used"
|
72
|
-
start_and_stop_loops_after(2)
|
73
|
-
refute_nil Resque.redis.get(Resque::StuckQueue::VERIFIED_KEY), "should set verified key after used"
|
74
|
-
end
|
75
|
-
|
76
29
|
def test_it_does_not_trigger_handler_if_under_max_time
|
77
|
-
puts
|
30
|
+
puts "#{__method__}"
|
31
|
+
Resque::StuckQueue.config[:trigger_timeout] = 5
|
78
32
|
Resque::StuckQueue.stubs(:read_from_redis).returns(Time.now.to_i)
|
33
|
+
|
79
34
|
@triggered = false
|
80
35
|
Resque::StuckQueue.config[:handler] = proc { @triggered = true }
|
81
|
-
start_and_stop_loops_after(
|
36
|
+
start_and_stop_loops_after(3)
|
82
37
|
assert_equal false, @triggered # "handler should not be called"
|
83
38
|
end
|
84
39
|
|
85
40
|
def test_it_triggers_handler_if_over_trigger_timeout
|
86
|
-
puts
|
41
|
+
puts "#{__method__}"
|
42
|
+
Resque::StuckQueue.config[:trigger_timeout] = 2
|
87
43
|
last_time_too_old = Time.now.to_i - Resque::StuckQueue::TRIGGER_TIMEOUT
|
88
44
|
Resque::StuckQueue.stubs(:read_from_redis).returns(last_time_too_old.to_s)
|
45
|
+
|
89
46
|
@triggered = false
|
90
47
|
Resque::StuckQueue.config[:handler] = proc { @triggered = true }
|
91
48
|
start_and_stop_loops_after(2)
|
92
49
|
assert_equal true, @triggered # "handler should be called"
|
93
50
|
end
|
94
51
|
|
95
|
-
private
|
96
|
-
|
97
|
-
def start_and_stop_loops_after(secs)
|
98
|
-
ops = []
|
99
|
-
ops << Thread.new { Resque::StuckQueue.start }
|
100
|
-
ops << Thread.new { sleep secs; Resque::StuckQueue.stop }
|
101
|
-
ops.map(&:join)
|
102
|
-
end
|
103
|
-
|
104
52
|
end
|
105
53
|
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
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.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shai Rosenfeld
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-01-
|
11
|
+
date: 2014-01-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: redis-mutex
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ! '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ! '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: bundler
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -57,6 +71,8 @@ files:
|
|
57
71
|
- resque_stuck_queue.gemspec
|
58
72
|
- test/resque/refresh_latest_timestamp.rb
|
59
73
|
- test/resque/set_redis_key.rb
|
74
|
+
- test/test_collision.rb
|
75
|
+
- test/test_helper.rb
|
60
76
|
- test/test_integration.rb
|
61
77
|
- test/test_resque_stuck_queue.rb
|
62
78
|
homepage: https://github.com/shaiguitar/resque_stuck_queue/
|
@@ -86,6 +102,8 @@ summary: fire a handler when your queues are wonky
|
|
86
102
|
test_files:
|
87
103
|
- test/resque/refresh_latest_timestamp.rb
|
88
104
|
- test/resque/set_redis_key.rb
|
105
|
+
- test/test_collision.rb
|
106
|
+
- test/test_helper.rb
|
89
107
|
- test/test_integration.rb
|
90
108
|
- test/test_resque_stuck_queue.rb
|
91
109
|
has_rdoc:
|