abpframework/abp · error · AbpException
The stream can not be read anymore, because a previous read
Error message
The stream can not be read anymore, because a previous read operation has failed!
What it means
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.
Source
Thrown at framework/src/Volo.Abp.BlobStoring/Volo/Abp/BlobStoring/BlobPipelineScopeStream.cs:290
// token trips in the gap after the check above) leaves it healthy, so the outer
// must not fault either — a retry with a live token can still verify the end
throw;
}
catch
{
// A real integrity failure is permanent, so a read-retry layer can not swallow it
// and later see a normal EOF
_authenticatedEndChecked = true;
_faulted = true;
throw;
}
}
private void EnsureNotFaulted()
{
if (_faulted)
{
throw new AbpException("The stream can not be read anymore, because a previous read operation has failed!");
}
}
#if !NETSTANDARD2_0
// Forwarded so a wrapper that only implements the modern overloads is not
// degraded to the byte[] fallback of the base class
public override int Read(Span<byte> buffer)
{
EnsureNotDisposed();
EnsureNotFaulted();
using (_currentTenant.Change(_tenantId))
{
int read;
try
{
read = _inner.Read(buffer);
}
catchView on GitHub (pinned to 7ed43b1931)
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.
Example fix
// before — retrying on the same faulted stream
foreach (var attempt in Enumerable.Range(1, 3))
{
try { await stream.CopyToAsync(dst); break; }
catch { /* retry on same stream */ }
}
// after — fetch a fresh stream per attempt
foreach (var attempt in Enumerable.Range(1, 3))
{
using var s = await provider.GetStreamAsync(name);
try { await s.CopyToAsync(dst); break; }
catch (AbpException) when (attempt < 3) { /* re-fetch next loop */ }
} Defensive patterns
Strategy: fallback
Validate before calling
// Track stream health so you never reuse a faulted stream.
static async Task<bool> IsStreamUsableAsync(BlobPipelineScopeStream s)
{
try { await s.ReadAsync(Array.Empty<byte>(), 0, 0); return true; }
catch (AbpException ex) when (ex.Message.Contains("previous read operation has failed")) { return false; }
}
// Prefer: discard the stream entirely after any exception and fetch a new one. Type guard
// Wrap the stream so a fault transitions it to a 'dead' state your retry layer recognizes.
public sealed class StreamHealth
{
public bool IsFaulted { get; private set; }
public void MarkFaulted(Exception _) => IsFaulted = true;
} Try / catch
Stream? stream = null;
try
{
stream = await provider.GetStreamAsync(name);
await stream.CopyToAsync(dst);
}
catch (AbpException ex) when (ex.Message.Contains("previous read operation has failed"))
{
// The stream is permanently dead: do NOT retry on it. Re-fetch.
stream?.Dispose();
logger.LogWarning(ex, "Stream faulted; re-fetching a fresh one.");
using var fresh = await provider.GetStreamAsync(name);
await fresh.CopyToAsync(dst);
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- The encrypted BLOB was not read to its authenticated end, so
- The encrypted BLOB is corrupted or has an invalid format: mi
- The encrypted BLOB is corrupted or has an invalid format: tr
- The encrypted BLOB is corrupted or has an invalid format: in
- The encrypted BLOB is corrupted or has an invalid format: in
AI-assisted analysis of abpframework/abp@7ed43b1931 (2026-08-13).
Data as JSON: /api/errors/f9eb8c79818b06a0.
Report an issue: GitHub.