Skip to main content

Anti-Pattern: Untracked Fire-and-Forget

🔴 The Problem

Launching background operations using Task.Run or new Thread() within an ASP.NET request without any mechanism to track or wait for their completion, and without proper error handling.

Why it's Critical in WEP NG

In Payment Callbacks (WEPCustomerController), the system responds "OK" to the payment provider before the transaction is actually recorded in the database, relying on a detached thread to do the work.

The Risks

  1. Data Loss (Financial): If the AppPool recycles (deploy, config change, scheduled recycle) or the server crashes while the thread is running, the thread is killed instantly. The payment is lost: the bank took the money, but WEP has no record.
  2. Silent Failures: If the detached thread throws an exception, it is often swallowed or crashes the process (depending on .NET version/config), leaving no trace and no way to retry.
  3. Race Conditions: Accessing the DbContext from a background thread while the request thread might still be disposing it (or accessing related resources) leads to undefined behavior.

🚱 Real WEP Example

// ❌ BAD: Financial Casino
public ActionResult PayboxUpdate()
{
// ... params parsing ...

// FIRE AND FORGET - HOPING FOR THE BEST
Task.Run(() =>
{
// If the server restarts NOW, this money is gone.
// If this throws, nobody knows.
PaymentModeFactory.HandlePayboxTransaction(params);
});

return Content("OK"); // We lied to Paybox, we haven't processed it yet.
}

💡 The Solution

1. Short-Term: Sync/Async-Wait (The "Honest" Approach)

Process the transaction during the request. Do not return "OK" until the data is safe.

// ✅ GOOD: Reliable
public async Task<ActionResult> PayboxUpdate()
{
// ... params parsing ...

try
{
// Await ensures we don't return until DB is updated
await PaymentModeFactory.HandlePayboxTransactionAsync(params);
return Content("OK");
}
catch (Exception ex)
{
_logger.Error("Payment Failed", ex);
return new HttpStatusCodeResult(500); // Paybox will try again later!
}
}

2. Long-Term: Reliable Background Job

If the processing is truly slow (>30s), put it in a persistent queue (Hangfire).

// ✅ BETTER: Resilient
public ActionResult PayboxUpdate()
{
// Enqueue job. Even if server crashes, Hangfire is backed by DB and will retry.
BackgroundJob.Enqueue(() => PaymentModeFactory.HandlePayboxTransaction(params));
return Content("OK");
}

🔍 Detection & Verification

Search for detached tasks without an await mechanism. Use the Async Hygiene Checklist for detailed verification.

# Forensic Grep: Find "Task.Run" or "StartNew"
grep -r "Task\.Run" . --include="*.cs"
grep -r "StartNew" . --include="*.cs"