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);
            }
            catch

View on GitHub (pinned to 7ed43b1931)

Solutions

  1. Do not reuse the stream after a read failure — dispose it and open a fresh one from the BLOB provider.
  2. Make sure retry logic re-fetches the stream (e.g. re-calls GetAsync) rather than retrying reads on the same instance.
  3. Surface the original integrity exception to the caller instead of retrying silently.
  4. 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

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


AI-assisted analysis of abpframework/abp@7ed43b1931 (2026-08-13). Data as JSON: /api/errors/f9eb8c79818b06a0. Report an issue: GitHub.