microsoft/FASTER · error · FasterException

Cannot use GrowIndex when using non-async sessions

Error message

Cannot use GrowIndex when using non-async sessions

What it means

GrowIndex resizes the hash index via a state machine that must not run on an epoch-protected thread; otherwise the epoch system cannot advance threads during the resize. If the current thread holds epoch protection (non-async/blocking session usage), the call throws instead of proceeding.

Solutions

  1. Call GrowIndex from a context without active epoch protection (e.g., main async session loop, separate task)
  2. Migrate to async sessions and check GrowIndexAsync, which manages epoch protection correctly
  3. Restructure so index growth is triggered by background maintenance code rather than inline request handling

Example fix

// before: called while epoch protected
fasterKV.epoch.Resume(); // user-managed protection
fasterKV.GrowIndex(); // throws if ThisInstanceProtected
// after: call from unprotected context
bool grew = fasterKV.GrowIndex(); // ensure caller thread is NOT epoch-protected
Defensive patterns

Strategy: try-catch

Validate before calling

null

Try / catch

try { fasterKV.GrowIndex(); }
catch (FasterException ex) when (ex.Message.Contains("GrowIndex when using non-async sessions"))
{
    // defer growth to a background task without epoch protection
    await Task.Run(() => fasterKV.GrowIndex());
}

Prevention

When it happens

Trigger: Calling GrowIndex from a thread that currently has epoch protection active (e.g., inside a synchronous session operation, within epoch-protected regions, or a caller that manually entered epoch protection).

Common situations: Synchronous (non-async) sessions trying to grow the index inline; invoking GrowIndex from inside upsert/read callbacks or epoch-protected test harness code.

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

Appendix: source

Thrown at cs/src/core/Index/FASTER/FASTER.cs:727

                internalStatus = InternalDelete(ref key, keyHash, ref context, ref pcontext, fasterSession, serialNo);
            while (HandleImmediateRetryStatus(internalStatus, fasterSession, ref pcontext));

            var status = HandleOperationStatus(fasterSession.Ctx, ref pcontext, internalStatus);

            Debug.Assert(serialNo >= fasterSession.Ctx.serialNum, "Operation serial numbers must be non-decreasing");
            fasterSession.Ctx.serialNum = serialNo;
            return status;
        }

        /// <summary>
        /// Grow the hash index by a factor of two. Make sure to take a full checkpoint
        /// after growth, for persistence.
        /// </summary>
        /// <returns>Whether the grow completed</returns>
        public bool GrowIndex()
        {
            if (epoch.ThisInstanceProtected())
                throw new FasterException("Cannot use GrowIndex when using non-async sessions");

            if (!StartStateMachine(new IndexResizeStateMachine())) return false;

            epoch.Resume();

            try
            {
                while (true)
                {
                    SystemState _systemState = SystemState.Copy(ref systemState);
                    if (_systemState.Phase == Phase.PREPARE_GROW)
                        ThreadStateMachineStep<Empty, Empty, Empty, NullFasterSession>(null, NullFasterSession.Instance, default);
                    else if (_systemState.Phase == Phase.IN_PROGRESS_GROW)
                        SplitBuckets(0);
                    else if (_systemState.Phase == Phase.REST)
                        break;
                    epoch.ProtectAndDrain();
                    Thread.Yield();

View on GitHub (pinned to 321d872eab)