{"record":{"id":"54e211199484f0d2","repo":"microsoft/FASTER","slug":"unsafe-to-execute-a-state-machine-blockingly-when-under","errorCode":null,"errorMessage":"unsafe to execute a state machine blockingly when under protection","messagePattern":"unsafe to execute a state machine blockingly when under protection","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"cs/src/core/Epochs/EpochProtectedVersionScheme.cs","lineNumber":443,"sourceCode":"\n            // Otherwise, need to check that we are not a duplicate attempt to increment version\n            if (stateMachine.ToVersion() != -1 && actualStateMachine.actualToVersion >= stateMachine.ToVersion())\n                return StateMachineExecutionStatus.FAIL;\n\n            return StateMachineExecutionStatus.RETRY;\n        }\n\n\n        /// <summary>\n        /// Start executing the given state machine\n        /// </summary>\n        /// <param name=\"stateMachine\"> state machine to start </param>\n        /// <param name=\"spin\">whether to spin wait until version transition is complete</param>\n        /// <returns> whether the state machine can be executed. If false, EPVS has advanced version past the target version specified </returns>\n        public bool ExecuteStateMachine(VersionSchemeStateMachine stateMachine, bool spin = false)\n        {\n            if (epoch.ThisInstanceProtected())\n                throw new InvalidOperationException(\"unsafe to execute a state machine blockingly when under protection\");\n            StateMachineExecutionStatus status;\n            do\n            {\n                status = TryExecuteStateMachine(stateMachine);\n            } while (status == StateMachineExecutionStatus.RETRY);\n\n            if (status != StateMachineExecutionStatus.OK) return false;\n\n            if (spin)\n            {\n                while (state.Version != stateMachine.actualToVersion || state.Phase != VersionSchemeState.REST)\n                {\n                    TryStepStateMachine();\n                    Thread.Yield();\n                }\n            }\n\n            return true;","sourceCodeStart":425,"sourceCodeEnd":461,"githubUrl":"https://github.com/microsoft/FASTER/blob/321d872eabda6a0345c8bd76419f89723ed864ae/cs/src/core/Epochs/EpochProtectedVersionScheme.cs#L425-L461","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nepoch.Resume();\nepvs.AdvanceVersionWithCriticalSection(...); // throws: thread is protected\n// after\nepoch.Suspend();\nepvs.AdvanceVersionWithCriticalSection(...);\nepoch.Resume();","handlingStrategy":"validation","validationCode":"bool SafeToAdvance(EpochProtectedVersionScheme epvs)\n{\n    // Never advance while the current thread holds an epoch protection\n    return !epvs.epoch.ThisInstanceProtected(); // or track Resume/Suspend pairs in your own flag\n}","typeGuard":"static bool IsEpochProtected(FASTER.epoch.IEpochProtection p) => p.ThisInstanceProtected();","tryCatchPattern":null,"preventionTips":["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."],"tags":["concurrency","deadlock-prevention","epoch"],"backgroundTag":"invalid-state-transition","analyzedSha":"321d872eabda6a0345c8bd76419f89723ed864ae","analyzedAt":"2026-09-15T22:18:00.693Z","contentChangedAt":"2026-09-15T22:18:00.693Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}