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 +4 -4
- data/config/locales/en.yml +2 -0
- data/lib/dalliance.rb +46 -4
- data/lib/dalliance/version.rb +2 -2
- data/lib/dalliance/workers/delayed_job.rb +8 -0
- data/lib/dalliance/workers/resque.rb +34 -0
- data/spec/dalliance/asynchronous_resque_spec.rb +64 -0
- data/spec/dalliance/dalliance_spec.rb +5 -3
- data/spec/dalliance/synchronous_spec.rb +4 -2
- data/spec/support/active_record.rb +7 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ceb1bcc758ad39970b608af9112dfb735f7408edc7d97caf818654d5fca558db
|
4
|
+
data.tar.gz: eed4e050d077b5cdf74b8d8997e917423229199904740a06028e2d3daca89c66
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9bbddd1d2685d64b7a2ccd0d5d610f5d7d44d249ef59d0c0d76198e4c0733340dc89dc506b9c084c1f667ec7370830560854575b1dc0079d63a16cc630ba0093
|
7
|
+
data.tar.gz: f60a961960220bfe1973d12a1c4440bd87d1ae6d2cbfd71c57e2308d66e58e0887a5aee8513bde25316bb50765d12dbb4d42105b04d3d8dedab4465e7a63634b
|
data/config/locales/en.yml
CHANGED
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,
|
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}
|
data/lib/dalliance/version.rb
CHANGED
@@ -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
|
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])
|
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])
|
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.
|
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-
|
11
|
+
date: 2021-06-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|