In cluster rehearsal 3, turning outboundMta off in node1's role came back x:settingsReload: {"applied": true}. Node1 kept delivering mail anyway, including a message in the report queue, until it was restarted.
The role was read only once, at boot:
Queue and report managers (crates/smtp/src/lib.rs:47): started only if the node's role included outboundMta at boot.
Task manager (crates/services/src/task_manager/manager.rs:62-70): started only if the role had some task type at boot.
After boot nothing looked at the role again. A running queue manager kept claiming and delivering, and a manager that wasn't started never started.
Choice: follow the role live
I made the services follow the role instead of adding a restartRequired flag. The change turned out to be small: each loop already has a natural point to check the role, and the running settings are reloaded on every role write (#39). A restart flag would have left the rehearsal's behavior in place and only reported it.
They now start on every node (outside recovery mode):
Queue manager. Before each scan it reads the role from the running settings.
Without outboundMta it claims nothing new.
Deliveries already running finish and report back as usual, which releases their locks.
When the role comes back it logs queue.started and scans the whole queue at once. A reload wakes it through the existing ReloadSettings queue event, and it also rechecks every 30 s.
Report scheduler. It handles DMARC/TLS report events only while the role has outboundMta, as at boot. Events that arrive without the role are dropped, as they were on a node started without it.
Task manager.
task_enabled already read the current role on every scan.
It now also runs on nodes whose role has no task type. The scan returns at once until the role gains one.
A job claimed before a role change is handed back at once. Before, it was run anyway or held until its lease lapsed.
A settings reload now wakes the task manager, so newly enabled task types are claimed straight away.
Side effect. Starting the queue manager everywhere also drains the queue channel on nodes without outboundMta. Upstream never read that channel, so every message queued on such a node parked a refresh in it. Reading the code, queueing would block once 1,024 had piled up. I haven't reproduced that.
What still needs a restart
Moving a node to a different role. A role object edit reaches the nodes whose INBUXA_ROLE names that role. Moving a node to another role means changing its environment, so it restarts anyway.
Listener changes in a role. Listeners bind at boot. This PR only covers tasks and delivery.
Tracer settings (the optional item): not done. A reload applies a tracer's new event interests but not its other settings. Examples: a log tracer's path, a webhook URL, an OTel endpoint. Telemetry::update refreshes interests only. Fixing it means respawning any tracer whose settings changed, for every tracer type. That isn't trivial, so I left it out.
Tests
cluster::live_roles::live_role_tests is new. It runs on PostgreSQL with two nodes over one store. Each node queues a message to an unreachable domain and schedules a TLS report task, then:
Rehearsal case. A node that starts with outboundMta delivers and runs the report task. Its role then loses outboundMta and the settings reload:
A new message isn't attempted and a new report task stays pending.
When the role gets outboundMta back, both are picked up.
Role gained after boot. A node that starts with no task type at all gains outboundMta. The waiting message is attempted and the report task runs.
Results:
Run
Result
This branch
pass (23.5 s)
main
fails at step 1: delivery attempted without outboundMta
main, earlier version of the test with only step 2
smtp:: (RocksDb), 75 tests. The suite is flaky under parallel load on main too:
main: 1 of 2 runs failed (smtp::inbound::dmarc::dmarc).
This branch: 2 of 3 runs failed (smtp::inbound::dkim2::dkim2_all_disclosed in both, plus smtp::outbound::smtp::smtp_delivery in the first). The third run passed all 75.
Rerun on their own, smtp_delivery and the six smtp::inbound::dkim2 tests pass.
The SMTP tests run with every role on, where the new code does nothing different.
Command: STORE=PostgreSql cargo test -p tests --features postgres cluster::live_roles
## Problem
In cluster rehearsal 3, turning outboundMta off in node1's role came back `x:settingsReload: {"applied": true}`. Node1 kept delivering mail anyway, including a message in the `report` queue, until it was restarted.
The role was read only once, at boot:
- **Queue and report managers** (`crates/smtp/src/lib.rs:47`): started only if the node's role included outboundMta at boot.
- **Task manager** (`crates/services/src/task_manager/manager.rs:62-70`): started only if the role had some task type at boot.
After boot nothing looked at the role again. A running queue manager kept claiming and delivering, and a manager that wasn't started never started.
## Choice: follow the role live
I made the services follow the role instead of adding a `restartRequired` flag. The change turned out to be small: each loop already has a natural point to check the role, and the running settings are reloaded on every role write (#39). A restart flag would have left the rehearsal's behavior in place and only reported it.
They now start on every node (outside recovery mode):
- **Queue manager.** Before each scan it reads the role from the running settings.
- Without outboundMta it claims nothing new.
- Deliveries already running finish and report back as usual, which releases their locks.
- When the role comes back it logs `queue.started` and scans the whole queue at once. A reload wakes it through the existing `ReloadSettings` queue event, and it also rechecks every 30 s.
- **Report scheduler.** It handles DMARC/TLS report events only while the role has outboundMta, as at boot. Events that arrive without the role are dropped, as they were on a node started without it.
- **Task manager.**
- `task_enabled` already read the current role on every scan.
- It now also runs on nodes whose role has no task type. The scan returns at once until the role gains one.
- A job claimed before a role change is handed back at once. Before, it was run anyway or held until its lease lapsed.
- A settings reload now wakes the task manager, so newly enabled task types are claimed straight away.
**Side effect.** Starting the queue manager everywhere also drains the queue channel on nodes without outboundMta. Upstream never read that channel, so every message queued on such a node parked a refresh in it. Reading the code, queueing would block once 1,024 had piled up. I haven't reproduced that.
## What still needs a restart
- **Moving a node to a different role.** A role object edit reaches the nodes whose `INBUXA_ROLE` names that role. Moving a node to another role means changing its environment, so it restarts anyway.
- **Listener changes in a role.** Listeners bind at boot. This PR only covers tasks and delivery.
- **Tracer settings (the optional item): not done.** A reload applies a tracer's new event interests but not its other settings. Examples: a log tracer's path, a webhook URL, an OTel endpoint. `Telemetry::update` refreshes interests only. Fixing it means respawning any tracer whose settings changed, for every tracer type. That isn't trivial, so I left it out.
## Tests
`cluster::live_roles::live_role_tests` is new. It runs on PostgreSQL with two nodes over one store. Each node queues a message to an unreachable domain and schedules a TLS report task, then:
1. **Rehearsal case.** A node that starts with outboundMta delivers and runs the report task. Its role then loses outboundMta and the settings reload:
- A new message isn't attempted and a new report task stays pending.
- When the role gets outboundMta back, both are picked up.
2. **Role gained after boot.** A node that starts with no task type at all gains outboundMta. The waiting message is attempted and the report task runs.
Results:
| Run | Result |
|---|---|
| This branch | pass (23.5 s) |
| `main` | fails at step 1: `delivery attempted without outboundMta` |
| `main`, earlier version of the test with only step 2 | fails: nothing picked the message up in 20 s |
Regressions:
- `cluster::task_roles::task_role_tests` (PostgreSql): pass.
- `smtp::` (RocksDb), 75 tests. The suite is flaky under parallel load on `main` too:
- `main`: 1 of 2 runs failed (`smtp::inbound::dmarc::dmarc`).
- This branch: 2 of 3 runs failed (`smtp::inbound::dkim2::dkim2_all_disclosed` in both, plus `smtp::outbound::smtp::smtp_delivery` in the first). The third run passed all 75.
- Rerun on their own, `smtp_delivery` and the six `smtp::inbound::dkim2` tests pass.
- The SMTP tests run with every role on, where the new code does nothing different.
Command: `STORE=PostgreSql cargo test -p tests --features postgres cluster::live_roles`
In cluster rehearsal 3, turning outboundMta off on node1's role was
reported applied (x:settingsReload applied: true), yet node1 kept
delivering mail, a report message included, until it was restarted.
The queue and report managers were started at boot only when the
node's role included outboundMta (crates/smtp/src/lib.rs), and the task
manager only when the role had some task type (spawn_task_manager).
After that nothing looked at the role again: a queue manager that was
running kept claiming and delivering, and one that wasn't never
started.
They now start on every node (outside recovery mode) and follow the
role live:
- Queue manager: before each scan it reads the role from the running
settings. Without outboundMta it claims nothing new; deliveries
already running finish and report back as usual, which releases
their locks. When the role comes back (a reload wakes the manager
with ReloadSettings, and it looks again every 30 s regardless) it
logs queue.started and scans the whole queue at once.
- Report scheduler: DMARC and TLS report events are handled only while
the role has outboundMta, as at boot; events arriving without it are
dropped, as they were on a node started without the role.
- Task manager: task_enabled already read the current role on every
scan. It now also runs on nodes whose role has no task type (the
scan returns at once until one is added), a job claimed before a
role change is handed back at once rather than run or held until
its lease lapses, and a settings reload wakes the manager so a role
that gained task types starts claiming them straight away.
Starting the queue manager on every node also drains the queue channel
on nodes without outboundMta. Upstream left that channel unread, so
each message queued there parked a refresh in it, and by the code,
queueing would block once 1024 had piled up (not reproduced here).
A role object edit reaches the nodes that name that role in
INBUXA_ROLE. Moving a node to another role still means changing its
environment, and so a restart. Listener changes in a role still need a
restart too (listeners bind at boot); this change is about tasks and
delivery.
cluster::live_roles::live_role_tests (new; PostgreSQL, two nodes over
one store):
1. A node started with outboundMta delivers and runs a TLS report
task; after its role loses outboundMta and the settings reload, a
new message isn't attempted and a new report task stays pending;
with the role back, both are taken up.
2. A node started with no task type at all gains outboundMta: a
waiting message is attempted and a report task runs.
On main the test fails at step 1 ("delivery attempted without
outboundMta"); with step 1 bypassed, step 2 fails (nothing picked the
message up in 20 s).
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Problem
In cluster rehearsal 3, turning outboundMta off in node1's role came back
x:settingsReload: {"applied": true}. Node1 kept delivering mail anyway, including a message in thereportqueue, until it was restarted.The role was read only once, at boot:
crates/smtp/src/lib.rs:47): started only if the node's role included outboundMta at boot.crates/services/src/task_manager/manager.rs:62-70): started only if the role had some task type at boot.After boot nothing looked at the role again. A running queue manager kept claiming and delivering, and a manager that wasn't started never started.
Choice: follow the role live
I made the services follow the role instead of adding a
restartRequiredflag. The change turned out to be small: each loop already has a natural point to check the role, and the running settings are reloaded on every role write (#39). A restart flag would have left the rehearsal's behavior in place and only reported it.They now start on every node (outside recovery mode):
queue.startedand scans the whole queue at once. A reload wakes it through the existingReloadSettingsqueue event, and it also rechecks every 30 s.task_enabledalready read the current role on every scan.Side effect. Starting the queue manager everywhere also drains the queue channel on nodes without outboundMta. Upstream never read that channel, so every message queued on such a node parked a refresh in it. Reading the code, queueing would block once 1,024 had piled up. I haven't reproduced that.
What still needs a restart
INBUXA_ROLEnames that role. Moving a node to another role means changing its environment, so it restarts anyway.Telemetry::updaterefreshes interests only. Fixing it means respawning any tracer whose settings changed, for every tracer type. That isn't trivial, so I left it out.Tests
cluster::live_roles::live_role_testsis new. It runs on PostgreSQL with two nodes over one store. Each node queues a message to an unreachable domain and schedules a TLS report task, then:Results:
maindelivery attempted without outboundMtamain, earlier version of the test with only step 2Regressions:
cluster::task_roles::task_role_tests(PostgreSql): pass.smtp::(RocksDb), 75 tests. The suite is flaky under parallel load onmaintoo:main: 1 of 2 runs failed (smtp::inbound::dmarc::dmarc).smtp::inbound::dkim2::dkim2_all_disclosedin both, plussmtp::outbound::smtp::smtp_deliveryin the first). The third run passed all 75.smtp_deliveryand the sixsmtp::inbound::dkim2tests pass.Command:
STORE=PostgreSql cargo test -p tests --features postgres cluster::live_rolesIn cluster rehearsal 3, turning outboundMta off on node1's role was reported applied (x:settingsReload applied: true), yet node1 kept delivering mail, a report message included, until it was restarted. The queue and report managers were started at boot only when the node's role included outboundMta (crates/smtp/src/lib.rs), and the task manager only when the role had some task type (spawn_task_manager). After that nothing looked at the role again: a queue manager that was running kept claiming and delivering, and one that wasn't never started. They now start on every node (outside recovery mode) and follow the role live: - Queue manager: before each scan it reads the role from the running settings. Without outboundMta it claims nothing new; deliveries already running finish and report back as usual, which releases their locks. When the role comes back (a reload wakes the manager with ReloadSettings, and it looks again every 30 s regardless) it logs queue.started and scans the whole queue at once. - Report scheduler: DMARC and TLS report events are handled only while the role has outboundMta, as at boot; events arriving without it are dropped, as they were on a node started without the role. - Task manager: task_enabled already read the current role on every scan. It now also runs on nodes whose role has no task type (the scan returns at once until one is added), a job claimed before a role change is handed back at once rather than run or held until its lease lapses, and a settings reload wakes the manager so a role that gained task types starts claiming them straight away. Starting the queue manager on every node also drains the queue channel on nodes without outboundMta. Upstream left that channel unread, so each message queued there parked a refresh in it, and by the code, queueing would block once 1024 had piled up (not reproduced here). A role object edit reaches the nodes that name that role in INBUXA_ROLE. Moving a node to another role still means changing its environment, and so a restart. Listener changes in a role still need a restart too (listeners bind at boot); this change is about tasks and delivery. cluster::live_roles::live_role_tests (new; PostgreSQL, two nodes over one store): 1. A node started with outboundMta delivers and runs a TLS report task; after its role loses outboundMta and the settings reload, a new message isn't attempted and a new report task stays pending; with the role back, both are taken up. 2. A node started with no task type at all gains outboundMta: a waiting message is attempted and a report task runs. On main the test fails at step 1 ("delivery attempted without outboundMta"); with step 1 bypassed, step 2 fails (nothing picked the message up in 20 s).6a80aaea08toe00978c0b4