on master help sign in
Fix bug #18: enforce maxForwardsPerServer on the send path

handleSendMessages staged retry-queue rows with only a duplicate check, so a burst of sends to recipients on one peer could grow that peer's outbox past the documented per-peer ceiling; only forwardQueuedToServer respected it. Skip staging for peers at the cap — they still get the immediate post-commit delivery attempt via forkForwardsTo.

commit1a32dc024db711dc3e3606241421cc510324a9fb authoriko <mail@iko.soy> dateThu 11 Jun 2026, 14:04 (4 months ago) parent916b7a3
1 file changed+5−1
server.knot+5−1
server.knot+5−1
@@ -862,9 +862,13 @@ handleSendMessages = \messages authorization clientIp -> do
862862 }) messages
863863 forEach staged (\c -> do
864864 appendMessage c.to c.encryptedBlob t
865+ -- Stage outbox rows for peers under their `maxForwardsPerServer`
866+ -- ceiling. Peers at the cap still get the immediate post-commit
867+ -- delivery attempt (`forkForwardsTo`) — only the retry-queue row
868+ -- is skipped.
865869 when federationEnabled (do
866870 fwds <- *forwards
867− let newFwds = map (\s -> {toServer: s, toKey: c.to, c.encryptedBlob, retries: 0}) (filter (\s -> not any (forwardMatches c.encryptedBlob s) fwds) c.remoteServers)
871+ let newFwds = map (\s -> {toServer: s, toKey: c.to, c.encryptedBlob, retries: 0}) (filter (\s -> not (any (forwardMatches c.encryptedBlob s) fwds) && countWhere (\f -> f.toServer == s) fwds < maxForwardsPerServer) c.remoteServers)
868872 *forwards = union fwds newFwds))
869873 yield staged)
870874 when federationEnabled (forEach committed (\c -> fork (forkForwardsTo c.to c.encryptedBlob c.remoteServers)))