stride3d/stride · error · TransactionException

Unable to rollback. A rollback or rollforward operation is…

Error message

Unable to rollback. A rollback or rollforward operation is already in progress.

What it means

Rollback() rejects re-entrant or concurrent invocation while another rollback/rollforward is running; the stack sets RollInProgress during the operation and this guard prevents corrupting the position while a rollback loop is active. This error is thrown when Rollback is called while RollInProgress is already true.

Solutions

  1. Never call Rollback/Rollforward from inside transaction rollback/rollforward handlers.
  2. Serialize undo/redo calls onto a single thread or use a lock/flag around stack operations.
  3. Suppress re-entrant notifications while a rollback is running (guard flags in event handlers).
  4. Check transactionStack.RollInProgress before invoking Rollback.

Example fix

// before
void OnTransactionRolledBack() => stack.Rollback(); // re-entrant

// after
void OnTransactionRolledBack()
{
    if (!stack.RollInProgress)
        stack.Rollback();
}
Defensive patterns

Strategy: validation

Validate before calling

if (!transactionStack.RollInProgress && transactionStack.CanRollback)
    transactionStack.Rollback();

Type guard

bool IsRollIdle(TransactionStack stack) => !stack.RollInProgress;

Try / catch

try
{
    transactionStack.Rollback();
}
catch (TransactionException ex) when (ex.Message.Contains("already in progress"))
{
    // re-entrant or concurrent call; ignore or defer
}

Prevention

When it happens

Trigger: Calling Rollback() from inside a transaction's Rollback handler (re-entrancy), or from another thread while a rollback/rollforward is in progress.

Common situations: Undo handlers that themselves trigger undo (e.g. property-change feedback loops); UI double-clicks or queued commands firing Rollback twice; multithreaded code calling Rollback concurrently.

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


AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/3632270048fff1e9. Report an issue: GitHub.

Appendix: source

Thrown at sources/core/Stride.Core.Design/Transactions/TransactionStack.cs:223

                // Complete parent transactions
                if ((transaction.Flags & TransactionFlags.KeepParentsAlive) != 0)
                {
                    foreach (var parentTransaction in transactionsInProgress.Reverse())
                        parentTransaction.Complete();
                }
            }
        }
    }

    /// <inheritdoc/>
    public void Rollback()
    {
        lock (lockObject)
        {
            if (!CanRollback)
                throw new TransactionException("Unable to rollback. This method cannot be invoked when CanRollback is false.");
            if (RollInProgress)
                throw new TransactionException("Unable to rollback. A rollback or rollforward operation is already in progress.");
            if (transactionsInProgress.Count > 0)
                throw new TransactionException("Unable to rollback. A transaction is in progress.");

            var lastTransaction = transactions[--currentPosition];
            RollInProgress = true;
            try
            {
                lastTransaction.Interface.Rollback();
            }
            finally
            {
                RollInProgress = false;
            }
            TransactionRollbacked?.Invoke(this, new TransactionEventArgs(lastTransaction));
        }
    }

    /// <inheritdoc/>

View on GitHub (pinned to 96fad776d2)