standard_circuit 0.4.0 → 0.4.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 +12 -0
- data/lib/standard_circuit/mailer/retry.rb +60 -6
- data/lib/standard_circuit/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5772ca0d1cec01fde070aab6ed65f232e52b00f7b2f4fa966348f3866a49dea7
|
|
4
|
+
data.tar.gz: a8ce2582796937fe0816f454c9a2fd56e6a29d85e35850136a0b5f7a0bc7885f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1c880e1e97c57f07a0a576a3b2e699583cf7719b1414a4dcc363f55b35109c30367954cea575b163abb8419439a1e9ed264a724110eec85264871e9894b7b930
|
|
7
|
+
data.tar.gz: 0b14981a97b3588b149b41d24c7ac7a554254a449ce40a1773f5921f9d6ca1417e7f5f21116ac239aaefba1993aade11d54a9985026d79adc88aeaaa78555580
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.1] - 2026-09-24
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **`mailer_retry` no longer crashes a lazily-loaded process with `NameError: uninitialized constant ActionMailer::MailDeliveryJob`.** 0.4.0 installed the retry from `ActiveSupport.on_load(:active_job)` and referenced `ActionMailer::MailDeliveryJob` inside the hook. When `class MailDeliveryJob < ActiveJob::Base` was itself what loaded `ActiveJob::Base`, the hook ran while that class was still being autoloaded. In development this broke the first `deliver_later` of a fresh process, and `bin/tapioca dsl` hit it too. Eager-loaded production processes and RSpec runs were not affected. The retry is now installed at whichever of these happens first, and never while `MailDeliveryJob` is still mid-autoload:
|
|
14
|
+
- `ActiveJob::Base` loads when `MailDeliveryJob` can already be referenced safely.
|
|
15
|
+
- `ActionMailer::Base` loads. It references `MailDeliveryJob`, so the class is complete by then. Every delivery goes through a mailer, so this also covers a worker that deserializes the job before any mailer loads.
|
|
16
|
+
- Something subclasses `MailDeliveryJob`. This covers a custom `delivery_job` with its own `rescue_from` that loads before any mailer. The subclass's own handlers keep precedence.
|
|
17
|
+
|
|
18
|
+
A new integration spec boots a Rails app in a subprocess for each load order (mailer first, job first, subclass first, `ActiveJob::Base` first, eager load).
|
|
19
|
+
|
|
20
|
+
**Hosts can delete their workaround.** Any initializer that preloads `ActiveJob::Base` to dodge this bug, such as jumpdrive-web's `config.after_initialize { ActiveJob::Base }`, can be removed.
|
|
21
|
+
|
|
10
22
|
## [0.4.0] - 2026-09-24
|
|
11
23
|
|
|
12
24
|
A developer-experience release that moves code copy-pasted across the five consumer apps into the gem. Everything is additive: behaviour is unchanged unless you opt in, and deprecated APIs keep working until 0.5.
|
|
@@ -35,19 +35,73 @@ module StandardCircuit
|
|
|
35
35
|
EXHAUSTED_MESSAGE = "Email delivery failed: mail circuit breaker retries exhausted".freeze
|
|
36
36
|
FINGERPRINT = "standard_circuit-mailer-retries-exhausted".freeze
|
|
37
37
|
|
|
38
|
+
# Prepended onto ActiveJob::Base's singleton class only when the
|
|
39
|
+
# :active_job hook fired mid-way through MailDeliveryJob's own
|
|
40
|
+
# definition. MailDeliveryJob is complete by the time anything
|
|
41
|
+
# subclasses it, and installing on it before the subclass body runs keeps
|
|
42
|
+
# the handler's precedence the same as for any other load order.
|
|
43
|
+
module InstallBeforeSubclassing
|
|
44
|
+
def inherited(subclass)
|
|
45
|
+
StandardCircuit::Mailer::Retry.install_configured if name == "ActionMailer::MailDeliveryJob"
|
|
46
|
+
super
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
38
50
|
class << self
|
|
39
|
-
# Defers installation until
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
51
|
+
# Defers installation until MailDeliveryJob can be referenced safely,
|
|
52
|
+
# so enabling this from an early initializer doesn't force ActiveJob
|
|
53
|
+
# or ActionMailer to load before their own configuration is applied.
|
|
54
|
+
# The hooks read the live config when they run; they are registered
|
|
55
|
+
# once.
|
|
56
|
+
#
|
|
57
|
+
# `ActionMailer::MailDeliveryJob` can't simply be referenced from an
|
|
58
|
+
# `on_load(:active_job)` hook: when `class MailDeliveryJob <
|
|
59
|
+
# ActiveJob::Base` is itself what loads ActiveJob::Base (a mailer or
|
|
60
|
+
# `deliver_later` touched first in a lazily-loaded process), the hook
|
|
61
|
+
# fires while that class is still being autoloaded and the reference
|
|
62
|
+
# raises NameError. So each trigger checks first, and together they
|
|
63
|
+
# cover every load order:
|
|
64
|
+
#
|
|
65
|
+
# * :active_job — installs straight away when ActiveJob::Base loads
|
|
66
|
+
# ahead of MailDeliveryJob (eager load, or any ApplicationJob first).
|
|
67
|
+
# * :action_mailer — ActionMailer::Base references MailDeliveryJob, so
|
|
68
|
+
# by the time this runs the class is complete. Every delivery goes
|
|
69
|
+
# through a mailer class, so this runs before the first delivery can
|
|
70
|
+
# raise, including in a worker that deserialized the job first
|
|
71
|
+
# (rescue handlers are read when an error is rescued, not at enqueue).
|
|
72
|
+
# * the first subclass of MailDeliveryJob — covers a custom
|
|
73
|
+
# `delivery_job` loaded ahead of any mailer, which would otherwise
|
|
74
|
+
# take a copy of the parent's rescue handlers without this one.
|
|
43
75
|
def install_on_load
|
|
44
76
|
return if @on_load_registered
|
|
45
77
|
|
|
46
78
|
@on_load_registered = true
|
|
47
79
|
::ActiveSupport.on_load(:active_job) do
|
|
48
|
-
|
|
49
|
-
|
|
80
|
+
if StandardCircuit::Mailer::Retry.mail_delivery_job_referenceable?
|
|
81
|
+
StandardCircuit::Mailer::Retry.install_configured
|
|
82
|
+
else
|
|
83
|
+
singleton_class.prepend(StandardCircuit::Mailer::Retry::InstallBeforeSubclassing)
|
|
84
|
+
end
|
|
50
85
|
end
|
|
86
|
+
::ActiveSupport.on_load(:action_mailer) { StandardCircuit::Mailer::Retry.install_configured }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Installs on ActionMailer::MailDeliveryJob with the configured options
|
|
90
|
+
# when mailer_retry is on and the class is safe to reference. Returns
|
|
91
|
+
# true when the handler is in place afterwards.
|
|
92
|
+
def install_configured
|
|
93
|
+
options = StandardCircuit.config.mailer_retry
|
|
94
|
+
return false unless options && mail_delivery_job_referenceable?
|
|
95
|
+
|
|
96
|
+
install(::ActionMailer::MailDeliveryJob, options)
|
|
97
|
+
true
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# False only while MailDeliveryJob is mid-autoload on this thread (see
|
|
101
|
+
# install_on_load): Ruby reports an in-progress autoload as undefined.
|
|
102
|
+
# When the autoload is merely pending, referencing it loads it normally.
|
|
103
|
+
def mail_delivery_job_referenceable?
|
|
104
|
+
defined?(::ActionMailer) && ::ActionMailer.const_defined?(:MailDeliveryJob, false)
|
|
51
105
|
end
|
|
52
106
|
|
|
53
107
|
# Adds the retry_on to +job_class+ unless a CircuitOpenError handler is
|