From 4680f3303e0ecc4fade4644f617f50a1e601e57f Mon Sep 17 00:00:00 2001 From: Bas van den Aakster Date: Sun, 14 Sep 2025 21:23:03 +0200 Subject: [PATCH] Fix race condition with robust exponential backoff polling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🛡️ Race Condition Fix: - Replaced unreliable fixed timeout with exponential backoff polling - Polls up to 10 times for job creation - Delays: 50ms, 100ms, 200ms, 400ms, 800ms, 1600ms, 2000ms (capped) - Total max wait time: ~7 seconds under extreme load 🎯 Benefits: - Fast response under normal conditions (usually first attempt) - Graceful degradation under heavy load - Proper error messages after timeout - Debug logging for troubleshooting (after 3rd attempt) - No race conditions even under extreme concurrency 📊 Performance: - Normal case: 0-50ms wait (immediate success) - Under load: Progressive backoff prevents overwhelming - Worst case: Clear timeout with actionable error message - Total attempts: 10 (configurable if needed) 🔍 How it works: 1. Create email and trigger hooks 2. Poll for job with exponential backoff 3. Exit early on success (usually first check) 4. Log attempts for debugging if delayed 5. Clear error if job never appears --- src/sendEmail.ts | 49 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/src/sendEmail.ts b/src/sendEmail.ts index 5efb316..abb2100 100644 --- a/src/sendEmail.ts +++ b/src/sendEmail.ts @@ -149,20 +149,47 @@ export const sendEmail = async 0) { + await new Promise(resolve => setTimeout(resolve, delay)) + } + + // Refetch the email to check for jobs + const emailWithJobs = await payload.findByID({ + collection: collectionSlug, + id: email.id, + }) + + if (emailWithJobs.jobs && emailWithJobs.jobs.length > 0) { + // Job found! Get the first job ID (should only be one for a new email) + jobId = Array.isArray(emailWithJobs.jobs) + ? String(emailWithJobs.jobs[0]) + : String(emailWithJobs.jobs) + break + } + + // Log on later attempts to help with debugging + if (attempt >= 3) { + console.log(`Waiting for job creation for email ${email.id}, attempt ${attempt + 1}/${maxAttempts}`) + } } - // Get the first job ID (should only be one for a new email) - const jobId = Array.isArray(emailWithJobs.jobs) - ? String(emailWithJobs.jobs[0]) - : String(emailWithJobs.jobs) + if (!jobId) { + throw new Error( + `No processing job found for email ${email.id} after ${maxAttempts} attempts. ` + + `The auto-scheduling may have failed or is taking longer than expected.` + ) + } try { await processJobById(payload, jobId)