- Shipped
- 4 Septemba 2026, 20:34 UTC
- Author
- Kamo
- Commit
- da88f00
Scaling MediaService to two replicas would have doubled every scheduled sweep it owns. Two of those send mail — MeetingReminderSweep and ChatEmailNoticeService — so the first deploy would have put two of every meeting reminder and two of every unread-chat notice in members' inboxes. Others publish scheduled Canva posts, import provider recordings, delete recordings under a retention policy and reconcile marketing attribution: doubled, that is a duplicate post, a duplicate asset, a race on a destructive delete, and double-counted conversions. Each now takes a named lease through SingletonTaskRunner before doing anything. @Transactional STAYS ON THE OUTER METHOD in the three sweeps that had it. Spring applies it with a proxy, and the inner method is reached through a this:: reference, which never goes through one — moving the annotation inward would have left the sweep silently non-transactional. That is the same self-invocation trap that has caught this codebase five times, and it is precisely why the lock is taken by an explicit call rather than by an annotation of its own. The body still runs inside the transaction, because the wrapper opens it. **************** is deliberately NOT locked: it re-binds THIS pod's own NATS subscription, so it has to run on every pod. Locking it would leave every pod but the leaseholder unsubscribed. 367 tests pass.