coil 1.6.1 → 1.7.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/CHANGELOG.md +5 -0
- data/app/jobs/coil/transactional_messages_job.rb +29 -9
- data/app/jobs/coil/transactional_messages_periodic_job.rb +17 -5
- data/db/migrate/20260314134950_add_processor_attempts_to_inbox_messages.rb +18 -0
- data/db/migrate/20260314162532_add_processor_attempts_to_outbox_messages.rb +18 -0
- data/lib/coil/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4d8cfb14efec96e90e76ae65f4dc955cbfc6096dd2769bffd96cec917170cc7f
|
|
4
|
+
data.tar.gz: 412d34f5d3692749ac166b81a3560a2c2c95b626b4aee8aa8e65cc44c264b4ba
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 30330b66219bccd62fbceef0716efc568540ca432464c94b81b3b09e0bc4d216e41a9e8e6c4e3e9bc46ad0de6af337e382f57cd34872990895d18d14ecbc7424
|
|
7
|
+
data.tar.gz: a89bf2e70d720a95d47e6157567e469374d53be4e580603aed02bae0ed7f70a28bae5b3270d7c1628b253eff1c44e4a02dc88d256cf97a8b7acab10972376d58
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
## main
|
|
2
2
|
|
|
3
|
+
## [1.7.0](https://github.com/OdekoTeam/coil/compare/1.6.1...1.7.0) (2026-03-16)
|
|
4
|
+
#### Changed
|
|
5
|
+
- Improve retry policy ([d51ea55](https://github.com/OdekoTeam/coil/commit/d51ea553eaa41cb99a4da70a0316707780f1a79d))
|
|
6
|
+
- **NOTE**: this release includes additional migrations, so **be sure to run** `bundle exec rails coil:install:migrations db:migrate`
|
|
7
|
+
|
|
3
8
|
## [1.6.1](https://github.com/OdekoTeam/coil/compare/1.6.0...1.6.1) (2026-03-16)
|
|
4
9
|
#### Fix
|
|
5
10
|
- Fix migration version ([63866a2](https://github.com/OdekoTeam/coil/commit/63866a21d0d54080b2ef8a37046237524b281089))
|
|
@@ -10,8 +10,6 @@ module Coil
|
|
|
10
10
|
# job to pick up where we left off.
|
|
11
11
|
MAX_DURATION = 5.minutes
|
|
12
12
|
|
|
13
|
-
sidekiq_options retry: 4, dead: false
|
|
14
|
-
|
|
15
13
|
def perform(key, processor_name = self.class.to_s)
|
|
16
14
|
deadline = Time.current + MAX_DURATION
|
|
17
15
|
next_in_line = process_messages(key:, processor_name:, deadline:)
|
|
@@ -38,18 +36,40 @@ module Coil
|
|
|
38
36
|
next_in_line = next_message(key:, processor_name:)
|
|
39
37
|
return next_in_line if next_in_line.nil? || Time.current > deadline
|
|
40
38
|
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
outcome = process_next(key:, processor_name:)
|
|
40
|
+
raise outcome.error if outcome.error
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class Outcome
|
|
45
|
+
attr_accessor :error
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def process_next(key:, processor_name:)
|
|
49
|
+
outcome = Outcome.new
|
|
43
50
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
locking(key) do
|
|
52
|
+
message = next_message(key:, processor_name:)
|
|
53
|
+
|
|
54
|
+
if message.present?
|
|
55
|
+
message.increment!(:processor_attempts)
|
|
56
|
+
begin
|
|
57
|
+
# Use a nested transaction and deferred error-handling to ensure the
|
|
58
|
+
# processor_attempts increment persists, even if the attempt fails.
|
|
59
|
+
ApplicationRecord.transaction(requires_new: true) do
|
|
60
|
+
around_process(message, processor_name:) do
|
|
61
|
+
pre_process(message)
|
|
62
|
+
message.processed(processor_name:)
|
|
63
|
+
process(message)
|
|
64
|
+
end
|
|
49
65
|
end
|
|
66
|
+
rescue => e
|
|
67
|
+
outcome.error = e
|
|
50
68
|
end
|
|
51
69
|
end
|
|
52
70
|
end
|
|
71
|
+
|
|
72
|
+
outcome
|
|
53
73
|
end
|
|
54
74
|
|
|
55
75
|
def around_process(message, processor_name:, &blk)
|
|
@@ -1,17 +1,28 @@
|
|
|
1
1
|
# typed: strict
|
|
2
2
|
|
|
3
|
+
require "sidekiq/api"
|
|
4
|
+
|
|
3
5
|
# The periodic job acts as a fallback mechanism, polling for messages that were
|
|
4
6
|
# not enqueued and processed automatically upon create, e.g. due to a failure to
|
|
5
7
|
# push a job onto the Redis queue.
|
|
6
8
|
module Coil
|
|
7
9
|
class TransactionalMessagesPeriodicJob < ApplicationJob
|
|
10
|
+
ATTEMPTS_THRESHOLD = 3
|
|
11
|
+
|
|
8
12
|
def perform
|
|
9
|
-
|
|
13
|
+
q = Sidekiq::Queue.new(Coil.sidekiq_queue)
|
|
14
|
+
t = Time.current - q.latency - TransactionalMessagesJob::MAX_DURATION
|
|
10
15
|
|
|
11
16
|
# Identify distinct message types, their associated job types, and
|
|
12
|
-
# the distinct keys for which we have unprocessed messages
|
|
13
|
-
#
|
|
14
|
-
#
|
|
17
|
+
# the distinct keys for which we have unprocessed messages.
|
|
18
|
+
#
|
|
19
|
+
# Exclude very recent messages, since a TransactionalMessagesJob could
|
|
20
|
+
# still be processing those.
|
|
21
|
+
#
|
|
22
|
+
# Exclude keys where a processor has already initiated several attempts,
|
|
23
|
+
# since that's a strong indicator that automatic retries are in play.
|
|
24
|
+
#
|
|
25
|
+
# Then, enqueue the appropriate jobs.
|
|
15
26
|
message_parent_class.select(:type).distinct.pluck(:type).each do |type|
|
|
16
27
|
message_class = message_class_for(type)
|
|
17
28
|
next unless message_class.present?
|
|
@@ -20,7 +31,8 @@ module Coil
|
|
|
20
31
|
message_class
|
|
21
32
|
.unprocessed(processor_name: job_class.name)
|
|
22
33
|
.where(created_at: nil...t)
|
|
23
|
-
.
|
|
34
|
+
.group(:key)
|
|
35
|
+
.having("MAX(processor_attempts) < ?", ATTEMPTS_THRESHOLD)
|
|
24
36
|
.pluck(:key)
|
|
25
37
|
.each { |k| job_class.perform_async(k) }
|
|
26
38
|
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# typed: false
|
|
2
|
+
|
|
3
|
+
class AddProcessorAttemptsToInboxMessages < ActiveRecord::Migration[6.0]
|
|
4
|
+
def change
|
|
5
|
+
comment = <<~DOC.squish
|
|
6
|
+
Number of processor attempts that have been initiated on this message.
|
|
7
|
+
DOC
|
|
8
|
+
|
|
9
|
+
add_column(
|
|
10
|
+
:coil_inbox_messages,
|
|
11
|
+
:processor_attempts,
|
|
12
|
+
:integer,
|
|
13
|
+
null: false,
|
|
14
|
+
default: 0,
|
|
15
|
+
comment:
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# typed: false
|
|
2
|
+
|
|
3
|
+
class AddProcessorAttemptsToOutboxMessages < ActiveRecord::Migration[6.0]
|
|
4
|
+
def change
|
|
5
|
+
comment = <<~DOC.squish
|
|
6
|
+
Number of processor attempts that have been initiated on this message.
|
|
7
|
+
DOC
|
|
8
|
+
|
|
9
|
+
add_column(
|
|
10
|
+
:coil_outbox_messages,
|
|
11
|
+
:processor_attempts,
|
|
12
|
+
:integer,
|
|
13
|
+
null: false,
|
|
14
|
+
default: 0,
|
|
15
|
+
comment:
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
end
|
data/lib/coil/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: coil
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ivan Brennan
|
|
@@ -262,6 +262,8 @@ files:
|
|
|
262
262
|
- db/migrate/20250102041225_remove_old_foreign_key_from_outbox_completions.rb
|
|
263
263
|
- db/migrate/20260309171521_index_inbox_messages_on_type.rb
|
|
264
264
|
- db/migrate/20260309173914_index_outbox_messages_on_type.rb
|
|
265
|
+
- db/migrate/20260314134950_add_processor_attempts_to_inbox_messages.rb
|
|
266
|
+
- db/migrate/20260314162532_add_processor_attempts_to_outbox_messages.rb
|
|
265
267
|
- lib/coil.rb
|
|
266
268
|
- lib/coil/engine.rb
|
|
267
269
|
- lib/coil/queue_locking.rb
|