dalliance 0.8.2 → 0.9.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
2
  SHA256:
3
- metadata.gz: 287e36b49931e0fdf7ccf431d6d9358c6802d3020d7b9bbbf507600ef2eb21b3
4
- data.tar.gz: 2fec96937b717e8feadc5a3ac946a5ef3907a23527ca1ecb2ba30ad89be067eb
3
+ metadata.gz: ceb1bcc758ad39970b608af9112dfb735f7408edc7d97caf818654d5fca558db
4
+ data.tar.gz: eed4e050d077b5cdf74b8d8997e917423229199904740a06028e2d3daca89c66
5
5
  SHA512:
6
- metadata.gz: 9c04f97d5a480e7a5c49b9c6d0f9a52d81acd7643d2654a9b9934f62ebd7f6907545d0e2f71aa9f2a133d43bc4f49ef6c794eccd37f9b1dec5448ff7a458d59a
7
- data.tar.gz: 3f4f2f7d6ab8721726b88232e6fa4d8282ba8ccb15544a4c765a062bb01d78a7d35b45e42a7e2bbd5d0a4efd1990cdf26ff1a894a28bc5ac7af77beabfe84566
6
+ metadata.gz: 9bbddd1d2685d64b7a2ccd0d5d610f5d7d44d249ef59d0c0d76198e4c0733340dc89dc506b9c084c1f667ec7370830560854575b1dc0079d63a16cc630ba0093
7
+ data.tar.gz: f60a961960220bfe1973d12a1c4440bd87d1ae6d2cbfd71c57e2308d66e58e0887a5aee8513bde25316bb50765d12dbb4d42105b04d3d8dedab4465e7a63634b
@@ -8,6 +8,8 @@ en:
8
8
  validation_error: Validation Error
9
9
  processing_error: Processing Error
10
10
  completed: Completed
11
+ cancel_requested: Cancellation Requested
12
+ cancelled: Cancelled
11
13
  attributes:
12
14
  dalliance_status: Status
13
15
  dalliance_error_hash: Errors
data/lib/dalliance.rb CHANGED
@@ -91,6 +91,8 @@ module Dalliance
91
91
  scope :validation_error, -> { where(:dalliance_status => 'validation_error') }
92
92
  scope :processing_error, -> { where(:dalliance_status => 'processing_error') }
93
93
  scope :completed, -> { where(:dalliance_status => 'completed') }
94
+ scope :cancel_requested, -> { where(:dalliance_status => 'cancel_requested') }
95
+ scope :cancelled, -> { where(:dalliance_status => 'cancelled') }
94
96
 
95
97
  state_machine :dalliance_status, :initial => :pending do
96
98
  state :pending
@@ -98,6 +100,8 @@ module Dalliance
98
100
  state :validation_error
99
101
  state :processing_error
100
102
  state :completed
103
+ state :cancel_requested
104
+ state :cancelled
101
105
 
102
106
  #event :queue_dalliance do
103
107
  # transition :processing_error => :pending
@@ -116,12 +120,23 @@ module Dalliance
116
120
  end
117
121
 
118
122
  event :finish_dalliance do
119
- transition :processing => :completed
123
+ transition [:processing, :cancel_requested] => :completed
120
124
  end
121
125
 
122
126
  event :reprocess_dalliance do
123
127
  transition [:validation_error, :processing_error, :completed] => :pending
124
128
  end
129
+
130
+ # Requests the record to stop processing. This does NOT cause processing
131
+ # to stop! Each model is required to handle cancellation on its own by
132
+ # periodically checking the dalliance status
133
+ event :request_cancel_dalliance do
134
+ transition [:pending, :processing] => :cancel_requested
135
+ end
136
+
137
+ event :cancelled_dalliance do
138
+ transition [:cancel_requested] => :cancelled
139
+ end
125
140
  end
126
141
  #END state_machine(s)
127
142
 
@@ -191,12 +206,34 @@ module Dalliance
191
206
  end
192
207
 
193
208
  def error_or_completed?
194
- validation_error? || processing_error? || completed?
209
+ validation_error? || processing_error? || completed? || cancelled?
210
+ end
211
+
212
+ # Cancels the job and removes it from the queue if has not already been taken
213
+ # by a worker. If the job is processing, it is up to the job implementation
214
+ # to stop and do any necessary cleanup. If the job does not honor the
215
+ # cancellation request, it will finish processing as normal and finish with a
216
+ # dalliance_status of 'completed'.
217
+ #
218
+ # Jobs can currently only be removed from Resque queues. DelayedJob jobs will
219
+ # not be dequeued, but will immediately exit once taken by a worker.
220
+ def cancel_and_dequeue_dalliance!
221
+ should_dequeue = pending?
222
+
223
+ request_cancel_dalliance!
224
+
225
+ if should_dequeue
226
+ self.dalliance_options[:worker_class].dequeue(self)
227
+ dalliance_log("[dalliance] #{self.class.name}(#{id}) - #{dalliance_status} - Removed from #{processing_queue} queue")
228
+ cancelled_dalliance!
229
+ end
230
+
231
+ true
195
232
  end
