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
- Call Load on the transactional state storage before retrying Store.
- Do not retry Store on the same instance after a failure; let the runtime re-activate the grain.
- 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
- Never retry Store on the same instance after a failure without Load.
- Let the grain reactivate on persistent store errors.
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
- Storage state corrupted: no record for committed state v{thi
- Etag does not match
- Could not load a consistent Azure Table transactional state
- Azure Table transactional state storage conflict. Partition=
- Data too large to write to table. Size={0} MaxSize={1}
AI-assisted analysis of dotnet/orleans@fca799fa70 (2026-08-13).
Data as JSON: /api/errors/9faaca0f23d67dd3.
Report an issue: GitHub.