Depends on #38 (reload behavior). This branch is built on it, so until #38 merges the diff shows both.
Problem
A registry write only took effect after an explicit x:Action ReloadSettings. The one exception was Directory/Authentication writes (crates/jmap/src/registry/set.rs, DIR-17).
In the rehearsal, a new MtaDeliverySchedule stayed unknown to the queue ("Queue strategy not found") until someone reloaded. Any client that forgot the reload left the server running stale settings.
Change: automatic reload after a write
When it reloads. After an x:<Object>/set that created, updated or destroyed at least one object the running settings are built from, the server reloads those settings. It then broadcasts RegistryChange::Reload, the same event ReloadSettings sends, so every node applies it. Other nodes pick it up about a second later, as they do for ReloadSettings; the cluster test below shows it at once.
Which object types reload what (common::cache::reload::write_reload_target):
Object types
What happens
Settings objects: every type the core, telemetry, listener and directory builders read (MTA stages, strategies, schedules, routes, queues, milters, hooks; spam filter objects; listeners; tracers and webhooks; Sieve system scripts; cluster roles; directories and authentication; HTTP/IMAP/JMAP/…)
full reload
Certificates
the certificate reload
Lookup keys and lookup stores
the lookup-store reload
Blocked and allowed IPs
the blocked-IP reload
Accounts, domains, roles, tenants, mailing lists, masked addresses, OAuth clients, keys, alerts: data read when needed
no reload
Stores
no reload: storage isn't rebuilt on reload, so it needs a restart, as before
Applications
no reload: they keep their own reload action
Why it's safe
Bulk writes. Full reloads are coalesced. A write gets a ticket after it is stored, then waits for the reload lock. If a reload that started after its write has already finished, it takes that reload's result. Otherwise it runs one reload covering every write stored so far. Many objects in one request cost one reload; 8 concurrent requests cost about two.
Validation failures. An object that fails validation isn't stored and doesn't count. A request where nothing was stored doesn't reload.
Reload errors. The write is never rolled back. With #38, a reload is refused only over errors in objects that were working. When it is refused, nothing is broadcast, and the set response says so (next section).
Cost. One core rebuild per save: the same work ReloadSettings does, which the admin UI already triggers after saves. The targeted reloads are cheap.
New set response field
x:settingsReload appears only when the write called for a reload:
"x:settingsReload":{"applied":true}"x:settingsReload":{"applied":false,"description":"Saved, but the running settings were not reloaded. Tracer with id …: Only one console tracer is allowed"}
The description comes from the same helper as #38's ReloadSettings refusal.
For the admin UI change
The server now reloads by itself after these saves, so a ReloadSettings call after each save is redundant. It's still harmless: one more reload.
The UI can instead read x:settingsReload from the set response. If applied is false, show the description, since the object was saved but isn't in effect.
ReloadSettings is still needed for:
Application changes.
Retrying after fixing some other object.
Test harness
Every reload sends the queue a ReloadSettings event. read_event, try_read_event and assert_no_events in the SMTP test harness now skip those events. expect_reload_settings still waits for one.
An MtaVirtualQueue and an MtaDeliverySchedule created over JMAP are in the running core with no ReloadSettings. The schedule is gone again once destroyed.
8 concurrent schedule creates all land.
Two console tracers in one request are stored, and the response reports applied: false with the error.
A Domain write has no x:settingsReload.
On main, the schedule is missing after the create (has_schedule fails).
cluster::broadcast::cluster_tests (3 nodes, STORE=PostgreSql COORDINATOR=Nats): now creates a schedule on node 0 without a reload, and checks all three nodes have it within 5 s. They had it at once. The same check on main fails ("node 0 didn't pick up the new delivery schedule").
Depends on #38 (reload behavior). This branch is built on it, so until #38 merges the diff shows both.
## Problem
A registry write only took effect after an explicit `x:Action ReloadSettings`. The one exception was Directory/Authentication writes (`crates/jmap/src/registry/set.rs`, DIR-17).
In the rehearsal, a new `MtaDeliverySchedule` stayed unknown to the queue ("Queue strategy not found") until someone reloaded. Any client that forgot the reload left the server running stale settings.
## Change: automatic reload after a write
**When it reloads.** After an `x:<Object>/set` that created, updated or destroyed at least one object the running settings are built from, the server reloads those settings. It then broadcasts `RegistryChange::Reload`, the same event `ReloadSettings` sends, so every node applies it. Other nodes pick it up about a second later, as they do for ReloadSettings; the cluster test below shows it at once.
**Which object types reload what** (`common::cache::reload::write_reload_target`):
| Object types | What happens |
|---|---|
| Settings objects: every type the core, telemetry, listener and directory builders read (MTA stages, strategies, schedules, routes, queues, milters, hooks; spam filter objects; listeners; tracers and webhooks; Sieve system scripts; cluster roles; directories and authentication; HTTP/IMAP/JMAP/…) | full reload |
| Certificates | the certificate reload |
| Lookup keys and lookup stores | the lookup-store reload |
| Blocked and allowed IPs | the blocked-IP reload |
| Accounts, domains, roles, tenants, mailing lists, masked addresses, OAuth clients, keys, alerts: data read when needed | no reload |
| Stores | no reload: storage isn't rebuilt on reload, so it needs a restart, as before |
| Applications | no reload: they keep their own reload action |
## Why it's safe
- **Bulk writes.** Full reloads are coalesced. A write gets a ticket after it is stored, then waits for the reload lock. If a reload that started after its write has already finished, it takes that reload's result. Otherwise it runs one reload covering every write stored so far. Many objects in one request cost one reload; 8 concurrent requests cost about two.
- **Validation failures.** An object that fails validation isn't stored and doesn't count. A request where nothing was stored doesn't reload.
- **Reload errors.** The write is never rolled back. With #38, a reload is refused only over errors in objects that were working. When it is refused, nothing is broadcast, and the set response says so (next section).
- **Cost.** One core rebuild per save: the same work ReloadSettings does, which the admin UI already triggers after saves. The targeted reloads are cheap.
## New set response field
`x:settingsReload` appears only when the write called for a reload:
```json
"x:settingsReload": {"applied": true}
"x:settingsReload": {"applied": false, "description": "Saved, but the running settings were not reloaded. Tracer with id …: Only one console tracer is allowed"}
```
The description comes from the same helper as #38's `ReloadSettings` refusal.
## For the admin UI change
The server now reloads by itself after these saves, so a `ReloadSettings` call after each save is redundant. It's still harmless: one more reload.
The UI can instead read `x:settingsReload` from the set response. If `applied` is false, show the description, since the object was saved but isn't in effect.
`ReloadSettings` is still needed for:
- Application changes.
- Retrying after fixing some other object.
## Test harness
Every reload sends the queue a `ReloadSettings` event. `read_event`, `try_read_event` and `assert_no_events` in the SMTP test harness now skip those events. `expect_reload_settings` still waits for one.
## Tests
- **`system::auto_reload::settings_reload_tests`** (new, RocksDb):
- An `MtaVirtualQueue` and an `MtaDeliverySchedule` created over JMAP are in the running core with no ReloadSettings. The schedule is gone again once destroyed.
- 8 concurrent schedule creates all land.
- Two console tracers in one request are stored, and the response reports `applied: false` with the error.
- A Domain write has no `x:settingsReload`.
- On `main`, the schedule is missing after the create (`has_schedule` fails).
- **`cluster::broadcast::cluster_tests`** (3 nodes, `STORE=PostgreSql COORDINATOR=Nats`): now creates a schedule on node 0 without a reload, and checks all three nodes have it within 5 s. They had it at once. The same check on `main` fails ("node 0 didn't pick up the new delivery schedule").
A 3-node rehearsal found that saving an MtaDeliverySchedule left it
unknown to the queue ("Queue strategy not found") until someone ran
x:Action ReloadSettings; only Directory and Authentication writes
reloaded (DIR-17). The admin UI has to remember a separate reload after
every save, and a script or API client that doesn't gets a server
running stale settings.
x:<Object>/set now reloads the running settings when it created,
updated or destroyed an object they are built from, and broadcasts the
same RegistryChange::Reload over the coordinator as ReloadSettings, so
every node applies it:
- Settings objects (MTA, spam filter, listeners, tracers, Sieve system
scripts, cluster roles, directories, ...: the object types the core,
telemetry, listener and directory builders read) get a full reload.
- Certificates, lookup stores and blocked/allowed IPs get their own
targeted reloads.
- Accounts, domains, roles and other data read as needed, stores (they
take a restart) and applications (their own reload action) get none.
Full reloads are coalesced: a write waits for a reload that started
after it was stored and joins one if it can, so a burst of writes, or
a request with many objects, costs one or two reloads, not one each.
The write itself is never undone. When the reload is refused (build
errors in objects that were working, the rule from the previous
commit), the set response says so in a new x:settingsReload field,
{"applied": false, "description": "Saved, but the running settings
were not reloaded. <object>: <error>"}; {"applied": true} otherwise.
The field is absent when the write needs no reload. The description
helper is shared with ReloadSettings' refusal.
Each reload sends the queue a ReloadSettings event, so the SMTP test
harness's read_event, try_read_event and assert_no_events now pass over
those; expect_reload_settings still waits for one.
system::auto_reload::settings_reload_tests (new): an MtaVirtualQueue
and an MtaDeliverySchedule created over JMAP are in the running
settings with no ReloadSettings, and gone once destroyed; eight
concurrent creates all land; a write whose reload fails is stored and
reported applied: false with the error; a domain write carries no
x:settingsReload. On main the new schedule is missing. The cluster
broadcast test (three nodes, PostgreSQL + NATS) now checks that every
node has a schedule created on node 0 without a reload.
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.
Depends on #38 (reload behavior). This branch is built on it, so until #38 merges the diff shows both.
Problem
A registry write only took effect after an explicit
x:Action ReloadSettings. The one exception was Directory/Authentication writes (crates/jmap/src/registry/set.rs, DIR-17).In the rehearsal, a new
MtaDeliverySchedulestayed unknown to the queue ("Queue strategy not found") until someone reloaded. Any client that forgot the reload left the server running stale settings.Change: automatic reload after a write
When it reloads. After an
x:<Object>/setthat created, updated or destroyed at least one object the running settings are built from, the server reloads those settings. It then broadcastsRegistryChange::Reload, the same eventReloadSettingssends, so every node applies it. Other nodes pick it up about a second later, as they do for ReloadSettings; the cluster test below shows it at once.Which object types reload what (
common::cache::reload::write_reload_target):Why it's safe
New set response field
x:settingsReloadappears only when the write called for a reload:The description comes from the same helper as #38's
ReloadSettingsrefusal.For the admin UI change
The server now reloads by itself after these saves, so a
ReloadSettingscall after each save is redundant. It's still harmless: one more reload.The UI can instead read
x:settingsReloadfrom the set response. Ifappliedis false, show the description, since the object was saved but isn't in effect.ReloadSettingsis still needed for:Test harness
Every reload sends the queue a
ReloadSettingsevent.read_event,try_read_eventandassert_no_eventsin the SMTP test harness now skip those events.expect_reload_settingsstill waits for one.Tests
system::auto_reload::settings_reload_tests(new, RocksDb):MtaVirtualQueueand anMtaDeliverySchedulecreated over JMAP are in the running core with no ReloadSettings. The schedule is gone again once destroyed.applied: falsewith the error.x:settingsReload.main, the schedule is missing after the create (has_schedulefails).cluster::broadcast::cluster_tests(3 nodes,STORE=PostgreSql COORDINATOR=Nats): now creates a schedule on node 0 without a reload, and checks all three nodes have it within 5 s. They had it at once. The same check onmainfails ("node 0 didn't pick up the new delivery schedule").A 3-node rehearsal found that saving an MtaDeliverySchedule left it unknown to the queue ("Queue strategy not found") until someone ran x:Action ReloadSettings; only Directory and Authentication writes reloaded (DIR-17). The admin UI has to remember a separate reload after every save, and a script or API client that doesn't gets a server running stale settings. x:<Object>/set now reloads the running settings when it created, updated or destroyed an object they are built from, and broadcasts the same RegistryChange::Reload over the coordinator as ReloadSettings, so every node applies it: - Settings objects (MTA, spam filter, listeners, tracers, Sieve system scripts, cluster roles, directories, ...: the object types the core, telemetry, listener and directory builders read) get a full reload. - Certificates, lookup stores and blocked/allowed IPs get their own targeted reloads. - Accounts, domains, roles and other data read as needed, stores (they take a restart) and applications (their own reload action) get none. Full reloads are coalesced: a write waits for a reload that started after it was stored and joins one if it can, so a burst of writes, or a request with many objects, costs one or two reloads, not one each. The write itself is never undone. When the reload is refused (build errors in objects that were working, the rule from the previous commit), the set response says so in a new x:settingsReload field, {"applied": false, "description": "Saved, but the running settings were not reloaded. <object>: <error>"}; {"applied": true} otherwise. The field is absent when the write needs no reload. The description helper is shared with ReloadSettings' refusal. Each reload sends the queue a ReloadSettings event, so the SMTP test harness's read_event, try_read_event and assert_no_events now pass over those; expect_reload_settings still waits for one. system::auto_reload::settings_reload_tests (new): an MtaVirtualQueue and an MtaDeliverySchedule created over JMAP are in the running settings with no ReloadSettings, and gone once destroyed; eight concurrent creates all land; a write whose reload fails is stored and reported applied: false with the error; a domain write carries no x:settingsReload. On main the new schedule is missing. The cluster broadcast test (three nodes, PostgreSQL + NATS) now checks that every node has a schedule created on node 0 without a reload.