PollyGrpc 1.0.1
dotnet add package PollyGrpc --version 1.0.1
NuGet\Install-Package PollyGrpc -Version 1.0.1
<PackageReference Include="PollyGrpc" Version="1.0.1" />
<PackageVersion Include="PollyGrpc" Version="1.0.1" />
<PackageReference Include="PollyGrpc" />
paket add PollyGrpc --version 1.0.1
#r "nuget: PollyGrpc, 1.0.1"
#:package PollyGrpc@1.0.1
#addin nuget:?package=PollyGrpc&version=1.0.1
#tool nuget:?package=PollyGrpc&version=1.0.1
PollyGrpc
Polly v8 resilience for gRPC .NET — retry, timeout, and circuit-breaker for any gRPC unary call, plus a built-in GrpcTransientErrors predicate covering the most common transient status codes. Works with any generated gRPC client, GrpcChannel, or CallInvoker.
// Before
var reply = await client.SayHelloAsync(new HelloRequest { Name = "world" });
// After — automatic retry + timeout on every call
var resilient = channel.WithPolly(pipeline =>
pipeline
.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 3,
ShouldHandle = GrpcTransientErrors.IsTransient, // built-in ✔
})
.AddTimeout(TimeSpan.FromSeconds(10)));
var reply = await resilient.ExecuteAsync(ct =>
client.SayHelloAsync(new HelloRequest { Name = "world" }, cancellationToken: ct));
Installation
dotnet add package PollyGrpc
Targets net6.0, net8.0, and net9.0.
Dependencies: Polly.Core 8.*, Grpc.Net.Client 2.*, Microsoft.Extensions.DependencyInjection.Abstractions 8.*
GrpcTransientErrors — the key feature
Knowing which gRPC status codes are safe to retry is the hard part. PollyGrpc ships GrpcTransientErrors.IsTransient so you never have to look them up.
new RetryStrategyOptions
{
MaxRetryAttempts = 3,
ShouldHandle = GrpcTransientErrors.IsTransient,
}
Covered status codes
| Code | Name | Description |
|---|---|---|
4 |
DeadlineExceeded | Request timed out before server could respond |
8 |
ResourceExhausted | Quota or rate limit exceeded (like HTTP 429) |
10 |
Aborted | Operation aborted — transaction conflict; safe to retry |
14 |
Unavailable | Server temporarily unavailable — most common transient gRPC error |
Tip:
StatusCode.Internal (13)can also be transient (connection reset). If you see it in logs, extend the predicate:
var myErrors = GrpcTransientErrors.StatusCodes.ToHashSet();
myErrors.Add(StatusCode.Internal);
new RetryStrategyOptions
{
ShouldHandle = new PredicateBuilder()
.Handle<RpcException>(ex => myErrors.Contains(ex.StatusCode))
}
Quick start
Approach 1 — ResilientGrpcChannel (simplest)
Wrap your existing GrpcChannel and pass any lambda that makes a gRPC call:
using PollyGrpc;
var channel = GrpcChannel.ForAddress("https://my-service:5001");
var resilient = channel.WithPolly(pipeline =>
pipeline
.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromMilliseconds(200),
BackoffType = DelayBackoffType.Exponential,
UseJitter = true,
ShouldHandle = GrpcTransientErrors.IsTransient,
})
.AddTimeout(TimeSpan.FromSeconds(10)));
var client = new Greeter.GreeterClient(channel);
// Unary call — pass AsyncUnaryCall directly
var reply = await resilient.ExecuteAsync(ct =>
client.SayHelloAsync(new HelloRequest { Name = "world" }, cancellationToken: ct));
// Or pass .ResponseAsync explicitly
var reply2 = await resilient.ExecuteAsync(ct =>
client.SayHelloAsync(new HelloRequest { Name = "world" }, cancellationToken: ct).ResponseAsync);
Approach 2 — PollyClientInterceptor (for typed clients)
The interceptor approach integrates transparently at the gRPC channel level — no changes to call sites needed:
var options = new PollyGrpcOptions
{
MaxRetries = 3,
BaseDelay = TimeSpan.FromMilliseconds(200),
CallTimeout = TimeSpan.FromSeconds(10),
TransientStatusCodes = GrpcTransientErrors.StatusCodes.ToHashSet(),
};
var channel = GrpcChannel.ForAddress("https://my-service:5001",
new GrpcChannelOptions
{
Interceptors = { new PollyClientInterceptor(options) }
});
// All calls through this channel are automatically protected
var client = new Greeter.GreeterClient(channel);
var reply = await client.SayHelloAsync(new HelloRequest { Name = "world" });
Approach 3 — Dependency injection
// Program.cs
builder.Services.AddPollyGrpc("https://my-service:5001", pipeline =>
pipeline
.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromMilliseconds(200),
BackoffType = DelayBackoffType.Exponential,
UseJitter = true,
ShouldHandle = GrpcTransientErrors.IsTransient,
})
.AddTimeout(TimeSpan.FromSeconds(10))
.AddCircuitBreaker(new CircuitBreakerStrategyOptions
{
FailureRatio = 0.5,
MinimumThroughput = 10,
SamplingDuration = TimeSpan.FromSeconds(30),
BreakDuration = TimeSpan.FromSeconds(15),
}));
// Service
public class GreeterService(ResilientGrpcChannel resilient, GrpcChannel channel)
{
private readonly Greeter.GreeterClient _client = new(channel);
public Task<HelloReply> SayHelloAsync(string name, CancellationToken ct = default) =>
resilient.ExecuteAsync(token =>
_client.SayHelloAsync(new HelloRequest { Name = name }, cancellationToken: token), ct);
}
ResilientGrpcChannel methods
| Method | Description |
|---|---|
ExecuteAsync<T>(Func<CancellationToken, AsyncUnaryCall<T>>) |
Unary call — automatically disposes the call handle |
ExecuteAsync<T>(Func<CancellationToken, Task<T>>) |
Any async operation returning T |
ExecuteAsync(Func<CancellationToken, Task>) |
Any async operation with no return value |
Pipeline order
[Timeout] → [Retry] → [Circuit Breaker] → [gRPC server]
pipeline
.AddTimeout(TimeSpan.FromSeconds(10)) // 1. Per-attempt deadline
.AddRetry(retryOptions) // 2. Retry transient failures
.AddCircuitBreaker(cbOptions) // 3. Open circuit under load
Related packages
| Package | Downloads | Description |
|---|---|---|
| PollyMediatR | Polly v8 resilience for MediatR | |
| PollyRedis | Polly v8 resilience for StackExchange.Redis | |
| PollyNpgsql | Polly v8 resilience for Npgsql (PostgreSQL) with PostgresTransientErrors predicate | |
| PollySqlClient | Polly v8 resilience for SQL Server and Azure SQL with SqlServerTransientErrors predicate | |
| PollyCosmosDb | Polly v8 resilience for Azure Cosmos DB with CosmosTransientErrors predicate | |
| PollyAzureServiceBus | Polly v8 resilience for Azure Service Bus | |
| PollyAzureBlob | Polly v8 resilience for Azure Blob Storage | |
| PollyEFCore | Polly v8 resilience for Entity Framework Core | |
| PollyDapper | Polly v8 resilience for Dapper | |
| PollyMongo | Polly v8 resilience for MongoDB.Driver | |
| PollyOpenAI | Polly v8 resilience for OpenAI and Azure OpenAI | |
| PollyHealthChecks | ASP.NET Core health checks for Polly v8 circuit breakers | |
| PollyBackoff | Jitter, linear & custom backoff for Polly v8 retry |
| PollyRabbitMQ | Polly v8 resilience for RabbitMQ.Client channels |
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Grpc.Net.Client (>= 2.80.0)
- Grpc.Net.ClientFactory (>= 2.80.0)
- Microsoft.Extensions.Http (>= 8.0.1)
- Polly.Core (>= 8.7.0)
-
net9.0
- Grpc.Net.Client (>= 2.80.0)
- Grpc.Net.ClientFactory (>= 2.80.0)
- Microsoft.Extensions.Http (>= 8.0.1)
- Polly.Core (>= 8.7.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Initial release. PollyClientInterceptor wraps unary and server-streaming gRPC calls with Polly v8 retry, circuit breaker, and timeout. AddPollyGrpcResilience() extension for IHttpClientBuilder.