resque-durable 0.2.0 → 0.2.1
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.
- data/lib/resque/durable/guid.rb +24 -0
- data/lib/resque/durable/monitor.rb +47 -0
- data/lib/resque/durable/queue_audit.rb +117 -0
- data/lib/resque/durable.rb +77 -0
- metadata +9 -6
@@ -0,0 +1,24 @@
|
|
1
|
+
module Resque
|
2
|
+
module Durable
|
3
|
+
module GUID
|
4
|
+
|
5
|
+
def self.generate
|
6
|
+
[ hostname,
|
7
|
+
Process.pid,
|
8
|
+
Time.now.to_i,
|
9
|
+
increment_counter
|
10
|
+
].join('/')
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.hostname
|
14
|
+
@hostname ||= `hostname`.chomp
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.increment_counter
|
18
|
+
@counter ||= 0
|
19
|
+
@counter += 1
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Resque
|
2
|
+
module Durable
|
3
|
+
module Monitor
|
4
|
+
|
5
|
+
attr_accessor :auditor, :expiration, :wait_duration
|
6
|
+
|
7
|
+
def initialize(auditor)
|
8
|
+
@auditor = auditor
|
9
|
+
end
|
10
|
+
|
11
|
+
def watch
|
12
|
+
auditor.recover
|
13
|
+
auditor.cleanup(expiration.ago)
|
14
|
+
end
|
15
|
+
|
16
|
+
def run
|
17
|
+
install_signal_handlers
|
18
|
+
|
19
|
+
loop do
|
20
|
+
watch
|
21
|
+
wait
|
22
|
+
break if @stopped
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def wait
|
27
|
+
sleep(wait_duration)
|
28
|
+
end
|
29
|
+
|
30
|
+
def wait_duration
|
31
|
+
@wait_duration ||= 1
|
32
|
+
end
|
33
|
+
|
34
|
+
def install_signal_handlers
|
35
|
+
trap('TERM') { stop }
|
36
|
+
trap('INT') { stop }
|
37
|
+
end
|
38
|
+
|
39
|
+
def stop
|
40
|
+
puts 'Stopping...'
|
41
|
+
@stopped = true
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'active_support/core_ext/class'
|
3
|
+
|
4
|
+
module Resque
|
5
|
+
module Durable
|
6
|
+
class QueueAudit < ActiveRecord::Base
|
7
|
+
set_table_name :durable_queue_audits
|
8
|
+
# id
|
9
|
+
# enqueued_id
|
10
|
+
# queue_name
|
11
|
+
# payload
|
12
|
+
# enqueue_count
|
13
|
+
# enqueued_at
|
14
|
+
# completed_at
|
15
|
+
# timeout_at
|
16
|
+
# updated_at
|
17
|
+
# created_at
|
18
|
+
DEFAULT_DURATION = 10.minutes
|
19
|
+
|
20
|
+
validates_length_of :payload_before_type_cast, :in => 1..5000
|
21
|
+
|
22
|
+
validates_inclusion_of :duration, :in => 1.minute..3.hours
|
23
|
+
|
24
|
+
named_scope :older_than, lambda { |date|
|
25
|
+
{ :conditions => [ 'created_at < ?', date ] }
|
26
|
+
}
|
27
|
+
|
28
|
+
named_scope :failed, lambda {
|
29
|
+
{ :conditions => [ 'completed_at is null AND timeout_at < ?', Time.now.utc ], :order => 'timeout_at asc' }
|
30
|
+
}
|
31
|
+
|
32
|
+
named_scope :complete, lambda {
|
33
|
+
{ :conditions => 'completed_at is not null' }
|
34
|
+
}
|
35
|
+
|
36
|
+
module Recovery
|
37
|
+
|
38
|
+
def recover
|
39
|
+
failed.all(:limit => 500).each { |audit| audit.enqueue if audit.retryable? }
|
40
|
+
end
|
41
|
+
|
42
|
+
def cleanup(date)
|
43
|
+
older_than(date).destroy_all
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
extend Recovery
|
48
|
+
|
49
|
+
|
50
|
+
def self.initialize_by_klass_and_args(job_klass, args)
|
51
|
+
new(:job_klass => job_klass, :payload => args, :enqueued_id => GUID.generate)
|
52
|
+
end
|
53
|
+
|
54
|
+
def job_klass
|
55
|
+
read_attribute(:job_klass).constantize
|
56
|
+
end
|
57
|
+
|
58
|
+
def job_klass=(klass)
|
59
|
+
write_attribute(:job_klass, klass.to_s)
|
60
|
+
end
|
61
|
+
|
62
|
+
def payload
|
63
|
+
ActiveSupport::JSON.decode(super)
|
64
|
+
end
|
65
|
+
|
66
|
+
def payload=(value)
|
67
|
+
super value.to_json
|
68
|
+
end
|
69
|
+
|
70
|
+
def queue
|
71
|
+
Resque.queue_from_class(job_klass)
|
72
|
+
end
|
73
|
+
|
74
|
+
def enqueue
|
75
|
+
job_klass.enqueue(*(payload.push(self)))
|
76
|
+
end
|
77
|
+
|
78
|
+
def duration
|
79
|
+
job_klass.job_timeout
|
80
|
+
end
|
81
|
+
|
82
|
+
def heartbeat!
|
83
|
+
update_attribute(:timeout_at, Time.now.utc + duration)
|
84
|
+
end
|
85
|
+
|
86
|
+
def fail!
|
87
|
+
update_attribute(:timeout_at, Time.now.utc)
|
88
|
+
end
|
89
|
+
|
90
|
+
def enqueued!
|
91
|
+
self.enqueued_at = Time.now.utc
|
92
|
+
self.timeout_at = enqueued_at + duration
|
93
|
+
self.enqueue_count += 1
|
94
|
+
save!
|
95
|
+
end
|
96
|
+
|
97
|
+
def complete!
|
98
|
+
self.completed_at = Time.now.utc
|
99
|
+
save!
|
100
|
+
end
|
101
|
+
|
102
|
+
def complete?
|
103
|
+
completed_at.present?
|
104
|
+
end
|
105
|
+
|
106
|
+
def retryable?
|
107
|
+
Time.now.utc > (timeout_at + delay)
|
108
|
+
end
|
109
|
+
|
110
|
+
# 1, 8, 27, 64, 125, 216, etc. minutes.
|
111
|
+
def delay
|
112
|
+
(enqueue_count ** 3).minutes
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Resque
|
2
|
+
module Durable
|
3
|
+
autoload :GUID, 'resque/durable/guid'
|
4
|
+
autoload :Monitor, 'resque/durable/monitor'
|
5
|
+
autoload :QueueAudit, 'resque/durable/queue_audit'
|
6
|
+
|
7
|
+
def self.extended(base)
|
8
|
+
base.cattr_accessor :job_timeout
|
9
|
+
base.job_timeout = 10.minutes
|
10
|
+
|
11
|
+
base.cattr_accessor :auditor
|
12
|
+
base.auditor = QueueAudit
|
13
|
+
end
|
14
|
+
|
15
|
+
def enqueue(*args)
|
16
|
+
if args.last.is_a?(auditor)
|
17
|
+
# the audit-is-re-enqueing case
|
18
|
+
audit = args.pop
|
19
|
+
else
|
20
|
+
audit = build_audit(args)
|
21
|
+
end
|
22
|
+
|
23
|
+
args << audit.enqueued_id
|
24
|
+
begin
|
25
|
+
audit.enqueued!
|
26
|
+
rescue Exception => e
|
27
|
+
audit_failed(e)
|
28
|
+
end
|
29
|
+
|
30
|
+
Resque.enqueue(self, *args)
|
31
|
+
rescue Exception => e
|
32
|
+
enqueue_failed(e)
|
33
|
+
end
|
34
|
+
|
35
|
+
def audit(args)
|
36
|
+
audit = auditor.find_by_enqueued_id(args.last)
|
37
|
+
audit_failed(ArgumentError.new("Could not find audit: #{args.last}")) if audit.nil?
|
38
|
+
audit
|
39
|
+
end
|
40
|
+
|
41
|
+
def heartbeat(args)
|
42
|
+
if a = audit(args)
|
43
|
+
a.heartbeat!
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def around_perform_manage_audit(*args)
|
48
|
+
if a = audit(args)
|
49
|
+
a.heartbeat!
|
50
|
+
return if a.complete?
|
51
|
+
yield
|
52
|
+
a.complete!
|
53
|
+
else
|
54
|
+
yield
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def on_failure_set_timeout(exception, *args)
|
59
|
+
if a = audit(args)
|
60
|
+
a.fail!
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def build_audit(args)
|
65
|
+
auditor.initialize_by_klass_and_args(self, args)
|
66
|
+
end
|
67
|
+
|
68
|
+
def audit_failed(e)
|
69
|
+
raise e
|
70
|
+
end
|
71
|
+
|
72
|
+
def enqueue_failed(e)
|
73
|
+
raise e
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: resque-durable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 21
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 2
|
9
|
-
-
|
10
|
-
version: 0.2.
|
9
|
+
- 1
|
10
|
+
version: 0.2.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Eric Chapweske
|
@@ -16,7 +16,7 @@ autorequire:
|
|
16
16
|
bindir: bin
|
17
17
|
cert_chain: []
|
18
18
|
|
19
|
-
date: 2012-04-
|
19
|
+
date: 2012-04-16 00:00:00 Z
|
20
20
|
dependencies: []
|
21
21
|
|
22
22
|
description:
|
@@ -27,8 +27,11 @@ extensions: []
|
|
27
27
|
|
28
28
|
extra_rdoc_files: []
|
29
29
|
|
30
|
-
files:
|
31
|
-
|
30
|
+
files:
|
31
|
+
- lib/resque/durable.rb
|
32
|
+
- lib/resque/durable/guid.rb
|
33
|
+
- lib/resque/durable/monitor.rb
|
34
|
+
- lib/resque/durable/queue_audit.rb
|
32
35
|
homepage:
|
33
36
|
licenses: []
|
34
37
|
|