fix: serialise redirect writes so concurrent saves cannot store a loop (WR-10)

The save-time simulation read the rules at READ COMMITTED without a lock,
so A -> B and B -> A written concurrently both passed. CheckRedirect,
AddressChanged and every batch now take a transaction-level advisory lock
before they read the rules.
This commit is contained in:
2026-10-27 15:29:08 +01:00
parent cb614cb1fb
commit c2f136ad7d
5 changed files with 69 additions and 1 deletions

View File

@@ -241,6 +241,11 @@ func (s *Service) runBatch(ctx context.Context, opts BatchOptions, build func(ct
if opts.Via != "" {
ctx = WithVia(ctx, opts.Via)
}
// One lock for the whole batch, so a refused change rolled back to
// its savepoint does not let another redirect writer in (WR-10).
if err := lockRedirectWrites(tx); err != nil {
return err
}
changes, err := build(ctx, tx)
if err != nil {
return err