stride3d/stride · error · TransactionException
This transaction has already been completed.
Error message
This transaction has already been completed.
What it means
Thrown by Transaction.Complete when the transaction's referenceCount has already reached zero, i.e. the transaction was already completed. Complete decrements the reference count and finishes the transaction when the last reference is released.
Solutions
- Call Complete() exactly once per transaction; do not also call it from Dispose paths you control
- Track completion with a bool flag before calling Complete again
- If sharing, have a single owner responsible for completing the transaction
Example fix
// before
using var transaction = stack.CreateTransaction();
transaction.Complete(); // then Dispose path may complete again
// after
var transaction = stack.CreateTransaction();
try { /* edits */ }
finally { transaction.Dispose(); } // single completion path Defensive patterns
Strategy: try-catch
Validate before calling
if (completed) return; completed = true; transaction.Complete();
Type guard
bool CanComplete(Transaction t) => !completedFlags.GetValueOrDefault(t, false);
Try / catch
try { transaction.Complete(); }
catch (TransactionException ex) when (ex.Message == "This transaction has already been completed.")
{
// already completed elsewhere; treat as no-op
} Prevention
- Call Complete() exactly once per transaction; prefer a single Dispose-based completion path
- Designate one owner responsible for completing shared transactions
- Track completion state locally before calling Complete
When it happens
Trigger: Calling Transaction.Complete() twice on the same transaction — commonly calling both Dispose() and Complete(), or keeping shared references and completing from multiple code paths.
Common situations: Using statements combined with explicit Complete() calls, transaction objects shared between services that each call Complete.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- This transaction has already been completed.
- Trying to dispose a lock that has already been released.
- This operation is already frozen.
- A disposed operation cannot be rollbacked.
- This operation is already in progress
AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14).
Data as JSON: /api/errors/40cb9902db011794.
Report an issue: GitHub.
Appendix: source
Thrown at sources/core/Stride.Core.Design/Transactions/Transaction.cs:64
}
/// <inheritdoc/>
public void Continue()
{
synchronizationContext = SynchronizationContext.Current;
}
/// <inheritdoc/>
public void AddReference()
{
referenceCount++;
}
/// <inheritdoc/>
public void Complete()
{
if (referenceCount == 0)
throw new TransactionException("This transaction has already been completed.");
// Transaction might be kept alive by others, only process it if last reference
// Note: this KeepAlive() and Complete() are not thread-safe, no need to use interlocked
if (referenceCount == 1)
{
// Disabling synchronization context check: when we await for dispatcher task we always resume in a different SC so it makes it difficult to enforce this rule.
//if (synchronizationContext != SynchronizationContext.Current)
// throw new TransactionException("This transaction is being completed in a different synchronization context.");
TryMergeOperations();
transactionStack.CompleteTransaction(this);
// Don't keep reference to synchronization context after completion
synchronizationContext = null;
}
--referenceCount;
}
View on GitHub (pinned to 96fad776d2)