dotnet/orleans · error · ArgumentException
Etag does not match
Error message
Etag does not match
What it means
Thrown by AzureTableTransactionalStateStorage.Store when the expectedETag argument does not match the etag currently held in the loaded key entity. This is optimistic-concurrency control: the row was changed by another writer between Load and Store.
Source
Thrown at src/Azure/Orleans.Transactions.AzureStorage/TransactionalState/AzureTableTransactionalStateStorage.cs:135
}
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;
}
}
private async Task<string> StoreCore(TransactionalStateMetaData metadata, List<PendingTransactionState<TState>>? statesToPrepare, long? commitUpTo, long? abortAfter)
{
if (string.IsNullOrEmpty(key.ETag.ToString()) && string.IsNullOrEmpty(key.Metadata))
{
// A split prepare can persist the fresh key before phase three publishes the incoming metadata.View on GitHub (pinned to fca799fa70)
Solutions
- Re-run Load to obtain the current etag, then retry Store with the fresh etag.
- Reduce concurrency on the affected grain or serialize transactional access.
- Ensure only one activation owns the grain at a time (single-activation placement).
Example fix
// before await storage.Store(staleETag, metadata, states, commit, abort); // etag mismatch // after var fresh = await storage.Load(); await storage.Store(fresh.ETag, metadata, states, commit, abort);
Defensive patterns
Strategy: retry
Try / catch
try { await storage.Store(expectedETag, ...); }
catch (ArgumentException ex) when (ex.Message == "Etag does not match")
{ var fresh = await storage.Load(); await storage.Store(fresh.ETag, ...); } Prevention
- Always refresh the etag via Load before retrying a contested Store.
- Reduce concurrent writers on the same transactional grain.
When it happens
Trigger: Concurrent transactions on the same grain modifying the same partition; a stale etag passed because Load was not re-run after a prior failure.
Common situations: Multiple silos/activations racing on the same transactional grain; retries that reuse an old etag; _storeRequiresLoad not honored.
Related errors
- Could not load a consistent Azure Table transactional state
- Azure Table transactional state storage conflict. Partition=
- Table storage condition not Satisfied. GrainType: {0}, Grai
- Storage state corrupted: no record for committed state v{thi
- Load must complete successfully before Store can be called a
AI-assisted analysis of dotnet/orleans@fca799fa70 (2026-08-13).
Data as JSON: /api/errors/ac6e9cc30bb6f8b1.
Report an issue: GitHub.