coil 1.4.0 → 1.5.1
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 +57 -0
- data/README.md +12 -3
- data/app/jobs/coil/inbox/messages_cleanup_job.rb +18 -0
- data/app/jobs/coil/outbox/messages_cleanup_job.rb +18 -0
- data/app/jobs/coil/transactional_messages_cleanup_job.rb +106 -0
- data/app/jobs/coil/transactional_messages_periodic_job.rb +9 -3
- data/db/migrate/20250101182458_add_foreign_key_to_inbox_completions_on_delete_cascade.rb +32 -0
- data/db/migrate/20250102031753_validate_foreign_key_inbox_completions.rb +19 -0
- data/db/migrate/20250102040148_remove_old_foreign_key_from_inbox_completions.rb +24 -0
- data/db/migrate/20250102040649_add_foreign_key_to_outbox_completions_on_delete_cascade.rb +32 -0
- data/db/migrate/20250102040950_validate_foreign_key_outbox_completions.rb +19 -0
- data/db/migrate/20250102041225_remove_old_foreign_key_from_outbox_completions.rb +24 -0
- data/lib/coil/version.rb +1 -1
- data/lib/coil.rb +2 -0
- data/rbi/coil.rbi +62 -0
- metadata +12 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c1f2b39ca3971b9e5cdab940d3bdf4d337da12a50b31c6d01426a503fa1c81f1
|
4
|
+
data.tar.gz: 82d60c9a84136f3b34bfe83679510d4c6c5d20cb73bf24dd78b968bcf8900dcb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bb5f38cfe1e2a3777227e5ecca9a46775595eca487ad0ece63e2341df31e97bbaab21982ff5853b47ad777584b312200ee96369edd9a2660bbfec4704155f340
|
7
|
+
data.tar.gz: c2054aec3f317daf195e2d34ac39b9544fbd8f7713379f0fae6d70e02bb53be57e9ea34913b407839d8043bc08bc430c99b41b61b8068bb028d7915a88c47135
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
## main
|
2
|
+
|
3
|
+
## [1.5.1](https://github.com/OdekoTeam/coil/compare/1.5.0...1.5.1) (2025-01-29)
|
4
|
+
#### Fixed
|
5
|
+
- Avoid `ActiveRecord::SubclassNotFound` on orphaned messages ([ea3a64a](https://github.com/OdekoTeam/coil/commit/ea3a64acfcb9f05d53e242f96eb1b525761e414d))
|
6
|
+
|
7
|
+
## [1.5.0](https://github.com/OdekoTeam/coil/compare/1.4.0...1.5.0) (2025-01-29)
|
8
|
+
#### Added
|
9
|
+
- Periodic cleanup jobs ([0e784b3](https://github.com/OdekoTeam/coil/commit/0e784b3684b8e12677e6d05bc0a7762e254e6357))
|
10
|
+
- jobs: `Coil::Inbox::MessagesCleanupJob`, `Coil::Outbox::MessagesCleanupJob`
|
11
|
+
- configuration settings: `Coil.inbox_retention_period`, `Coil.outbox_retention_period`
|
12
|
+
- **NOTE**: this release includes additional migrations, so **be sure to run** `bundle exec rails coil:install:migrations db:migrate`
|
13
|
+
|
14
|
+
## [1.4.0](https://github.com/OdekoTeam/coil/compare/1.3.3...1.4.0) (2024-12-11)
|
15
|
+
#### Added
|
16
|
+
- `Coil.sidekiq_queue` configuration setting ([15fb072](https://github.com/OdekoTeam/coil/commit/15fb072284bad1eeb5661d3b037d3e07bb46c59a))
|
17
|
+
#### Fixed
|
18
|
+
- Redundant variable in test ([cd41472](https://github.com/OdekoTeam/coil/commit/cd414726298ce80153f508f755bedd371bbb8dab))
|
19
|
+
|
20
|
+
## [1.3.3](https://github.com/OdekoTeam/coil/compare/1.3.2...1.3.3) (2024-10-25)
|
21
|
+
#### Changed
|
22
|
+
- Documentation ([b25f00c](https://github.com/OdekoTeam/coil/commit/b25f00c19dfdb4cd3653ce715ace0be3145046e9))
|
23
|
+
|
24
|
+
## [1.3.2](https://github.com/OdekoTeam/coil/compare/1.3.1...1.3.2) (2024-10-24)
|
25
|
+
#### Fixed
|
26
|
+
- Build warnings ([f1c1145](https://github.com/OdekoTeam/coil/commit/f1c11459b5b54567c0b267ae6279f776ec33a680))
|
27
|
+
|
28
|
+
## [1.3.1](https://github.com/OdekoTeam/coil/compare/1.3.0...1.3.1) (2024-10-22)
|
29
|
+
#### Added
|
30
|
+
- CI publish to RubyGems.org ([cbc46bb](https://github.com/OdekoTeam/coil/commit/cbc46bbc153d1a7fa9d51dbf134dabe894cd594d))
|
31
|
+
|
32
|
+
## [1.3.0](https://github.com/OdekoTeam/coil/compare/1.2.1...1.3.0) (2024-10-22)
|
33
|
+
#### Added
|
34
|
+
- License ([caf8a2f](https://github.com/OdekoTeam/coil/commit/caf8a2ff68672f434eeb092e876bb6df8852b6b1))
|
35
|
+
#### Changed
|
36
|
+
- CI Docker registry ([1a2586d](https://github.com/OdekoTeam/coil/commit/1a2586db9541134c1e433d112591457a6ee9edc0))
|
37
|
+
|
38
|
+
## [1.2.1](https://github.com/OdekoTeam/coil/compare/1.2.0...1.2.1) (2024-07-01)
|
39
|
+
#### Fixed
|
40
|
+
- `next_message` race condition ([eb43a90](https://github.com/OdekoTeam/coil/commit/eb43a900c2db4300fc41c6c874aca66868039718))
|
41
|
+
|
42
|
+
## [1.2.0](https://github.com/OdekoTeam/coil/compare/1.0.1...1.2.0) (2024-06-10)
|
43
|
+
#### Added
|
44
|
+
- `around_process` job method ([46d3d5b](https://github.com/OdekoTeam/coil/commit/46d3d5b53f6dd066d5add024f39120c6140c14f3))
|
45
|
+
#### Fixed
|
46
|
+
- type aliases documentation ([d5da8d1](https://github.com/OdekoTeam/coil/commit/d5da8d13de6ac6fa9df6ba7c281db86090358f7f))
|
47
|
+
|
48
|
+
## [1.0.1](https://github.com/OdekoTeam/coil/compare/1.0.0...1.0.1) (2024-06-07)
|
49
|
+
#### Fixed
|
50
|
+
- `QueueLocking` documentation ([285a5cb](https://github.com/OdekoTeam/coil/commit/285a5cb13bffa3b824c54b59c02be2bbad93bef4))
|
51
|
+
- Support for older Rails and Sidekiq versions ([2051243](https://github.com/OdekoTeam/coil/commit/205124313b75feaf42aac10aa7455bc54a2394f0))
|
52
|
+
#### Changed
|
53
|
+
- CI Docker image ([4da1d3f](https://github.com/OdekoTeam/coil/commit/4da1d3ffdf8bb8fe03d14466ae26640159942bca))
|
54
|
+
|
55
|
+
## 1.0.0 (2024-06-06)
|
56
|
+
#### Added
|
57
|
+
- First release
|
data/README.md
CHANGED
@@ -36,10 +36,11 @@ gem "schema_version_cache"
|
|
36
36
|
|
37
37
|
Install engine and migrations:
|
38
38
|
```console
|
39
|
-
$ bundle
|
40
|
-
$ bundle exec rails coil:install:migrations
|
41
|
-
$ bundle exec rails db:migrate
|
39
|
+
$ bundle install
|
40
|
+
$ bundle exec rails coil:install:migrations db:migrate
|
42
41
|
```
|
42
|
+
(_NOTE_: Also run the above commands when upgrading, as newer versions may
|
43
|
+
introduce additional migrations.)
|
43
44
|
|
44
45
|
Register periodic jobs:
|
45
46
|
```ruby
|
@@ -49,9 +50,15 @@ Sidekiq.configure_server do |config|
|
|
49
50
|
config.periodic do |mgr|
|
50
51
|
mgr.register("*/10 * * * *", "Coil::Inbox::MessagesPeriodicJob")
|
51
52
|
mgr.register("5-59/10 * * * *", "Coil::Outbox::MessagesPeriodicJob")
|
53
|
+
|
54
|
+
mgr.register("7-59/20 * * * *", "Coil::Inbox::MessagesCleanupJob")
|
55
|
+
mgr.register("12-59/20 * * * *", "Coil::Outbox::MessagesCleanupJob")
|
52
56
|
end
|
53
57
|
end
|
54
58
|
```
|
59
|
+
(_NOTE_: The cleanup jobs delete already-processed messages once their retention
|
60
|
+
period has passed. Retention periods can be [configured](#configuration) using
|
61
|
+
`Coil.inbox_retention_period` and `Coil.outbox_retention_period`.)
|
55
62
|
|
56
63
|
Filter retryable errors out of alerting, e.g. airbrake:
|
57
64
|
```ruby
|
@@ -242,6 +249,8 @@ initializer at `config/initializers/coil.rb` with the following content, then
|
|
242
249
|
uncomment and adjust the settings you wish to change:
|
243
250
|
```ruby
|
244
251
|
# Coil.sidekiq_queue = "default"
|
252
|
+
# Coil.inbox_retention_period = 12.weeks
|
253
|
+
# Coil.outbox_retention_period = 12.weeks
|
245
254
|
```
|
246
255
|
|
247
256
|
## Development
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
# This job deletes processed inbox messages whose retention period has passed.
|
4
|
+
module Coil
|
5
|
+
module Inbox
|
6
|
+
class MessagesCleanupJob < TransactionalMessagesCleanupJob
|
7
|
+
private
|
8
|
+
|
9
|
+
def message_parent_class
|
10
|
+
::Coil::Inbox::Message
|
11
|
+
end
|
12
|
+
|
13
|
+
def retention_period
|
14
|
+
::Coil.inbox_retention_period
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
# This job deletes processed outbox messages whose retention period has passed.
|
4
|
+
module Coil
|
5
|
+
module Outbox
|
6
|
+
class MessagesCleanupJob < TransactionalMessagesCleanupJob
|
7
|
+
private
|
8
|
+
|
9
|
+
def message_parent_class
|
10
|
+
::Coil::Outbox::Message
|
11
|
+
end
|
12
|
+
|
13
|
+
def retention_period
|
14
|
+
::Coil.outbox_retention_period
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# A cleanup job deletes processed messages whose retention period has passed.
|
5
|
+
module Coil
|
6
|
+
class TransactionalMessagesCleanupJob < ApplicationJob
|
7
|
+
DuplicateJobError = Class.new(StandardError)
|
8
|
+
|
9
|
+
# Sidekiq is not designed for long-running jobs, so we place an upper bound
|
10
|
+
# on job duration. When a job exceeds this bound, we'll enqueue a subsequent
|
11
|
+
# job to pick up where we left off.
|
12
|
+
MAX_DURATION = 5.minutes
|
13
|
+
|
14
|
+
sidekiq_options retry: 4, dead: false
|
15
|
+
|
16
|
+
def perform(batch_size = 1000)
|
17
|
+
result = delete_messages(batch_size)
|
18
|
+
total_deletions = result.deletions.values.sum
|
19
|
+
deletions_json = result.deletions.to_json
|
20
|
+
|
21
|
+
case result
|
22
|
+
when Finished
|
23
|
+
Rails.logger.info(<<~INFO.squish)
|
24
|
+
#{self.class} finished after deleting #{total_deletions} messages
|
25
|
+
(#{deletions_json}).
|
26
|
+
INFO
|
27
|
+
when ExceededDeadline
|
28
|
+
Rails.logger.info(<<~INFO.squish)
|
29
|
+
#{self.class} exceeded deadline after deleting #{total_deletions}
|
30
|
+
messages (#{deletions_json}). Enqueuing subsequent job.
|
31
|
+
INFO
|
32
|
+
self.class.perform_async(batch_size)
|
33
|
+
end
|
34
|
+
rescue DuplicateJobError
|
35
|
+
# A duplicate job is in the midst of its message-deletion loop. We'll call
|
36
|
+
# this job done and allow the other one to continue.
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def delete_messages(batch_size)
|
42
|
+
ApplicationRecord.uncached do
|
43
|
+
locking do
|
44
|
+
_delete_messages(batch_size)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
Finished = Struct.new(:deletions)
|
50
|
+
ExceededDeadline = Struct.new(:deletions)
|
51
|
+
|
52
|
+
def _delete_messages(batch_size)
|
53
|
+
now = Time.current
|
54
|
+
created_before = now - retention_period
|
55
|
+
deadline = now + MAX_DURATION
|
56
|
+
deletions = Hash.new(0)
|
57
|
+
|
58
|
+
# Identify distinct message types, their associated job types, and the
|
59
|
+
# messages that can safely be deleted. Delete in batches until finished
|
60
|
+
# or the deadline is exceeded.
|
61
|
+
message_parent_class.select(:type).distinct.pluck(:type).each do |type|
|
62
|
+
message_class = message_class_for(type)
|
63
|
+
messages =
|
64
|
+
if message_class.present?
|
65
|
+
message_class.processed(processor_name: message_class.new.job_class.name)
|
66
|
+
else
|
67
|
+
message_parent_class.where(type:)
|
68
|
+
end
|
69
|
+
|
70
|
+
messages
|
71
|
+
.where(created_at: nil...created_before)
|
72
|
+
.in_batches(of: batch_size) do |batch|
|
73
|
+
return ExceededDeadline.new(deletions) if Time.current > deadline
|
74
|
+
deletions[type] += batch.distinct(false).delete_all
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
Finished.new(deletions)
|
79
|
+
end
|
80
|
+
|
81
|
+
QUEUE_TYPE = "CLEANUP_QUEUE"
|
82
|
+
|
83
|
+
def locking(&blk)
|
84
|
+
QueueLocking.locking(
|
85
|
+
queue_type: QUEUE_TYPE,
|
86
|
+
message_type: message_parent_class.to_s,
|
87
|
+
message_keys: [self.class.to_s],
|
88
|
+
wait: false,
|
89
|
+
&blk
|
90
|
+
)
|
91
|
+
rescue QueueLocking::LockWaitTimeout
|
92
|
+
raise DuplicateJobError
|
93
|
+
end
|
94
|
+
|
95
|
+
def message_class_for(type)
|
96
|
+
message_parent_class.sti_class_for(type)
|
97
|
+
rescue ActiveRecord::SubclassNotFound
|
98
|
+
end
|
99
|
+
|
100
|
+
def message_parent_class
|
101
|
+
end
|
102
|
+
|
103
|
+
def retention_period
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -12,9 +12,10 @@ module Coil
|
|
12
12
|
# the distinct keys for which we have unprocessed messages (excluding
|
13
13
|
# very recent messages, since a TransactionalMessagesJob may still be
|
14
14
|
# processing those). Then, enqueue the necessary jobs for those keys.
|
15
|
-
message_parent_class.select(:type).distinct.each do |
|
16
|
-
message_class =
|
17
|
-
|
15
|
+
message_parent_class.select(:type).distinct.pluck(:type).each do |type|
|
16
|
+
message_class = message_class_for(type)
|
17
|
+
next unless message_class.present?
|
18
|
+
job_class = message_class.new.job_class
|
18
19
|
|
19
20
|
message_class
|
20
21
|
.unprocessed(processor_name: job_class.name)
|
@@ -27,6 +28,11 @@ module Coil
|
|
27
28
|
|
28
29
|
private
|
29
30
|
|
31
|
+
def message_class_for(type)
|
32
|
+
message_parent_class.sti_class_for(type)
|
33
|
+
rescue ActiveRecord::SubclassNotFound
|
34
|
+
end
|
35
|
+
|
30
36
|
def message_parent_class
|
31
37
|
end
|
32
38
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# typed: false
|
2
|
+
|
3
|
+
class AddForeignKeyToInboxCompletionsOnDeleteCascade < ActiveRecord::Migration[6.0]
|
4
|
+
def change
|
5
|
+
from_table = :coil_inbox_completions
|
6
|
+
to_table = :coil_inbox_messages
|
7
|
+
column = :last_completed_message_id
|
8
|
+
old_key = find_foreign_key(from_table, to_table:, column:, on_delete: nil)
|
9
|
+
|
10
|
+
# NOTE: To minimize the impact on read/write availability while adding this
|
11
|
+
# foreign key, we specify `validate: false`, skipping the table scan that
|
12
|
+
# would normally be performed to validate that all existing rows satisfy the
|
13
|
+
# new constraint. We can then validate it in a separate transaction (see the
|
14
|
+
# next migration) without blocking reads/writes to the tables involved.
|
15
|
+
# https://www.postgresql.org/docs/current/sql-altertable.html#SQL-ALTERTABLE-DESC-ADD-TABLE-CONSTRAINT
|
16
|
+
add_foreign_key(
|
17
|
+
from_table,
|
18
|
+
to_table,
|
19
|
+
column:,
|
20
|
+
name: "#{old_key.name}_on_delete_cascade",
|
21
|
+
on_delete: :cascade,
|
22
|
+
validate: false
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def find_foreign_key(from_table, **options)
|
29
|
+
foreign_keys(from_table).detect { |fk| fk.defined_for?(**options) } ||
|
30
|
+
raise("No foreign key found from table '#{from_table}' for #{options}")
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# typed: false
|
2
|
+
|
3
|
+
class ValidateForeignKeyInboxCompletions < ActiveRecord::Migration[6.0]
|
4
|
+
def change
|
5
|
+
from_table = :coil_inbox_completions
|
6
|
+
to_table = :coil_inbox_messages
|
7
|
+
column = :last_completed_message_id
|
8
|
+
key = find_foreign_key(from_table, to_table:, column:, on_delete: :cascade)
|
9
|
+
|
10
|
+
validate_foreign_key from_table, to_table, name: key.name
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def find_foreign_key(from_table, **options)
|
16
|
+
foreign_keys(from_table).detect { |fk| fk.defined_for?(**options) } ||
|
17
|
+
raise("No foreign key found from table '#{from_table}' for #{options}")
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# typed: false
|
2
|
+
|
3
|
+
class RemoveOldForeignKeyFromInboxCompletions < ActiveRecord::Migration[6.0]
|
4
|
+
def change
|
5
|
+
from_table = :coil_inbox_completions
|
6
|
+
to_table = :coil_inbox_messages
|
7
|
+
column = :last_completed_message_id
|
8
|
+
new_key = find_foreign_key(from_table, to_table:, column:, on_delete: :cascade)
|
9
|
+
|
10
|
+
remove_foreign_key(
|
11
|
+
from_table,
|
12
|
+
to_table,
|
13
|
+
column:,
|
14
|
+
name: new_key.name.delete_suffix("_on_delete_cascade")
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def find_foreign_key(from_table, **options)
|
21
|
+
foreign_keys(from_table).detect { |fk| fk.defined_for?(**options) } ||
|
22
|
+
raise("No foreign key found from table '#{from_table}' for #{options}")
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# typed: false
|
2
|
+
|
3
|
+
class AddForeignKeyToOutboxCompletionsOnDeleteCascade < ActiveRecord::Migration[6.0]
|
4
|
+
def change
|
5
|
+
from_table = :coil_outbox_completions
|
6
|
+
to_table = :coil_outbox_messages
|
7
|
+
column = :last_completed_message_id
|
8
|
+
old_key = find_foreign_key(from_table, to_table:, column:, on_delete: nil)
|
9
|
+
|
10
|
+
# NOTE: To minimize the impact on read/write availability while adding this
|
11
|
+
# foreign key, we specify `validate: false`, skipping the table scan that
|
12
|
+
# would normally be performed to validate that all existing rows satisfy the
|
13
|
+
# new constraint. We can then validate it in a separate transaction (see the
|
14
|
+
# next migration) without blocking reads/writes to the tables involved.
|
15
|
+
# https://www.postgresql.org/docs/current/sql-altertable.html#SQL-ALTERTABLE-DESC-ADD-TABLE-CONSTRAINT
|
16
|
+
add_foreign_key(
|
17
|
+
from_table,
|
18
|
+
to_table,
|
19
|
+
column:,
|
20
|
+
name: "#{old_key.name}_on_delete_cascade",
|
21
|
+
on_delete: :cascade,
|
22
|
+
validate: false
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def find_foreign_key(from_table, **options)
|
29
|
+
foreign_keys(from_table).detect { |fk| fk.defined_for?(**options) } ||
|
30
|
+
raise("No foreign key found from table '#{from_table}' for #{options}")
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# typed: false
|
2
|
+
|
3
|
+
class ValidateForeignKeyOutboxCompletions < ActiveRecord::Migration[6.0]
|
4
|
+
def change
|
5
|
+
from_table = :coil_outbox_completions
|
6
|
+
to_table = :coil_outbox_messages
|
7
|
+
column = :last_completed_message_id
|
8
|
+
key = find_foreign_key(from_table, to_table:, column:, on_delete: :cascade)
|
9
|
+
|
10
|
+
validate_foreign_key from_table, to_table, name: key.name
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def find_foreign_key(from_table, **options)
|
16
|
+
foreign_keys(from_table).detect { |fk| fk.defined_for?(**options) } ||
|
17
|
+
raise("No foreign key found from table '#{from_table}' for #{options}")
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# typed: false
|
2
|
+
|
3
|
+
class RemoveOldForeignKeyFromOutboxCompletions < ActiveRecord::Migration[6.0]
|
4
|
+
def change
|
5
|
+
from_table = :coil_outbox_completions
|
6
|
+
to_table = :coil_outbox_messages
|
7
|
+
column = :last_completed_message_id
|
8
|
+
new_key = find_foreign_key(from_table, to_table:, column:, on_delete: :cascade)
|
9
|
+
|
10
|
+
remove_foreign_key(
|
11
|
+
from_table,
|
12
|
+
to_table,
|
13
|
+
column:,
|
14
|
+
name: new_key.name.delete_suffix("_on_delete_cascade")
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def find_foreign_key(from_table, **options)
|
21
|
+
foreign_keys(from_table).detect { |fk| fk.defined_for?(**options) } ||
|
22
|
+
raise("No foreign key found from table '#{from_table}' for #{options}")
|
23
|
+
end
|
24
|
+
end
|
data/lib/coil/version.rb
CHANGED
data/lib/coil.rb
CHANGED
data/rbi/coil.rbi
CHANGED
@@ -79,6 +79,16 @@ Coil::Inbox::Message::COMPLETION = Coil::Inbox::Completion
|
|
79
79
|
Coil::Inbox::Message::PERSISTENCE_QUEUE = T.let(T.unsafe(nil), String)
|
80
80
|
Coil::Inbox::Message::PROCESS_QUEUE = T.let(T.unsafe(nil), String)
|
81
81
|
|
82
|
+
class Coil::Inbox::MessagesCleanupJob < ::Coil::TransactionalMessagesCleanupJob
|
83
|
+
private
|
84
|
+
|
85
|
+
sig { override.returns(T.class_of(::Coil::Inbox::Message)) }
|
86
|
+
def message_parent_class; end
|
87
|
+
|
88
|
+
sig { override.returns(ActiveSupport::Duration) }
|
89
|
+
def retention_period; end
|
90
|
+
end
|
91
|
+
|
82
92
|
class Coil::Inbox::MessagesPeriodicJob < ::Coil::TransactionalMessagesPeriodicJob
|
83
93
|
private
|
84
94
|
|
@@ -140,6 +150,16 @@ Coil::Outbox::Message::COMPLETION = Coil::Outbox::Completion
|
|
140
150
|
Coil::Outbox::Message::PERSISTENCE_QUEUE = T.let(T.unsafe(nil), String)
|
141
151
|
Coil::Outbox::Message::PROCESS_QUEUE = T.let(T.unsafe(nil), String)
|
142
152
|
|
153
|
+
class Coil::Outbox::MessagesCleanupJob < ::Coil::TransactionalMessagesCleanupJob
|
154
|
+
private
|
155
|
+
|
156
|
+
sig { override.returns(T.class_of(::Coil::Outbox::Message)) }
|
157
|
+
def message_parent_class; end
|
158
|
+
|
159
|
+
sig { override.returns(ActiveSupport::Duration) }
|
160
|
+
def retention_period; end
|
161
|
+
end
|
162
|
+
|
143
163
|
class Coil::Outbox::MessagesPeriodicJob < ::Coil::TransactionalMessagesPeriodicJob
|
144
164
|
private
|
145
165
|
|
@@ -311,6 +331,45 @@ class Coil::TransactionalMessagesJob::DuplicateJobError < ::StandardError; end
|
|
311
331
|
Coil::TransactionalMessagesJob::MAX_DURATION = T.let(T.unsafe(nil), ActiveSupport::Duration)
|
312
332
|
class Coil::TransactionalMessagesJob::RetryableError < ::StandardError; end
|
313
333
|
|
334
|
+
class Coil::TransactionalMessagesCleanupJob < ::Coil::ApplicationJob
|
335
|
+
abstract!
|
336
|
+
|
337
|
+
class DuplicateJobError < ::StandardError; end
|
338
|
+
|
339
|
+
MAX_DURATION = T.let(T.unsafe(nil), ActiveSupport::Duration)
|
340
|
+
|
341
|
+
sig { params(batch_size: Integer).void }
|
342
|
+
def perform(batch_size = 1000); end
|
343
|
+
|
344
|
+
private
|
345
|
+
|
346
|
+
Result = T.type_alias { T.any(Finished, ExceededDeadline) }
|
347
|
+
|
348
|
+
sig { params(batch_size: Integer).returns(Result) }
|
349
|
+
def delete_messages(batch_size); end
|
350
|
+
|
351
|
+
sig { params(batch_size: Integer).returns(Result) }
|
352
|
+
def _delete_messages(batch_size); end
|
353
|
+
|
354
|
+
QUEUE_TYPE = T.let(T.unsafe(nil), String)
|
355
|
+
|
356
|
+
sig {
|
357
|
+
type_parameters(:P)
|
358
|
+
.params(blk: T.proc.returns(T.type_parameter(:P)))
|
359
|
+
.returns(T.type_parameter(:P))
|
360
|
+
}
|
361
|
+
def locking(&blk); end
|
362
|
+
|
363
|
+
sig { params(type: String).returns(T.nilable(::Coil::AnyMessageClass)) }
|
364
|
+
def message_class_for(type); end
|
365
|
+
|
366
|
+
sig { abstract.returns(::Coil::AnyMessageClass) }
|
367
|
+
def message_parent_class; end
|
368
|
+
|
369
|
+
sig { abstract.returns(ActiveSupport::Duration) }
|
370
|
+
def retention_period; end
|
371
|
+
end
|
372
|
+
|
314
373
|
class Coil::TransactionalMessagesPeriodicJob < ::Coil::ApplicationJob
|
315
374
|
abstract!
|
316
375
|
|
@@ -319,6 +378,9 @@ class Coil::TransactionalMessagesPeriodicJob < ::Coil::ApplicationJob
|
|
319
378
|
|
320
379
|
private
|
321
380
|
|
381
|
+
sig { params(type: String).returns(T.nilable(::Coil::AnyMessageClass)) }
|
382
|
+
def message_class_for(type); end
|
383
|
+
|
322
384
|
sig { abstract.returns(::Coil::AnyMessageClass) }
|
323
385
|
def message_parent_class; end
|
324
386
|
end
|
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.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ivan Brennan
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2025-01-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -249,12 +249,16 @@ executables: []
|
|
249
249
|
extensions: []
|
250
250
|
extra_rdoc_files: []
|
251
251
|
files:
|
252
|
+
- CHANGELOG.md
|
252
253
|
- LICENSE
|
253
254
|
- README.md
|
254
255
|
- Rakefile
|
255
256
|
- app/jobs/coil/application_job.rb
|
257
|
+
- app/jobs/coil/inbox/messages_cleanup_job.rb
|
256
258
|
- app/jobs/coil/inbox/messages_periodic_job.rb
|
259
|
+
- app/jobs/coil/outbox/messages_cleanup_job.rb
|
257
260
|
- app/jobs/coil/outbox/messages_periodic_job.rb
|
261
|
+
- app/jobs/coil/transactional_messages_cleanup_job.rb
|
258
262
|
- app/jobs/coil/transactional_messages_job.rb
|
259
263
|
- app/jobs/coil/transactional_messages_periodic_job.rb
|
260
264
|
- app/models/coil/application_record.rb
|
@@ -268,6 +272,12 @@ files:
|
|
268
272
|
- app/models/concerns/coil/transactional_message.rb
|
269
273
|
- config/routes.rb
|
270
274
|
- db/migrate/20240604163650_create_coil_tables.rb
|
275
|
+
- db/migrate/20250101182458_add_foreign_key_to_inbox_completions_on_delete_cascade.rb
|
276
|
+
- db/migrate/20250102031753_validate_foreign_key_inbox_completions.rb
|
277
|
+
- db/migrate/20250102040148_remove_old_foreign_key_from_inbox_completions.rb
|
278
|
+
- db/migrate/20250102040649_add_foreign_key_to_outbox_completions_on_delete_cascade.rb
|
279
|
+
- db/migrate/20250102040950_validate_foreign_key_outbox_completions.rb
|
280
|
+
- db/migrate/20250102041225_remove_old_foreign_key_from_outbox_completions.rb
|
271
281
|
- lib/coil.rb
|
272
282
|
- lib/coil/engine.rb
|
273
283
|
- lib/coil/queue_locking.rb
|