Skip to content

Make LimitArrayPoolWriteStream.ReturnAllPooledBuffers idempotent#128083

Open
EgorBo wants to merge 1 commit into
dotnet:mainfrom
EgorBo:fix-limit-array-pool-write-stream-concurrent-dispose
Open

Make LimitArrayPoolWriteStream.ReturnAllPooledBuffers idempotent#128083
EgorBo wants to merge 1 commit into
dotnet:mainfrom
EgorBo:fix-limit-array-pool-write-stream-concurrent-dispose

Conversation

@EgorBo
Copy link
Copy Markdown
Member

@EgorBo EgorBo commented May 12, 2026

Makes LimitArrayPoolWriteStream.ReturnAllPooledBuffers idempotent under concurrent Dispose() so that user code cannot accidentally double-return the same buffer to ArrayPool<byte>.Shared. Two Interlocked.Exchange calls on the existing fields are enough — no new allocations or fast-path overhead in the single-threaded case.

It's not a security issue, just makes code more robust to buggy implementations

using var content = new RacingContent();
await content.LoadIntoBufferAsync(int.MaxValue);

internal sealed class RacingContent : HttpContent
{
    private static readonly byte[] s_payload = new byte[16 * 1024];

    protected override bool TryComputeLength(out long length) { length = 0; return false; }

    protected override async Task SerializeToStreamAsync(Stream stream, TransportContext? context)
    {
        await stream.WriteAsync(s_payload);   // populates _pooledBuffers[0]
        await stream.WriteAsync(s_payload);   // _lastBuffer is now pooled

        // if stream.Dispose is called in parallel here before we exit - it will lead to double-free in ArrayPool
    }
}

Note

This PR was authored with the assistance of GitHub Copilot.

Use Interlocked exchanges so that concurrent Dispose() calls cannot
double-return the same buffer to ArrayPool<byte>.Shared.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 12, 2026 15:20
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @karelz, @dotnet/ncl
See info in area-owners.md if you want to be subscribed.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR makes HttpContent.LimitArrayPoolWriteStream.ReturnAllPooledBuffers() safe to call multiple times concurrently by making buffer-return operations idempotent, preventing accidental double-return of the same byte[] to ArrayPool<byte>.Shared (which can corrupt the shared pool).

Changes:

  • Atomically claims and clears _pooledBuffers via Interlocked.Exchange(ref _pooledBuffers, null) so only one caller returns the pooled buffer list.
  • Atomically claims and clears _lastBufferIsPooled via Interlocked.Exchange(ref _lastBufferIsPooled, false) so only one caller returns the final pooled buffer.
  • Updates internal comments to document the rationale and the concurrency/idempotency intent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants