{"record":{"id":"c0dca838eaf5bdd4","repo":"dotnet/orleans","slug":"blob-storage-condition-not-satisfied-blobname-b","errorCode":null,"errorMessage":"Blob storage condition not Satisfied. BlobName: {blob.Name}, Container: {blob.BlobContainerName}, CurrentETag: {currentETag}","messagePattern":"Blob storage condition not Satisfied\\. BlobName: (.+?), Container: (.+?), CurrentETag: (.+?)","errorType":"exception","errorClass":"InconsistentStateException","httpStatus":null,"severity":"error","filePath":"src/Azure/Orleans.Persistence.AzureStorage/Providers/Storage/AzureBlobStorage.cs","lineNumber":330,"sourceCode":"            {\n                loadedState = this.ConvertFromStorageFormat<T>(new BinaryData(content));\n                LogWarningLargePayloadFallback(contentLength, grainType, grainId, eTag, blobName, containerName);\n            }\n\n            LogTraceDataRead(grainType, grainId, eTag, blobName, containerName);\n            grainState.ETag = eTag;\n            return loadedState;\n        }\n\n        private static async Task<TResult> DoOptimisticUpdate<TState, TResult>(Func<TState, Task<TResult>> updateOperation, TState state, BlobClient blob, string? currentETag)\n        {\n            try\n            {\n                return await updateOperation(state).ConfigureAwait(false);\n            }\n            catch (RequestFailedException ex) when (ex.IsPreconditionFailed() || ex.IsConflict() || ex.IsNotFound() && !ex.IsContainerNotFound())\n            {\n                throw new InconsistentStateException($\"Blob storage condition not Satisfied. BlobName: {blob.Name}, Container: {blob.BlobContainerName}, CurrentETag: {currentETag}\", \"Unknown\", currentETag, ex);\n            }\n        }\n\n        public void Participate(ISiloLifecycle lifecycle)\n        {\n            lifecycle.Subscribe(OptionFormattingUtilities.Name<AzureBlobGrainStorage>(this.name), this.options.InitStage, Init);\n        }\n\n        /// <summary> Initialization function for this storage provider. </summary>\n        private async Task Init(CancellationToken ct)\n        {\n            var stopWatch = Stopwatch.StartNew();\n\n            try\n            {\n                LogDebugInitializing(this.name, this.options.ContainerName);\n                if (options.CreateClient is not { } createClient)\n                {","sourceCodeStart":312,"sourceCodeEnd":348,"githubUrl":"https://github.com/dotnet/orleans/blob/fca799fa70ecb6ad975224271703ca43221f58de/src/Azure/Orleans.Persistence.AzureStorage/Providers/Storage/AzureBlobStorage.cs#L312-L348","documentation":"Thrown as InconsistentStateException by AzureBlobStorage.DoOptimisticUpdate when a blob write fails with a precondition-failed, conflict, or (non-container) not-found RequestFailedException. Orleans grain storage uses ETag-based optimistic concurrency; a mismatch means another caller mutated the blob between the read and the write, so the operation is aborted for the runtime to retry.","triggerScenarios":"Two silos/activations or two grain calls read the same grain state, then both attempt to write; the second write's If-Match ETag no longer matches the blob's current ETag, so Azure returns PreconditionFailed/412 (or Conflict/409, or NotFound/404 if the blob was deleted), which DoOptimisticUpdate wraps into InconsistentStateException.","commonSituations":"Concurrent writes to the same grain from multiple silos; a race between read and write within the same grain; an external process modified the blob outside Orleans; the blob was deleted between read and write; retry storms under load.","solutions":["Let the Orleans runtime retry (InconsistentStateException is typically retried by the storage layer/caller); ensure ReadStateAsync is re-invoked before re-writing so the ETag refreshes.","Reduce concurrent writers to the same grain (single-writer activation, grain locking) to avoid contention.","Verify no external writer is mutating the grain's blob; align ETag handling if you interoperate.","Increase configured retry counts/backoff if transient under load."],"exampleFix":"// before\nvar state = await grain.Read();\nstate.Counter++;\nawait grain.Write(state); // stale ETag -> InconsistentStateException\n// after\nfor (int attempt = 0; ; attempt++)\n{\n    var state = await grain.Read(); // fresh ETag each attempt\n    state.Counter++;\n    try { await grain.Write(state); break; }\n    catch (InconsistentStateException) when (attempt < 5) { /* refresh and retry */ }\n}","handlingStrategy":"retry","validationCode":"// Before writing, re-read fresh state to obtain the current ETag:\nvar fresh = await grain.ReadStateAsync();\nfresh.State = newState;\nawait grain.WriteStateAsync();","typeGuard":"static bool IsInconsistentState(Exception ex) => ex is InconsistentStateException;","tryCatchPattern":"for (int attempt = 0; attempt < maxRetries; attempt++)\n{\n    try\n    {\n        await storage.WriteStateAsync(grainType, grainReference, grainState);\n        return;\n    }\n    catch (InconsistentStateException) when (attempt < maxRetries - 1)\n    {\n        await Task.Delay(backoff(attempt));\n        await storage.ReadStateAsync(grainType, grainReference, grainState); // refresh ETag\n    }\n}","preventionTips":["Refresh grain state (and its ETag) immediately before writing on retry.","Single-writer per grain activation to reduce contention.","Ensure no external process mutates the grain's blob.","Tune storage retry/backoff under high concurrency."],"tags":["azure-blob-storage","grain-storage","optimistic-concurrency","etag","concurrency","retry"],"backgroundTag":null,"analyzedSha":"fca799fa70ecb6ad975224271703ca43221f58de","analyzedAt":"2026-08-13T19:55:57.938Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}