- Shipped
- September 23, 2026 at 7:50 AM UTC
- Author
- Kamo
- Commit
- 90509f4
Spring's default single scheduler thread is shared by every @Scheduled method in this service — the 1-second campaign pulse, the drip engine's tick, and ReminderSchedulerService's own 60-second sweep among roughly a dozen others. processRemindersLocked sent every due reminder's email/popup/ sms, one blocking call at a time, on that shared thread: a sweep with several due reminders held it, and everything else scheduled on the pod, for as long as the whole batch took to send. Each reminder's send now runs on a small fixed thread pool (4 workers, named calendar-reminder-sender), the same principle DripEngine already applies by handing its whole pass off to a worker thread — applied here per reminder instead of per pass, since the slow part is the blocking SMTP/notification send rather than the DB sweep itself. The calling thread still waits for the whole batch (bounded by a 90s drain timeout) before processRemindersLocked returns, which is what keeps the 'email-calendar-reminders' lease covering the whole pass: a lease released while sends were still in flight elsewhere would let another pod pick up the SAME due reminders and send them twice. What changes is only how long that wait takes — up to fourfold less, never worse than the old sequential loop. **************** fails without the change: it proves each send runs on the pool rather than the calling thread, and that the call still blocks until every send has finished.