196
233
 
197
234
  def validate_dalliance_status
198
235
  unless error_or_completed?
199
- errors.add(:dalliance_status, :invalid)
236
+ errors.add(:dalliance_status, "Processing must be finished or cancelled, but status is '#{dalliance_status}'")
200
237
  if defined?(Rails)
201
238
  throw(:abort)
202
239
  else
@@ -254,6 +291,11 @@ module Dalliance
254
291
  end
255
292
 
256
293
  def do_dalliance_process(perform_method:, background_processing: false)
294
+ # The job might have been cancelled after it was queued, but before
295
+ # processing started. Check for that up front before doing any processing.
296
+ cancelled_dalliance! if cancel_requested?
297
+ return if cancelled? # method generated from AASM
298
+
257
299
  start_time = Time.now
258
300
 
259
301
  begin
@@ -265,7 +307,7 @@ module Dalliance
265
307
 
266
308
  self.send(perform_method)
267
309
 
268
- finish_dalliance! unless validation_error?
310
+ finish_dalliance! unless validation_error? || cancelled?
269
311
  rescue StandardError => e
270
312
  #Save the error for future analysis...
271
313
  self.dalliance_error_hash = {:error => e.class.name, :message => e.message, :backtrace => e.backtrace}
@@ -1,8 +1,8 @@
1
1
  module Dalliance
2
2
  module VERSION
3
3
  MAJOR = 0
4
- MINOR = 8
5
- TINY = 2
4
+ MINOR = 9
5
+ TINY = 0
6
6
  PRE = nil
7
7
 
8
8
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
@@ -10,6 +10,10 @@ module Dalliance
10
10
  .perform_later(instance.class.name, instance.id, perform_method.to_s)
11
11
  end
12
12
 
13
+ def self.dequeue(_instance)
14
+ # NOP
15
+ end
16
+
13
17
  def perform(instance_klass, instance_id, perform_method)
14
18
  instance_klass
15
19
  .constantize
@@ -31,6 +35,10 @@ module Dalliance
31
35
  )
32
36
  end
33
37
 
38
+ def self.dequeue(_instance)
39
+ # NOP
40
+ end
41
+
34
42
  def perform
35
43
  instance_klass
36
44
  .constantize
@@ -10,6 +10,23 @@ module Dalliance
10
10
  .perform_later(instance.class.name, instance.id, perform_method.to_s)
11
11
  end
12
12
 
13
+ def self.dequeue(instance)
14
+ redis = ::Resque.redis
15
+ queue = instance.processing_queue
16
+
17
+ redis.everything_in_queue(queue).each do |string|
18
+ # Structure looks like, e.g.
19
+ # { 'class' => 'ActiveJob::...', 'args' => [{ 'arguments' => ['SomeClass', 123, 'dalliance_process'] }] }
20
+ data = ::Resque.decode(string)
21
+ dalliance_args = data['args'][0]['arguments']
22
+
23
+ if dalliance_args == [instance.class.name, instance.id, 'dalliance_process'] ||
24
+ dalliance_args == [instance.class.name, instance.id, 'dalliance_reprocess']
25
+ redis.remove_from_queue(queue, string)
26
+ end
27
+ end
28
+ end
29
+
13
30
  def perform(instance_klass, instance_id, perform_method)
14
31
  instance_klass
15
32
  .constantize
@@ -28,6 +45,23 @@ module Dalliance
28
45
  ::Resque.enqueue_to(queue, self, instance.class.name, instance.id, perform_method.to_s)
29
46
  end
30
47
 
48
+ def self.dequeue(instance)
49
+ redis = ::Resque.redis
50
+ queue = instance.processing_queue
51
+
52
+ redis.everything_in_queue(queue).each do |string|
53
+ # Structure looks like, e.g.
54
+ # { 'class' => 'ActiveJob::...', 'args' => [{ 'arguments' => ['SomeClass', 123, 'dalliance_process'] }] }
55
+ data = ::Resque.decode(string)
56
+ dalliance_args = data['args'][0]['arguments']
57
+
58
+ if dalliance_args == [instance.class.name, instance.id, 'dalliance_process'] ||
59
+ dalliance_args == [instance.class.name, instance.id, 'dalliance_reprocess']
60
+ redis.remove_from_queue(queue, string)
61
+ end
62
+ end
63
+ end
64
+
31
65
  def self.perform(instance_klass, instance_id, perform_method)
32
66
  instance_klass
33
67
  .constantize
@@ -12,6 +12,7 @@ RSpec.describe DallianceModel do
12
12
 
13
13
  before do
14
14
  Resque.remove_queue(:dalliance)
15
+ Resque.remove_queue(:notaqueue)
15
16
  end
16
17
 
17
18
  context "no worker_class" do
@@ -168,6 +169,69 @@ RSpec.describe DallianceModel do
168
169
  end
169
170
  end
170
171
 
