0X8004D104

XACT_E_RECOVERYALREADYDONE (0x8004D104) fix

Windows Errors Intermediate 👁 14 views 📅 May 28, 2026

This error means you called RecoveryComplete twice on the same resource manager in a distributed transaction. Here's why it happens and how to fix it.

When you'll see this error

This error shows up when you're working with Microsoft Distributed Transaction Coordinator (MSDTC) and a resource manager — typically SQL Server or a custom transactional resource manager. You'll hit it if you call IRMHelper::RecoveryComplete or the equivalent API more than once for the same resource manager instance. I've seen it most often in custom COM+ applications that do manual recovery after a crash, where recovery logic runs twice by accident — maybe from a retry loop that doesn't check state first, or from two separate components both trying to finalize recovery.

What's actually happening

The error code 0x8004D104 maps to XACT_E_RECOVERYALREADYDONE. The root cause is simple: MSDTC tracks the recovery state per resource manager. Once you signal that recovery is complete, the transaction manager marks that RM as recovered. Calling it again is invalid — the RM already reported in. The system isn't designed to handle duplicate recovery calls because recovery is a one-shot process: the RM replays its transaction log, tells MSDTC it's done, and normal transaction processing begins. A second call suggests either a bug in your code or a misconfigured retry mechanism.

Why does MSDTC enforce this? Because allowing duplicate recovery calls could cause race conditions. Imagine two threads both calling RecoveryComplete — the first one finishes, but the second one could overwrite state or trigger a false recovery completion while new transactions are still being reconciled. So the API returns this error as a hard stop.

The fix

  1. Identify where RecoveryComplete is called. Search your codebase for RecoveryComplete, IRMHelper, or ITransactionResourceManager. In .NET that's often System.Transactions or P/Invoke to ole32.dll. In C++ look for CoCreateInstance of a resource manager spitter.
  2. Check for duplicate calls. The simplest fix is to add a boolean flag, like bool recoveryDone = false; — set it true after the first call, and guard subsequent calls with if (!recoveryDone). This prevents the double call entirely.
  3. If you're using a retry loop, wrap RecoveryComplete outside the retry logic. Don't put it inside a try-catch that retries on failure — if the first call succeeds but the loop doesn't exit, the next iteration will fail with this error. Move the call to after the loop completes.
  4. If the error comes from SQL Server's MSDTC integration, check the SQL Server error log. Sometimes the RM manager in SQL (like when using linked servers or distributed queries) calls RecoveryComplete internally. Restarting the SQL Server service can reset the state, but only if the underlying issue is a transient glitch — not if it's a code problem.
  5. For COM+ applications, review the component's recovery settings. In the Component Services console, right-click your COM+ application, go to Properties > Advanced, and make sure "Enable automatic recovery" is set appropriately. If it's on, the system may call RecoveryComplete for you, and your code shouldn't call it again.
  6. Kill lingering MSDTC processes as a last resort. Open an admin command prompt and run net stop msdtc && net start msdtc. This resets all MSDTC state, including recovery flags. But this is a sledgehammer — use it only for testing or if you're certain no active transactions are in flight.

If the fix doesn't work

Check three things:

  • Is your code calling it from multiple threads or processes? If two separate instances of your app run simultaneously, each might try to recover the same RM. You need a mutex or a single-instance design.
  • Are you using a third-party library or driver that calls RecoveryComplete behind the scenes? Some ODBC drivers or ORMs do this when reconnecting. Look at the stack trace to see what's making the call — the culprit isn't always your code.
  • Is the resource manager already marked as recovered from a previous crash? If your app crashed after an earlier RecoveryComplete, and you restart it without a clean MSDTC reset, the RM may still be in a "recovered" state. In that case, don't call RecoveryComplete at all — check the RM's status first using GetTransactionInfo or similar.

One more thing: if you're writing a custom resource manager, you can avoid this entirely by designing your recovery flow to be idempotent. That means the second call should be a no-op, not an error. But the Windows API doesn't give you that luxury — it returns an error. So the pragmatic fix is the guard flag. Works every time.

Was this solution helpful?