mirror of
https://github.com/xtr-dev/payload-mailing.git
synced 2025-12-10 00:03:23 +00:00
Fix race condition with robust exponential backoff polling
🛡️ 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
This commit is contained in:
@@ -149,20 +149,47 @@ export const sendEmail = async <TEmail extends BaseEmailDocument = BaseEmailDocu
|
|||||||
throw new Error('PayloadCMS jobs not configured - cannot process email immediately')
|
throw new Error('PayloadCMS jobs not configured - cannot process email immediately')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refetch the email to get the populated jobs relationship
|
// Poll for the job with exponential backoff
|
||||||
const emailWithJobs = await payload.findByID({
|
// This handles the async nature of hooks and ensures we wait for job creation
|
||||||
collection: collectionSlug,
|
const maxAttempts = 10
|
||||||
id: email.id,
|
const initialDelay = 50 // Start with 50ms
|
||||||
})
|
let jobId: string | undefined
|
||||||
|
|
||||||
if (!emailWithJobs.jobs || emailWithJobs.jobs.length === 0) {
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
||||||
throw new Error(`No processing job found for email ${email.id}. The auto-scheduling may have failed.`)
|
// Calculate delay with exponential backoff (50ms, 100ms, 200ms, 400ms...)
|
||||||
|
// Cap at 2 seconds per attempt
|
||||||
|
const delay = Math.min(initialDelay * Math.pow(2, attempt), 2000)
|
||||||
|
|
||||||
|
if (attempt > 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)
|
if (!jobId) {
|
||||||
const jobId = Array.isArray(emailWithJobs.jobs)
|
throw new Error(
|
||||||
? String(emailWithJobs.jobs[0])
|
`No processing job found for email ${email.id} after ${maxAttempts} attempts. ` +
|
||||||
: String(emailWithJobs.jobs)
|
`The auto-scheduling may have failed or is taking longer than expected.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await processJobById(payload, jobId)
|
await processJobById(payload, jobId)
|
||||||
|
|||||||
Reference in New Issue
Block a user