resque-durable 1.2.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 29b76c4ad5376048fd49658063b16f73acd87d54
4
- data.tar.gz: 30820b659c590e531c89ba844bec65e1eab92065
2
+ SHA256:
3
+ metadata.gz: a514c11d6f3655311d22c6d055a243f5cc22a8ec26d7ade6880dd1a6c05ca000
4
+ data.tar.gz: 5d005e32eeb791e0a4bae2a275f146eac208e6848f0cb013ce5bbd020168ad0e
5
5
  SHA512:
6
- metadata.gz: 0cff225b7f9ac693baf965e14d52177d5a7ad9d2b67e04e11656ba4295c91519bbc84e26bd425a92730bc16a27903096e50830efd9c3095564ba6e658fc76424
7
- data.tar.gz: 1b1fc8e0d1d2713e39b43a26ed86ccd6a368ab13508df6dfe895013d74a1938d210c24154513d1239bdf99adf59578ecb1cc0afec1f30900f2f480a7ebeceb36
6
+ metadata.gz: 62aeda110d3e195e18285692a57b4519be97ce777e8b2d8144b3ec21e356ec75173e55b9570961669c818791521af92b1117f93791915cbdb181465cee3eedae
7
+ data.tar.gz: dbb59ca8dc5e86a31059625930028df2e6cd81535e715c39078c6599b2d5ee8c2cd3747cd7c82eac3c2ef60fdfb825119d8920ac9bc29a4aad024f1fac4b6e42
@@ -3,11 +3,20 @@ module Resque
3
3
  autoload :GUID, 'resque/durable/guid'
4
4
  autoload :Monitor, 'resque/durable/monitor'
5
5
  autoload :QueueAudit, 'resque/durable/queue_audit'
6
+ autoload :BackgroundHeartbeat, 'resque/durable/background_heartbeat'
6
7
 
7
8
  def self.extended(base)
9
+ # The duration since the last heartbeat that the monitor will wait before
10
+ # re-enqueing the job.
8
11
  base.cattr_accessor :job_timeout
9
12
  base.job_timeout = 10.minutes
10
13
 
14
+ # How frequently a background thread will optimistically heartbeat the
15
+ # QueueAudit. Value must be smaller than job_timeout. Currently opt-in.
16
+ #
17
+ # Recommended value: `15.seconds`
18
+ base.cattr_accessor :background_heartbeat_interval
19
+
11
20
  base.cattr_accessor :auditor
12
21
  base.auditor = QueueAudit
13
22
  end
@@ -46,9 +55,17 @@ module Resque
46
55
 
47
56
  def around_perform_manage_audit(*args)
48
57
  if a = audit(args)
49
- a.heartbeat!
50
58
  return if a.complete?
51
- yield
59
+ if background_heartbeat_interval
60
+ raise "background_heartbeat_interval (#{background_heartbeat_interval.inspect}) be smaller than job_timeout (#{job_timeout.inspect})" if background_heartbeat_interval >= job_timeout
61
+ BackgroundHeartbeat.new(audit(args), background_heartbeat_interval).with_heartbeat do
62
+ yield
63
+ end
64
+ else
65
+ a.heartbeat!
66
+ yield
67
+ end
68
+
52
69
  a.complete!
53
70
  else
54
71
  yield
@@ -0,0 +1,82 @@
1
+ require 'thread'
2
+
3
+ module Resque
4
+ module Durable
5
+
6
+ # Creates a background thread to regularly heartbeat the queue audit.
7
+ class BackgroundHeartbeat
8
+
9
+ def initialize(queue_audit, interval)
10
+ @queue_audit = queue_audit
11
+ @last_timeout = nil
12
+ @interval = interval
13
+ @mutex = Mutex.new
14
+ @stop = false
15
+ @thread = nil
16
+ end
17
+
18
+ class << self
19
+ # only a separate method for easy stubbing
20
+ def exit_now!
21
+ abort
22
+ end
23
+ end
24
+
25
+ def with_heartbeat
26
+ start!
27
+ yield
28
+ ensure
29
+ stop_and_wait!
30
+ end
31
+
32
+ def heartbeat!
33
+ @last_timeout ||= @queue_audit.timeout_at
34
+ @last_timeout = @queue_audit.optimistic_heartbeat!(@last_timeout)
35
+ rescue StandardError => e
36
+ @queue_audit.logger.error("Exception in BackgroundHeartbeat thread: #{e.class.name}: #{e.message}")
37
+ self.class.exit_now!
38
+ ensure
39
+ ActiveRecord::Base.clear_active_connections!
40
+ end
41
+
42
+ def start!
43
+ raise "Thread is already running!" if @thread
44
+ @stop = false
45
+
46
+ # Perform immediately to reduce heartbeat race condition opportunities
47
+ heartbeat!
48
+
49
+ @thread = Thread.new do
50
+ while !@stop
51
+ heartbeat!
52
+
53
+ @mutex.synchronize do
54
+ @mutex.sleep(@interval)
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ def stop_and_wait!
61
+ return unless @thread
62
+ # Prevent deadlock if called by the `heartbeat` thread, which can't wait for itself to die.
63
+ return signal_stop! if @thread == Thread.current
64
+ while @thread.alive?
65
+ signal_stop!
66
+ sleep 0.01
67
+ end
68
+ @thread.join
69
+ @thread = nil
70
+ end
71
+
72
+ # Signal the `heartbeat` thread to stop looping immediately. Safe to be call from any thread.
73
+ def signal_stop!
74
+ return unless @thread
75
+ @mutex.synchronize do
76
+ @stop = true
77
+ @thread.wakeup
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -1,9 +1,11 @@
1
1
  require 'active_record'
