dotnet/orleans · error · InvalidOperationException

Load must complete successfully before Store can be called…

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.

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.

Appendix: 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)