dotnet / dotnet/sdk

System.ObjectDisposedException in HttpClient Before Task.Delay Completes

Open
#50,143 0 comments 0 reactions 1 assignee Claimed by @MiYanni View on GitHub
untriaged
Dominant language
C#
Stars
3.2k
Forks
1.3k
PR merge metrics
PR metrics pending

Description

### Title

`ObjectDisposedException` from `NetworkStream` during background read-ahead after idle timeout (before any second request)

---

### Description

An unexpected `ObjectDisposedException` is thrown about **5 seconds after the first request completes**, even though no additional HTTP requests have been made. The exception appears to originate from a background read-ahead operation attempting to access a `NetworkStream` that has already been disposed due to `PooledConnectionIdleTimeout`.

---

### Repro code

```csharp
using Microsoft.Extensions.DependencyInjection;

var services = new ServiceCollection();

services.AddHttpClient("Primary")
.ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
{
PooledConnectionIdleTimeout = TimeSpan.FromSeconds(5)
});

var provider = services.BuildServiceProvider();
var clientFactory = provider.GetRequiredService();
var client = clientFactory.CreateClient("Primary");

var foo = await client.GetAsync("http://google.com");

// Delay to simulate idle time; exception occurs BEFORE this completes
Task.Delay(20000).Wait();

var bar = await client.GetAsync("http://google.com");
```

---

### Observed behavior

After approximately 5 seconds (matching the idle timeout), this exception is thrown:

```
System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'System.Net.Sockets.NetworkStream'.
at System.Net.Sockets.NetworkStream.ReadAsync(Memory`1 buffer, CancellationToken cancellationToken)
at System.Net.Http.HttpConnection.g__ReadAheadWithZeroByteReadAsync|40_0()
```

**Important notes:**
- The exception occurs **before** the 20-second delay completes.
- The second HTTP request is **never executed**.
- The stack trace points to an internal async task still trying to read from a disposed stream.

---

### Expected behavior

If the connection is being cleaned up due to idle timeout, internal tasks such as read-ahead should not attempt to use the stream after disposal. If disposal races do occur, they should be caught and handled gracefully — not throw to the user.

---

### Environment

- .NET version: **.NET 8**
- OS: **Sequoia 15.6 **
- Reproducible: **Consistently**, ~5 seconds after app starts

---

### Hypothesis

This may be a race condition between:

- `SocketsHttpHandler` disposing the idle connection, and
- An internal read-ahead operation (`g__ReadAheadWithZeroByteReadAsync`) still referencing the stream without verifying it's not disposed.

This causes a fatal exception even when no follow-up request is made, which breaks the expected behavior of idle timeout cleanup.

---

### Suggested resolution

Ensure internal connection read-ahead logic checks for or safely handles disposed `NetworkStream` objects during cleanup triggered by `PooledConnectionIdleTimeout`.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.