microsoft/FASTER · error · InvalidOperationException

unsafe to execute a state machine blockingly when under…

Error message

unsafe to execute a state machine blockingly when under protection

What it means

EpochProtectedVersionScheme (EPVS) protects epochs for concurrent readers while version advancement happens on a separate thread. ExecuteStateMachine runs the version-state machine with blocking/spinning waits, which deadlocks if the calling thread is itself inside epoch protection (ThisInstanceProtected). The library throws InvalidOperationException eagerly to prevent a self-deadlock: a protected thread can never observe the epoch transitions it is blocking on.

Solutions

  1. Move the AdvanceVersionWithCriticalSection / ExecuteStateMachine call out of any epoch-protected region (call epoch.Suspend() first, ExecuteStateMachine, then epoch.Resume()).
  2. Perform version advancement on a dedicated thread that is not epoch-protected.
  3. If blocking isn't required, use a non-spinning invocation and poll for completion later from an unprotected context.

Example fix

// before
epoch.Resume();
epvs.AdvanceVersionWithCriticalSection(...); // throws: thread is protected
// after
epoch.Suspend();
epvs.AdvanceVersionWithCriticalSection(...);
epoch.Resume();
Defensive patterns

Strategy: validation

Validate before calling

bool SafeToAdvance(EpochProtectedVersionScheme epvs)
{
    // Never advance while the current thread holds an epoch protection
    return !epvs.epoch.ThisInstanceProtected(); // or track Resume/Suspend pairs in your own flag
}

Type guard

static bool IsEpochProtected(FASTER.epoch.IEpochProtection p) => p.ThisInstanceProtected();

Prevention

When it happens

Trigger: Calling ExecuteStateMachine (directly or via AdvanceVersionWithCriticalSection) from a thread that has called epoch.Resume()/EnterEpoch (e.g., inside an FASTER session's epoch-protected callback or between Resume and Suspend) with the intent to block until the version transition completes.

Common situations: Developers calling AdvanceVersionWithCriticalSection inside a Read/ReadModified callback or an epoch-resumed section (checkpoint callbacks, middleware wrapped in Resume/Suspend) where the state machine's spin-wait waits for epoch claims that the calling thread itself holds open.

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 microsoft/FASTER@321d872eab (2026-09-15). Data as JSON: /api/errors/54e211199484f0d2. Report an issue: GitHub.

Appendix: source

Thrown at cs/src/core/Epochs/EpochProtectedVersionScheme.cs:443

            // Otherwise, need to check that we are not a duplicate attempt to increment version
            if (stateMachine.ToVersion() != -1 && actualStateMachine.actualToVersion >= stateMachine.ToVersion())
                return StateMachineExecutionStatus.FAIL;

            return StateMachineExecutionStatus.RETRY;
        }


        /// <summary>
        /// Start executing the given state machine
        /// </summary>
        /// <param name="stateMachine"> state machine to start </param>
        /// <param name="spin">whether to spin wait until version transition is complete</param>
        /// <returns> whether the state machine can be executed. If false, EPVS has advanced version past the target version specified </returns>
        public bool ExecuteStateMachine(VersionSchemeStateMachine stateMachine, bool spin = false)
        {
            if (epoch.ThisInstanceProtected())
                throw new InvalidOperationException("unsafe to execute a state machine blockingly when under protection");
            StateMachineExecutionStatus status;
            do
            {
                status = TryExecuteStateMachine(stateMachine);
            } while (status == StateMachineExecutionStatus.RETRY);

            if (status != StateMachineExecutionStatus.OK) return false;

            if (spin)
            {
                while (state.Version != stateMachine.actualToVersion || state.Phase != VersionSchemeState.REST)
                {
                    TryStepStateMachine();
                    Thread.Yield();
                }
            }

            return true;

View on GitHub (pinned to 321d872eab)