dotnet/orleans · error · InvalidOperationException

Load must complete successfully before Store can be called a

Error message

Load must complete successfully before Store can be called again after a failed Store operation.

What it means

Thrown by AzureTableTransactionalStateStorage.Store when the _storeRequiresLoad flag is set, which flips true only after a prior Store threw. The storage is now in an uncertain state and insists on a fresh Load to re-sync the etag and state before any further write.

Source

Thrown at src/Azure/Orleans.Transactions.AzureStorage/TransactionalState/AzureTableTransactionalStateStorage.cs:129

                    TransactionalStateMetaData metadata = JsonConvert.DeserializeObject<TransactionalStateMetaData>(this.key.Metadata!, this.jsonSettings)!;
                    var result = new TransactionalStorageLoadResponse<TState>(this.key.ETag.ToString(), committedState, this.key.CommittedSequenceId, metadata, PrepareRecordsToRecover);
                    _storeRequiresLoad = false;
                    return result;
                }
            }
            catch (Exception ex)
            {
                LogErrorTransactionalStateLoadFailed(ex);
                throw;
            }
        }

        public async Task<string> Store(string? expectedETag, TransactionalStateMetaData metadata, List<PendingTransactionState<TState>>? statesToPrepare, long? commitUpTo, long? abortAfter)
        {
            if (_storeRequiresLoad)
            {
                throw new InvalidOperationException("Load must complete successfully before Store can be called again after a failed Store operation.");
            }

            var keyETag = key.ETag.ToString();
            if ((!string.IsNullOrWhiteSpace(keyETag) || !string.IsNullOrWhiteSpace(expectedETag)) && keyETag != expectedETag)
            {
                throw new ArgumentException(nameof(expectedETag), "Etag does not match");
            }

            try
            {
                return await StoreCore(metadata, statesToPrepare, commitUpTo, abortAfter).ConfigureAwait(false);
            }
            catch
            {
                _storeRequiresLoad = true;
                throw;
            }
        }

View on GitHub (pinned to fca799fa70)

Solutions

  1. Call Load on the transactional state storage before retrying Store.
  2. Do not retry Store on the same instance after a failure; let the runtime re-activate the grain.
  3. Investigate and fix the root cause of the original Store failure (etag conflict, throttling).

Example fix

// before
try { await storage.Store(...); }
catch { await storage.Store(...); } // throws again: _storeRequiresLoad

// after
try { await storage.Store(...); }
catch { await storage.Load(); /* re-sync then retry */ await storage.Store(...); }
Defensive patterns

Strategy: validation

Validate before calling

if (_storeRequiresLoad) await storage.Load();
await storage.Store(etag, metadata, states, commit, abort);

Try / catch

try { await storage.Store(...); }
catch (Exception) { await storage.Load(); throw; }

Prevention

When it happens

Trigger: A previous Store call failed (network error, conflict, etc.), setting _storeRequiresLoad; the caller retried Store without calling Load first.

Common situations: Retry loops that call Store again after InconsistentStateException; an exception in the transaction commit path that the surrounding code swallows before re-attempting.

Related errors


AI-assisted analysis of dotnet/orleans@fca799fa70 (2026-08-13). Data as JSON: /api/errors/9faaca0f23d67dd3. Report an issue: GitHub.