172
+ context 'cancelling' do
173
+ before(:all) do
174
+ DallianceModel.dalliance_options[:dalliance_method] = :dalliance_success_method
175
+ DallianceModel.dalliance_options[:worker_class] = Dalliance::Workers::Resque
176
+ DallianceModel.dalliance_options[:queue] = 'dalliance'
177
+ end
178
+
179
+ it 'can be cancelled after being queued' do
180
+ subject.dalliance_background_process
181
+ expect { subject.request_cancel_dalliance! and subject.cancelled_dalliance! }
182
+ .to change { subject.dalliance_status }
183
+ .from('pending')
184
+ .to('cancelled')
185
+ end
186
+
187
+ it 'dequeues the job' do
188
+ expect { subject.dalliance_background_process }
189
+ .to change { Resque.size('dalliance') }
190
+ .from(0)
191
+ .to(1)
192
+
193
+ expect { subject.cancel_and_dequeue_dalliance! }
194
+ .to change { Resque.size('dalliance') }
195
+ .from(1)
196
+ .to(0)
197
+ .and change { subject.dalliance_status }
198
+ .from('pending')
199
+ .to('cancelled')
200
+ end
201
+
202
+ it 'sets dalliance_status to "cancelled" if cancellation was requested' do
203
+ subject.dalliance_background_process
204
+ subject.request_cancel_dalliance!
205
+
206
+ Resque::Worker.new(:dalliance).process
207
+ subject.reload
208
+
209
+ expect(subject.dalliance_status).to eq 'cancelled'
210
+ end
211
+
212
+ it 'runs normally if the job does not honor the cancellation request' do
213
+ DallianceModel.dalliance_options[:dalliance_method] = :dalliance_ignore_cancellation_method
214
+
215
+ subject.dalliance_background_process
216
+
217
+ Resque::Worker.new(:dalliance).process
218
+ subject.reload
219
+
220
+ expect(subject.successful).to eq true
221
+ expect(subject.dalliance_status).to eq 'completed'
222
+ end
223
+
224
+ it 'does not process' do
225
+ subject.request_cancel_dalliance!
226
+ subject.dalliance_background_process
227
+
228
+ Resque::Worker.new(:dalliance).process
229
+ subject.reload
230
+
231
+ expect(subject.successful).to eq false
232
+ end
233
+ end
234
+
171
235
  context "raise error" do
172
236
  before(:all) do
173
237
  DallianceModel.dalliance_options[:dalliance_method] = :dalliance_error_method
@@ -9,13 +9,15 @@ RSpec.describe 'Dalliance' do
9
9
 
10
10
  context "self#dalliance_status_in_load_select_array" do
11
11
  it "should return [state, human_name]" do
12
- expect(DallianceModel.dalliance_status_in_load_select_array).to eq([
12
+ expect(DallianceModel.dalliance_status_in_load_select_array).to contain_exactly(
13
13
  ["Completed", "completed"],
14
14
  ["Pending", "pending"],
15
15
  ["Processing", "processing"],
16
16
  ["Processing Error", "processing_error"],
17
- ["Validation Error", "validation_error"]
18
- ])
17
+ ["Validation Error", "validation_error"],
18
+ ["Cancellation Requested", "cancel_requested"],
19
+ ['Cancelled', 'cancelled']
20
+ )
19
21
  end
20
22
  end
21
23
 
@@ -180,13 +180,15 @@ RSpec.describe DallianceModel do
180
180
  it "should return false when pending?" do
181
181
  subject.update_column(:dalliance_status, 'pending')
182
182
  expect(subject.destroy).to be_falsey
183
- expect(subject.errors[:dalliance_status]).to eq(['is invalid'])
183
+ expect(subject.errors[:dalliance_status])
184
+ .to eq(["Processing must be finished or cancelled, but status is 'pending'"])
184
185
  end
185
186
 
186
187
  it "should return false when processing?" do
187
188
  subject.update_column(:dalliance_status, 'processing')
188
189
  expect(subject.destroy).to be_falsey
189
- expect(subject.errors[:dalliance_status]).to eq(['is invalid'])
190
+ expect(subject.errors[:dalliance_status])
191
+ .to eq(["Processing must be finished or cancelled, but status is 'processing'"])
190
192
  end
191
193
 
192
194
  it "should return true when validation_error?" do
@@ -59,6 +59,13 @@ class DallianceModel < ActiveRecord::Base
59
59
  update_attribute(:reprocessed_count, self.reprocessed_count + 1)
60
60
  end
61
61
 
62
+ # Pretends that an external action requested processing to be cancelled, but
63
+ # ignores the request and finishes anyway.
64
+ def dalliance_ignore_cancellation_method
65
+ request_cancel_dalliance!
66
+ update_attribute(:successful, true)
67
+ end
68
+
62
69
  def dalliance_error_method
63
70
  raise RuntimeError
64
71
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dalliance
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.2
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Sullivan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-02-12 00:00:00.000000000 Z
11
+ date: 2021-06-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails