stride3d/stride · error · TransactionException
This transaction has already been completed.
Error message
This transaction has already been completed.
What it means
DummyTransaction.Dispose completes the transaction, but a transaction can only be completed once. If isCompleted is already true, Dispose throws TransactionException instead of silently re-completing. This guards the invariant that each transaction lifecycle ends exactly once.
Solutions
- Dispose each transaction exactly once
- Track completion with isCompleted before calling Dispose again
- Wrap Dispose in a guard that checks/sets a disposed flag
- Prefer a single using statement over manual Dispose calls
Example fix
// before transaction.Dispose(); transaction.Dispose(); // throws TransactionException // after if (transaction is DummyTransaction dt && !dt.IsCompleted) transaction.Dispose();
Defensive patterns
Strategy: validation
Validate before calling
if (transaction is DummyTransaction dt && dt.IsCompletedForTest) return; // skip double dispose transaction?.Dispose();
Type guard
static bool CanDispose(DummyTransaction t) => !t.IsCompleted;
Try / catch
try { transaction.Dispose(); }
catch (TransactionException) { /* already completed — treat as success */ } Prevention
- Use a single using statement per transaction
- Never mix manual Complete() with using/Dispose
- Keep transaction ownership in one code path
When it happens
Trigger: Calling Dispose twice on the same DummyTransaction (e.g. 'using' plus an explicit Dispose call, or two nested using blocks over the same instance). The test Dispose_CalledMultipleTimes_DoesNotThrow exercises this path.
Common situations: Double-dispose patterns: wrapping an already-managed transaction in another using, disposing in both a finally block and a using, or re-disposing a cached transaction object.
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
- Trying to dispose a lock that has already been released.
- This transaction has already been completed.
- Tried to unregister soundEffectInstance while not contained…
- The default profile cannot be unloaded
- Unable to unload the current profile.
AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14).
Data as JSON: /api/errors/7b9d054dd913aba7.
Report an issue: GitHub.
Appendix: source
Thrown at sources/presentation/Stride.Core.Presentation/Services/DummyTransaction.cs:27
/// A dummy transaction created when <see cref="IUndoRedoService.UndoRedoInProgress"/> is true and a new transaction is requested.
/// Any operation pushed during this transaction will throw.
/// </summary>
internal class DummyTransaction : ITransaction, IReadOnlyTransaction
{
private bool isCompleted;
public Guid Id { get; } = Guid.NewGuid();
public IReadOnlyList<Operation> Operations { get; } = [];
public bool IsEmpty => true;
public TransactionFlags Flags => TransactionFlags.None;
public void Dispose()
{
if (isCompleted)
throw new TransactionException("This transaction has already been completed.");
Complete();
}
public void Continue()
{
}
public void Complete()
{
if (isCompleted)
throw new TransactionException("This transaction has already been completed.");
isCompleted = true;
}
public void AddReference()
{View on GitHub (pinned to 96fad776d2)