Files
ResourcePool.Docs/GRPC_CLIENT_RESET_DEBUG.md
T

5.9 KiB

gRPC "Client Reset Request Stream" Debug Guide

Lỗi

System.IO.IOException: The client reset the request stream.

🔍 Nguyên nhân

Lỗi này xảy ra khi gRPC client đóng/hủy connection trước khi server xử lý xong.

Các nguyên nhân phổ biến:

1. Client Timeout ⏱️ (Phổ biến nhất)

  • Client có timeout ngắn hơn thời gian server xử lý
  • Ví dụ: Client timeout 5s, nhưng server cần 10s để xử lý

Cách kiểm tra:

# Xem logs với thời gian xử lý
grep "GetProxy completed" logs.txt
grep "GetProxy cancelled" logs.txt

Giải pháp:

  • Tăng timeout ở client
  • Tối ưu performance ở server

2. Server xử lý chậm 🐌

Các nguyên nhân server chậm:

  • Database query chậm
  • Redis cache chậm
  • ClickHouse logging chậm (đã fix)
  • Logic phức tạp trong RequestResource()

Cách kiểm tra:

# Xem thời gian xử lý trung bình
grep "GetProxy completed" logs.txt | grep -oP '\d+ms' | sort -n

Giải pháp:

  • Profile code để tìm bottleneck
  • Add index vào database
  • Optimize queries
  • Cache kết quả

3. Network Issues 🌐

  • Connection bị drop
  • Load balancer timeout
  • Firewall rules

Cách kiểm tra:

# Kiểm tra network errors
kubectl logs <pod-name> | grep -i "connection\|network\|timeout"

4. Client Implementation Issues 🐛

  • Client không đợi response đủ lâu
  • Client có retry logic và cancel request cũ
  • Client crash/restart

📊 Monitoring với logs mới

Sau khi deploy code mới, bạn sẽ thấy logs như:

Successful request:

[abc12345] GetProxy started - AccessToken: token123
[abc12345] GetProxy completed in 150ms - ProxyId: 456

Cancelled request:

[xyz67890] GetProxy started - AccessToken: token456
[xyz67890] GetProxy cancelled by client after 5000ms - AccessToken: token456

Failed request:

[def11111] GetProxy started - AccessToken: token789
[def11111] GetProxy failed after 2000ms - AccessToken: token789

🔧 Cách debug

Bước 1: Xác định thời gian xử lý

# Xem requests bị cancel và thời gian của chúng
kubectl logs <pod-name> | grep "cancelled by client"

# Nếu thấy nhiều requests bị cancel sau ~5s, 10s, 30s
# => Client timeout ở các mốc đó

Bước 2: So sánh với requests thành công

# Xem thời gian xử lý của requests thành công
kubectl logs <pod-name> | grep "completed in" | grep -oP '\d+ms'

# Nếu hầu hết < 1000ms nhưng có vài cái > 5000ms
# => Có vấn đề performance không ổn định

Bước 3: Kiểm tra client timeout

// Ở phía client, kiểm tra timeout config
var channel = GrpcChannel.ForAddress("http://server:5002", new GrpcChannelOptions
{
    HttpHandler = new SocketsHttpHandler
    {
        PooledConnectionIdleTimeout = Timeout.InfiniteTimeSpan,
        KeepAlivePingDelay = TimeSpan.FromSeconds(60),
        KeepAlivePingTimeout = TimeSpan.FromSeconds(30),
        EnableMultipleHttp2Connections = true
    }
});

// Hoặc set deadline cho mỗi call
var response = await client.GetProxyAsync(request, deadline: DateTime.UtcNow.AddSeconds(30));

Giải pháp đã áp dụng

1. Cấu hình Kestrel HTTP/2 (Program.cs)

// HTTP/2 limits để tránh connection faults
options.Limits.Http2.MaxStreamsPerConnection = 100;
options.Limits.Http2.InitialConnectionWindowSize = 131072; // 128KB
options.Limits.Http2.InitialStreamWindowSize = 98304; // 96KB

// Tăng timeout
options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(2);
options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(30);

// Disable rate limits cho gRPC
options.Limits.MinRequestBodyDataRate = null;
options.Limits.MinResponseDataRate = null;

Lợi ích:

  • Tránh "HTTP/2 connection faulted" errors
  • Cho phép nhiều concurrent streams hơn
  • Tăng window size để handle large messages
  • Không bị timeout khi network chậm

2. Cấu hình gRPC (Startup.cs)

services.AddGrpc(options =>
{
    options.MaxReceiveMessageSize = 10 * 1024 * 1024; // 10MB
    options.MaxSendMessageSize = 10 * 1024 * 1024; // 10MB
    options.EnableDetailedErrors = true;
});

Lợi ích:

  • Tránh lỗi message size limit
  • Detailed errors giúp debug dễ hơn

3. Thêm logging chi tiết (ProxyService.cs)

  • Track thời gian xử lý từng request với Stopwatch
  • Log khi client cancel request
  • Request ID để trace từng request
  • Catch OperationCanceledException riêng

4. Error handling

  • Log warning thay vì error cho client cancellation
  • Không crash service khi client disconnect
  • Continue service khi ClickHouse quá tải

Ngay lập tức:

  1. Deploy code mới với logging
  2. Monitor logs để xác định pattern
  3. Xác định thời gian xử lý trung bình

Nếu thấy nhiều cancellations:

Option A: Tăng client timeout

// Client code
var deadline = DateTime.UtcNow.AddSeconds(30); // Tăng từ 5s lên 30s
var response = await client.GetProxyAsync(request, deadline: deadline);

Option B: Tối ưu server performance

  1. Profile RequestResource() method
  2. Add caching cho hot data
  3. Optimize database queries
  4. Consider async logging (fire-and-forget)

Option C: Implement timeout gracefully

// Server code - return cached/fallback data nếu quá lâu
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
cts.CancelAfter(TimeSpan.FromSeconds(5));

try
{
    return await GetFreshData(cts.Token);
}
catch (OperationCanceledException)
{
    // Return cached data as fallback
    return await GetCachedData();
}

🎯 Expected Metrics

Sau khi fix:

  • Cancellation rate < 1%
  • P95 response time < 1000ms
  • P99 response time < 3000ms
  • No service crashes due to client disconnects