dalliance 0.8.2 → 0.9.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
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