stride3d/stride · error · TransactionException

Unable to rollforward. A rollback or rollforward operation…

Error message

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

What it means

Rollforward() rejects re-entrant or concurrent invocation while another rollback/rollforward is running; the stack sets RollInProgress for the duration and this guard prevents the redo loop from being corrupted. This error is thrown when Rollforward is called while RollInProgress is already true.

Solutions

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

Example fix

// before
void OnTransactionRolledForward() => stack.Rollforward(); // re-entrant

// after
void OnTransactionRolledForward()
{
    if (!stack.RollInProgress)
        stack.Rollforward();
}
Defensive patterns

Strategy: validation

Validate before calling

if (!transactionStack.RollInProgress && transactionStack.CanRollforward)
    transactionStack.Rollforward();

Type guard

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

Try / catch

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

Prevention

When it happens

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

Common situations: Redo handlers that themselves trigger redo via property-change feedback loops; queued UI commands firing Rollforward twice; multithreaded redo calls.

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/ac8eaa797c5abb4e. Report an issue: GitHub.

Appendix: source

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

                lastTransaction.Interface.Rollback();
            }
            finally
            {
                RollInProgress = false;
            }
            TransactionRollbacked?.Invoke(this, new TransactionEventArgs(lastTransaction));
        }
    }

    /// <inheritdoc/>
    public void Rollforward()
    {
        lock (lockObject)
        {
            if (!CanRollforward)
                throw new TransactionException("Unable to rollforward. This method cannot be invoked when CanRollforward is false.");
            if (RollInProgress)
                throw new TransactionException("Unable to rollforward. 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.Rollforward();
            }
            finally
            {
                RollInProgress = false;
            }
            TransactionRollforwarded?.Invoke(this, new TransactionEventArgs(lastTransaction));
        }
    }

    public void Resize(int newCapacity)

View on GitHub (pinned to 96fad776d2)