2
- require 'active_support/core_ext/class'
2
+ require 'active_support/core_ext'
3
3
 
4
4
  module Resque
5
5
  module Durable
6
6
  class QueueAudit < ActiveRecord::Base
7
+ JobCollision = Class.new(StandardError)
8
+
7
9
  self.table_name = :durable_queue_audits
8
10
  # id
9
11
  # enqueued_id
@@ -19,19 +21,18 @@ module Resque
19
21
 
20
22
  validates_length_of :payload_before_type_cast, :in => 1..5000
21
23
 
22
- validates_inclusion_of :duration, :in => 1.minute..3.hours
24
+ validates_inclusion_of :duration, :in => 1.minute.to_i..3.hours.to_i
23
25
 
24
- scope :older_than, lambda { |date|
25
- { :conditions => [ 'created_at < ?', date ] }
26
- }
26
+ scope :older_than, ->(date) { where('created_at < ?', date) }
27
27
 
28
- scope :failed, lambda {
29
- { :conditions => [ 'completed_at is null AND timeout_at < ?', Time.now.utc ], :order => 'timeout_at asc', :limit => 500 }
28
+ scope :failed, -> {
29
+ where(completed_at: nil)
30
+ .where('timeout_at < ?', Time.now.utc)
31
+ .order('timeout_at asc')
32
+ .limit(500)
30
33
  }
31
34
 
32
- scope :complete, lambda {
33
- { :conditions => 'completed_at is not null' }
34
- }
35
+ scope :complete, -> { where('completed_at is not null') }
35
36
 
36
37
  module Recovery
37
38
 
@@ -89,6 +90,18 @@ module Resque
89
90
  update_attribute(:timeout_at, Time.now.utc + duration)
90
91
  end
91
92
 
93
+ # Bumps the `timeout_at` column, but raises a `JobCollision` exception if
94
+ # another process has changed the value, indicating we may have multiple
95
+ # workers processing the same job.
96
+ def optimistic_heartbeat!(last_timeout_at)
97
+ next_timeout_at = Time.now.utc + duration
98
+ nrows = self.class.
99
+ where(id: id, timeout_at: last_timeout_at).
100
+ update_all(timeout_at: next_timeout_at)
101
+ raise JobCollision.new unless nrows == 1
102
+ next_timeout_at
103
+ end
104
+
92
105
  def fail!
93
106
  update_attribute(:timeout_at, Time.now.utc)
94
107
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: resque-durable
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Chapweske
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-09-09 00:00:00.000000000 Z
12
+ date: 2020-01-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
@@ -17,14 +17,14 @@ dependencies:
17
17
  requirements:
18
18
  - - ">="
19
19
  - !ruby/object:Gem::Version
20
- version: '0'
20
+ version: '4.2'
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - ">="
26
26
  - !ruby/object:Gem::Version
27
- version: '0'
27
+ version: '4.2'
28
28
  description:
29
29
  email:
30
30
  executables: []
@@ -32,6 +32,7 @@ extensions: []
32
32
  extra_rdoc_files: []
33
33
  files:
34
34
  - lib/resque/durable.rb
35
+ - lib/resque/durable/background_heartbeat.rb
35
36
  - lib/resque/durable/guid.rb
36
37
  - lib/resque/durable/monitor.rb
37
38
  - lib/resque/durable/queue_audit.rb
@@ -47,15 +48,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
47
48
  requirements:
48
49
  - - ">="
49
50
  - !ruby/object:Gem::Version
50
- version: '0'
51
+ version: '2.4'
51
52
  required_rubygems_version: !ruby/object:Gem::Requirement
52
53
  requirements:
53
54
  - - ">="
54
55
  - !ruby/object:Gem::Version
55
56
  version: '0'
56
57
  requirements: []
57
- rubyforge_project:
58
- rubygems_version: 2.2.2
58
+ rubygems_version: 3.0.3
59
59
  signing_key:
60
60
  specification_version: 4
61
61
  summary: Resque queue backed by database audits, with automatic retry