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
- Move the AdvanceVersionWithCriticalSection / ExecuteStateMachine call out of any epoch-protected region (call epoch.Suspend() first, ExecuteStateMachine, then epoch.Resume()).
- Perform version advancement on a dedicated thread that is not epoch-protected.
- 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
- Always pair epoch.Resume()/Suspend() and never call version advancement between them.
- Advance versions from a dedicated unprotected coordinator thread.
- Track 'am I inside an epoch-protected section' with an AsyncLocal/thread-local flag in wrapper code.
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
- Unexpected sealed buffer found
- Invalid release by non-owner thread
- BeginLockable cannot be called twice (call EndLockable…
- LockableUnsafeContext requires
- LockableContext requires
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)