# 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:** ```bash # 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:** ```bash # 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:** ```bash # Kiểm tra network errors kubectl logs | 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ý ```bash # Xem requests bị cancel và thời gian của chúng kubectl logs | 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 ```bash # Xem thời gian xử lý của requests thành công kubectl logs | 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 ```csharp // Ở 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) ```csharp // 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) ```csharp 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 ## 📈 Recommended Actions ### 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 ```csharp // 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 ```csharp // 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