resque-durable 1.2.0 → 2.0.0

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 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