-
Notifications
You must be signed in to change notification settings - Fork 807
Description
ObjectDisposedException during HTTP/2 mTLS handshake in Kubernetes production environment
What version of gRPC and what language are you using?
Language: C# / .NET 8.0
gRPC Packages:
Grpc.AspNetCore
: 2.71.0Grpc.Net.Client
: 2.71.0 (implicit via Grpc.AspNetCore)Grpc.Tools
: 2.72.0Google.Protobuf
: 3.32.0
What operating system (Linux, Windows,...) and version?
Development Environment: Windows 10.0.26100 (Windows 11)
Production Environment: Linux containers in Kubernetes (Azure Kubernetes Service)
- Container Runtime: Linux containers
- Content root path:
/usr/local/xyz/abc
What runtime / compiler are you using (e.g. .NET Core SDK version dotnet --info
)
.NET SDK: 8.0.413
Runtime: Microsoft.AspNetCore.App 8.0.19
Target Framework: net8.0
What did you do?
Scenario: Health probe service making mTLS gRPC calls to a TTS service in Kubernetes production environment.
Code Pattern:
// Create gRPC channel with mTLS client certificate
var clientCert = X509Certificate2.CreateFromPemFile(certPath, keyPath);
var httpHandler = new HttpClientHandler
{
ClientCertificates = { clientCert }
};
using var channel = GrpcChannel.ForAddress(serviceUrl, new GrpcChannelOptions
{
HttpHandler = httpHandler
});
var client = new Synthesizer.SynthesizerClient(channel);
var reply = client.Synthesize(request, headers);
await foreach (var message in reply.ResponseStream.ReadAllAsync())
{
// Process response
}
Environment Details:
- Service URL:
https://headless-service-url:8081
- mTLS with client certificates loaded from PEM files
- Kubernetes service-to-service communication
- Health check pattern with 600-second intervals
What did you expect to see?
Successful HTTP/2 gRPC connection establishment and streaming response processing without disposal errors.
What did you see instead?
Error: ObjectDisposedException: Cannot access a disposed object. Object name: 'System.Net.Security.SslStream'
Full Stack Trace:
Grpc.Core.RpcException: Status(StatusCode="Unavailable", Detail="Error starting gRPC call. HttpRequestException: An error occurred while sending the request. IOException: An HTTP/2 connection could not be established because the server did not complete the HTTP/2 handshake. ObjectDisposedException: Cannot access a disposed object. Object name: 'System.Net.Security.SslStream'.", DebugException="System.Net.Http.HttpRequestException: An error occurred while sending the request.")
---> System.Net.Http.HttpRequestException: An error occurred while sending the request.
---> System.IO.IOException: An HTTP/2 connection could not be established because the server did not complete the HTTP/2 handshake.
---> System.ObjectDisposedException: Cannot access a disposed object. Object name: 'System.Net.Security.SslStream'.
at System.Net.Security.SslStream.<ThrowIfExceptional>g__ThrowExceptional|126_0(ExceptionDispatchInfo e)
at System.Net.Security.SslStream.WriteAsync(ReadOnlyMemory`1 buffer, CancellationToken cancellationToken)
at System.Net.Http.Http2Connection.SetupAsync(CancellationToken cancellationToken)
--- End of inner exception stack trace ---
at System.Net.Http.Http2Connection.SetupAsync(CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.ConstructHttp2ConnectionAsync(Stream stream, HttpRequestMessage request, IPEndPoint remoteEndPoint, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
at System.Net.Http.HttpConnectionPool.ConstructHttp2ConnectionAsync(Stream stream, HttpRequestMessage request, IPEndPoint remoteEndPoint, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.AddHttp2ConnectionAsync(QueueItem queueItem)
at System.Threading.Tasks.TaskCompletionSourceWithCancellation`1.WaitWithCancellationAsync(CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at Grpc.Net.Client.Balancer.Internal.BalancerHttpHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at Grpc.Net.Client.Internal.GrpcCall`2.RunCall(HttpRequestMessage request, Nullable`1 timeout)
--- End of inner exception stack trace ---
at Grpc.Net.Client.Internal.HttpContentClientStreamReader`2.MoveNextCore(CancellationToken cancellationToken)
at Grpc.Core.AsyncStreamReaderExtensions.ReadAllAsyncCore[T](IAsyncStreamReader`1 streamReader, CancellationToken cancellationToken)+MoveNext()
at Grpc.Core.AsyncStreamReaderExtensions.ReadAllAsyncCore[T](IAsyncStreamReader`1 streamReader, CancellationToken cancellationToken)+System.Threading.Tasks.Sources.IValueTaskSource<System.Boolean>.GetResult()
Anything else we should know about your project / environment?
Key Observations:
- Issue occurs consistently in Kubernetes production environment
- Timing: ~16 seconds after application startup, during first gRPC call attempt
- mTLS specific: Issue appears when using client certificates for mutual TLS
- HTTP/2 related: Error occurs during HTTP/2 handshake setup phase
Workarounds Attempted:
- Fresh
HttpClientHandler
creation per call - Connection pooling disabled (
PooledConnectionLifetime = TimeSpan.Zero
) - OCSP/CRL check disabling
- Multiple timeout configurations
DisposeHttpClient = true
inGrpcChannelOptions
- Custom SSL certificate validation callbacks
Environment Context:
- Kubernetes service mesh: Complex network infrastructure
- SSL handshake timing: Often takes 15+ seconds in this environment
- Certificate management: PEM files loaded from mounted volumes
Similar Issues:
This appears related to existing issues around HTTP/2 connection establishment and SSL stream lifecycle management in containerized environments with high-latency SSL handshakes.
Reproduction:
Issue reproduces consistently in Kubernetes production environment but not in local development, suggesting environmental factors (network latency, SSL negotiation timing) contribute to the disposal race condition.