# gRPC Troubleshooting Common gRPC issues and solutions. ## Connection Issues ### Cannot Connect to Server **Symptoms:** ``` Grpc.Core.RpcException: Status(StatusCode="Unavailable", Detail="failed to connect to all addresses") ``` **Common Causes:** 1. **Wrong address/port:** ```csharp // ❌ Wrong var channel = GrpcChannel.ForAddress("http://localhost:5000"); // ✅ Correct - Check server is listening on this port var channel = GrpcChannel.ForAddress("https://localhost:5001"); ``` 2. **HTTP vs HTTPS mismatch:** ```csharp // Server configured for HTTPS app.Urls.Add("https://localhost:5001"); // Client must use HTTPS var channel = GrpcChannel.ForAddress("https://localhost:5001"); ``` 3. **Server not running:** ```bash # Check if server is running netstat -an | grep 5001 # Or on Windows netstat -an | findstr 5001 ``` ### SSL/TLS Certificate Errors **Symptoms:** ``` The SSL connection could not be established The remote certificate is invalid according to the validation procedure ``` **Solutions:** **Development (disable SSL validation):** ```csharp var handler = new HttpClientHandler { ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator }; var channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions { HttpHandler = handler }); ``` **Production (trust certificate):** ```bash # macOS - Trust certificate sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain cert.crt # Linux - Add to trusted certificates sudo cp cert.crt /usr/local/share/ca-certificates/ sudo update-ca-certificates # Windows - Import to Trusted Root certutil -addstore -f "ROOT" cert.crt ``` **Use valid certificate in production:** ```csharp builder.WebHost.ConfigureKestrel(options => { options.Listen(IPAddress.Any, 5001, listenOptions => { listenOptions.UseHttps("certificate.pfx", "password"); }); }); ``` ### HTTP/2 Not Supported **Symptoms:** ``` The request was aborted. The HTTP/2 connection closed. ``` **Solution - Enable HTTP/2:** ```csharp // Client var channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions { HttpVersion = new Version(2, 0) }); // Server (Kestrel) builder.WebHost.ConfigureKestrel(options => { options.ConfigureEndpointDefaults(listenOptions => { listenOptions.Protocols = HttpProtocols.Http2; }); }); ``` ## Code Generation Issues ### Proto Files Not Generating C# Code **Symptoms:** - No generated files in `obj/` directory - Build succeeds but classes not available **Solutions:** 1. **Verify .csproj configuration:** ```xml all runtime; build; native; contentfiles; analyzers ``` 2. **Clean and rebuild:** ```bash dotnet clean dotnet build ``` 3. **Check build output:** ```bash dotnet build -v detailed ``` Look for lines containing "protoc" and "grpc_csharp_plugin" 4. **Verify Grpc.Tools is restored:** ```bash dotnet restore ``` ### Source Generator Not Running **Symptoms:** - `CommandServiceImpl` and `QueryServiceImpl` not found - Generator package installed but no output **Solutions:** 1. **Verify package reference:** ```xml ``` 2. **Check generated files location:** ```bash # View generated files ls obj/Debug/net10.0/generated/Svrnty.CQRS.Grpc.Generators/ # Or on Windows dir obj\Debug\net10.0\generated\Svrnty.CQRS.Grpc.Generators\ ``` 3. **Restart IDE:** - Close and reopen Visual Studio or Rider - Sometimes IDEs cache analyzer/generator state 4. **Force regeneration:** ```bash dotnet clean rm -rf obj bin dotnet build ``` ### Type Mismatch Errors **Symptoms:** ``` Cannot convert from 'CreateUserCommand' to 'CreateUserCommand' ``` **Cause:** Two types with same name - one from proto, one from C# **Solution:** Use the proto-generated type OR ensure your C# type matches proto exactly: **Option 1 - Use proto-generated type:** ```csharp // Use the generated type from proto var command = new CreateUserCommand { Name = "John", Email = "john@example.com" }; ``` **Option 2 - Match proto exactly:** ```protobuf message CreateUserCommand { string name = 1; string email = 2; int32 age = 3; } ``` ```csharp // C# type must match proto fields exactly public record CreateUserCommand { public string Name { get; init; } = string.Empty; public string Email { get; init; } = string.Empty; public int Age { get; init; } } ``` ## Validation Issues ### Validation Errors Not Returned Correctly **Symptoms:** - Validation fails but client receives generic error - Rich Error Model not working **Solutions:** 1. **Ensure Rich Error Model packages:** ```xml ``` 2. **Import google/rpc/status.proto:** ```protobuf import "google/rpc/status.proto"; import "google/rpc/error_details.proto"; ``` 3. **Client-side error handling:** ```csharp using Grpc.Core; using Google.Rpc; try { var response = await client.CreateUserAsync(command); } catch (RpcException ex) when (ex.StatusCode == StatusCode.InvalidArgument) { // Get detailed status var status = ex.GetRpcStatus(); if (status != null) { var badRequest = status.GetDetail(); foreach (var violation in badRequest.FieldViolations) { Console.WriteLine($"{violation.Field}: {violation.Description}"); } } } ``` ### FluentValidation Not Triggered **Symptoms:** - Validators registered but not executing - Invalid data accepted **Solutions:** 1. **Verify validator registration:** ```csharp builder.Services.AddTransient, CreateUserCommandValidator>(); ``` 2. **Check handler registration:** ```csharp builder.Services.AddCommand(); ``` 3. **Ensure validation logic is correct:** ```csharp public class CreateUserCommandValidator : AbstractValidator { public CreateUserCommandValidator() { RuleFor(x => x.Name) .NotEmpty() .WithMessage("Name is required"); RuleFor(x => x.Email) .NotEmpty() .EmailAddress() .WithMessage("Valid email is required"); } } ``` ## Performance Issues ### Slow Response Times **Causes and Solutions:** 1. **Not reusing channels:** ```csharp // ❌ Bad - Creates new connection per call public async Task CreateUser(string name, string email) { var channel = GrpcChannel.ForAddress("https://localhost:5001"); var client = new CommandService.CommandServiceClient(channel); var response = await client.CreateUserAsync(new CreateUserCommand { Name = name, Email = email }); await channel.ShutdownAsync(); return response.UserId; } // ✅ Good - Reuse channel private readonly GrpcChannel _channel = GrpcChannel.ForAddress("https://localhost:5001"); public async Task CreateUser(string name, string email) { var client = new CommandService.CommandServiceClient(_channel); var response = await client.CreateUserAsync(new CreateUserCommand { Name = name, Email = email }); return response.UserId; } ``` 2. **Enable connection pooling:** ```csharp builder.Services.AddGrpcClient(options => { options.Address = new Uri("https://localhost:5001"); }) .ConfigurePrimaryHttpMessageHandler(() => { return new SocketsHttpHandler { PooledConnectionIdleTimeout = Timeout.InfiniteTimeSpan, KeepAlivePingDelay = TimeSpan.FromSeconds(60), KeepAlivePingTimeout = TimeSpan.FromSeconds(30), EnableMultipleHttp2Connections = true }; }); ``` 3. **Use streaming for large datasets:** ```csharp // Instead of single large response rpc GetAllUsers (GetAllUsersQuery) returns (UserListResponse); // Use server streaming rpc StreamUsers (StreamUsersQuery) returns (stream UserDto); ``` ### High Memory Usage **Solutions:** 1. **Limit message size:** ```csharp builder.Services.AddGrpc(options => { options.MaxReceiveMessageSize = 4 * 1024 * 1024; // 4 MB options.MaxSendMessageSize = 4 * 1024 * 1024; // 4 MB }); ``` 2. **Use pagination:** ```protobuf message ListUsersQuery { int32 page = 1; int32 page_size = 2; } message UserListResponse { repeated UserDto users = 1; int32 total_count = 2; } ``` 3. **Stream large responses:** ```csharp public override async Task StreamUsers( StreamUsersQuery request, IServerStreamWriter responseStream, ServerCallContext context) { var users = await _repository.GetUsersAsync(); foreach (var user in users) { await responseStream.WriteAsync(user); } } ``` ## Deadline Exceeded Errors **Symptoms:** ``` Grpc.Core.RpcException: Status(StatusCode="DeadlineExceeded", Detail="Deadline Exceeded") ``` **Solutions:** 1. **Increase client deadline:** ```csharp var deadline = DateTime.UtcNow.AddSeconds(30); var response = await client.CreateUserAsync( new CreateUserCommand { ... }, deadline: deadline); ``` 2. **Set default deadline:** ```csharp var channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions { MaxReceiveMessageSize = null, MaxSendMessageSize = null, ServiceConfig = new ServiceConfig { MethodConfigs = { new MethodConfig { Names = { MethodName.Default }, Timeout = TimeSpan.FromSeconds(30) } } } }); ``` 3. **Server-side - respect cancellation:** ```csharp public override async Task CreateUser( CreateUserCommand request, ServerCallContext context) { // Check cancellation token context.CancellationToken.ThrowIfCancellationRequested(); // Pass to handler var userId = await _handler.HandleAsync(request, context.CancellationToken); return new CreateUserResponse { UserId = userId }; } ``` ## Metadata/Headers Issues ### Headers Not Received **Problem:** ```csharp // Client sends header var metadata = new Metadata { { "Authorization", "Bearer token" } }; await client.CreateUserAsync(request, headers: metadata); // Server doesn't see it var authHeader = context.RequestHeaders.GetValue("Authorization"); // null ``` **Solution:** Check header name matches exactly (case-sensitive in some implementations): ```csharp // Client var metadata = new Metadata { { "authorization", "Bearer token" } // lowercase }; // Server var authHeader = context.RequestHeaders.GetValue("authorization"); ``` ### CORS Issues with Metadata **Problem:** gRPC-Web calls fail with CORS errors when sending custom headers. **Solution:** ```csharp builder.Services.AddCors(options => { options.AddPolicy("AllowGrpcWeb", policy => { policy.WithOrigins("http://localhost:3000") .AllowAnyHeader() .AllowAnyMethod() .WithExposedHeaders( "Grpc-Status", "Grpc-Message", "Grpc-Encoding", "Grpc-Accept-Encoding", "Authorization", // Your custom headers "X-Request-ID"); }); }); app.UseGrpcWeb(); app.UseCors("AllowGrpcWeb"); ``` ## Reflection Issues ### Reflection Not Working **Symptoms:** ```bash grpcurl -plaintext localhost:5001 list # Error: server does not support the reflection API ``` **Solutions:** 1. **Install package:** ```bash dotnet add package Grpc.AspNetCore.Server.Reflection ``` 2. **Register and map service:** ```csharp builder.Services.AddGrpcReflection(); app.MapGrpcReflectionService(); ``` 3. **Verify service is mapped:** ```bash grpcurl -plaintext localhost:5001 list # Should show: # grpc.reflection.v1alpha.ServerReflection # myapp.CommandService # myapp.QueryService ``` ## Common Client Errors ### Connection Refused (Browser) **Problem:** gRPC doesn't work directly in browsers - need gRPC-Web. **Solution:** 1. **Server - Enable gRPC-Web:** ```csharp builder.Services.AddGrpc(); app.UseGrpcWeb(); app.MapGrpcService().EnableGrpcWeb(); ``` 2. **Client - Use grpc-web:** ```bash npm install grpc-web npm install google-protobuf ``` 3. **Generate grpc-web code:** ```bash protoc -I=. cqrs_services.proto \ --js_out=import_style=commonjs:. \ --grpc-web_out=import_style=typescript,mode=grpcwebtext:. ``` ### Unimplemented Method **Symptoms:** ``` Grpc.Core.RpcException: Status(StatusCode="Unimplemented", Detail="Method is unimplemented") ``` **Causes:** 1. **Service not mapped:** ```csharp // Missing app.MapGrpcService(); ``` 2. **Method not implemented:** ```csharp // Handler not registered builder.Services.AddCommand(); ``` 3. **Proto method name mismatch:** ```protobuf // Proto rpc CreateUser (CreateUserCommand) returns (CreateUserResponse); // Client must match exactly await client.CreateUserAsync(...); // ✅ await client.CreateUSERAsync(...); // ❌ Wrong case ``` ## Debugging Tips ### Enable Detailed Logging ```csharp builder.Logging.AddFilter("Grpc", LogLevel.Debug); builder.Logging.AddFilter("Microsoft.AspNetCore.Server.Kestrel", LogLevel.Debug); ``` ### Inspect Network Traffic **Using Wireshark:** 1. Install Wireshark 2. Capture on loopback interface 3. Filter: `tcp.port == 5001` 4. Follow TCP stream to see gRPC frames **Using grpcurl:** ```bash # Verbose output grpcurl -v -plaintext -d '{"name": "John"}' localhost:5001 myapp.CommandService/CreateUser # Test connectivity grpcurl -plaintext localhost:5001 list # Describe service grpcurl -plaintext localhost:5001 describe myapp.CommandService ``` ### Test with BloomRPC 1. Download BloomRPC 2. Import .proto file 3. Set server address 4. Test requests with GUI ## Production Checklist Before deploying gRPC to production: - [ ] Use HTTPS with valid certificates - [ ] Configure authentication (JWT, mTLS, etc.) - [ ] Set appropriate deadlines - [ ] Implement health checks - [ ] Configure logging - [ ] Set message size limits - [ ] Enable connection pooling - [ ] Disable reflection (or require auth) - [ ] Configure CORS for gRPC-Web - [ ] Monitor error rates - [ ] Set up load balancing - [ ] Test failover scenarios ## See Also - [gRPC Integration Overview](README.md) - [Getting Started](getting-started-grpc.md) - [gRPC Clients](grpc-clients.md) - [Service Implementation](service-implementation.md) - [gRPC .NET Troubleshooting](https://learn.microsoft.com/en-us/aspnet/core/grpc/troubleshoot) - [gRPC Status Codes](https://grpc.io/docs/guides/status-codes/)