{"record":{"id":"f9eb8c79818b06a0","repo":"abpframework/abp","slug":"the-stream-can-not-be-read-anymore-because-a-prev","errorCode":null,"errorMessage":"The stream can not be read anymore, because a previous read operation has failed!","messagePattern":"The stream can not be read anymore, because a previous read operation has failed!","errorType":"exception","errorClass":"AbpException","httpStatus":null,"severity":"error","filePath":"framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobPipelineScopeStream.cs","lineNumber":290,"sourceCode":"            // token trips in the gap after the check above) leaves it healthy, so the outer\n            // must not fault either — a retry with a live token can still verify the end\n            throw;\n        }\n        catch\n        {\n            // A real integrity failure is permanent, so a read-retry layer can not swallow it\n            // and later see a normal EOF\n            _authenticatedEndChecked = true;\n            _faulted = true;\n            throw;\n        }\n    }\n\n    private void EnsureNotFaulted()\n    {\n        if (_faulted)\n        {\n            throw new AbpException(\"The stream can not be read anymore, because a previous read operation has failed!\");\n        }\n    }\n\n#if !NETSTANDARD2_0\n    // Forwarded so a wrapper that only implements the modern overloads is not\n    // degraded to the byte[] fallback of the base class\n    public override int Read(Span<byte> buffer)\n    {\n        EnsureNotDisposed();\n        EnsureNotFaulted();\n        using (_currentTenant.Change(_tenantId))\n        {\n            int read;\n            try\n            {\n                read = _inner.Read(buffer);\n            }\n            catch","sourceCodeStart":272,"sourceCodeEnd":308,"githubUrl":"https://github.com/abpframework/abp/blob/7ed43b1931b9df46a50c0c59148a18645641d0df/framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobPipelineScopeStream.cs#L272-L308","documentation":"BlobPipelineScopeStream verifies the authenticated end of encrypted BLOBs. Once a real (non-cancellation) integrity failure occurs, the stream sets _faulted = true permanently so a retry/read-ahead layer cannot swallow the failure and later return a normal EOF. Any subsequent Read/ReadAsync hits EnsureNotFaulted and throws this AbpException.","triggerScenarios":"Calling Read, ReadAsync, or CopyToAsync on a BlobPipelineScopeStream after a previous read or end-check threw an integrity/cryptographic exception; reusing a stream that has already failed.","commonSituations":"Retry/resilience middleware (Polly, custom loops) that catches the first exception then continues reading the same stream; consumer code that ignores an exception and re-reads; stream pooling that recycles a faulted stream.","solutions":["Do not reuse the stream after a read failure — dispose it and open a fresh one from the BLOB provider.","Make sure retry logic re-fetches the stream (e.g. re-calls GetAsync) rather than retrying reads on the same instance.","Surface the original integrity exception to the caller instead of retrying silently.","Remove stream-pooling/recycling for encrypted BLOB streams."],"exampleFix":"// before — retrying on the same faulted stream\nforeach (var attempt in Enumerable.Range(1, 3))\n{\n    try { await stream.CopyToAsync(dst); break; }\n    catch { /* retry on same stream */ }\n}\n\n// after — fetch a fresh stream per attempt\nforeach (var attempt in Enumerable.Range(1, 3))\n{\n    using var s = await provider.GetStreamAsync(name);\n    try { await s.CopyToAsync(dst); break; }\n    catch (AbpException) when (attempt < 3) { /* re-fetch next loop */ }\n}","handlingStrategy":"fallback","validationCode":"// Track stream health so you never reuse a faulted stream.\nstatic async Task<bool> IsStreamUsableAsync(BlobPipelineScopeStream s)\n{\n    try { await s.ReadAsync(Array.Empty<byte>(), 0, 0); return true; }\n    catch (AbpException ex) when (ex.Message.Contains(\"previous read operation has failed\")) { return false; }\n}\n// Prefer: discard the stream entirely after any exception and fetch a new one.","typeGuard":"// Wrap the stream so a fault transitions it to a 'dead' state your retry layer recognizes.\npublic sealed class StreamHealth\n{\n    public bool IsFaulted { get; private set; }\n    public void MarkFaulted(Exception _) => IsFaulted = true;\n}","tryCatchPattern":"Stream? stream = null;\ntry\n{\n    stream = await provider.GetStreamAsync(name);\n    await stream.CopyToAsync(dst);\n}\ncatch (AbpException ex) when (ex.Message.Contains(\"previous read operation has failed\"))\n{\n    // The stream is permanently dead: do NOT retry on it. Re-fetch.\n    stream?.Dispose();\n    logger.LogWarning(ex, \"Stream faulted; re-fetching a fresh one.\");\n    using var fresh = await provider.GetStreamAsync(name);\n    await fresh.CopyToAsync(dst);\n}","preventionTips":["Never retry reads on a stream that has thrown; always fetch a new one.","Make sure Polly/resilience policies re-acquire the stream, not just re-call Read.","Do not pool/recycle encrypted BLOB streams.","Surface integrity failures to the caller instead of masking them."],"tags":["crypto","integrity","streams","state","retry"],"backgroundTag":null,"analyzedSha":"7ed43b1931b9df46a50c0c59148a18645641d0df","analyzedAt":"2026-08-13T16:26:11.351Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}