Production Readiness Design
Overview
Add four production-grade features to FastGRPC: TLS/mTLS, graceful shutdown, health checking, and timeout/deadline control. Each feature is independent and opt-in, following the existing FastAPI-style philosophy.
Feature 1: TLS/mTLS
Scope
app.py:run()andrun_async()methods
New Parameters on run() / run_async()
| Parameter | Type | Default | Description |
|---|---|---|---|
ssl_certificate_chain |
str \| Path \| None |
None |
Server certificate chain file path (PEM) |
ssl_private_key |
str \| Path \| None |
None |
Server private key file path (PEM) |
ca_certificate |
str \| Path \| None |
None |
CA root certificate path; providing it enables mTLS |
Logic
- If both
ssl_certificate_chainandssl_private_keyare provided: read files, callgrpc.ssl_server_credentials()to build server credentials, useserver.add_secure_port()instead ofadd_insecure_port(). - If
ca_certificateis also provided: passroot_certificatesto enable mTLS (clients must present certificates signed by this CA). - If neither is provided: keep existing behavior (
add_insecure_port).
API Example
# TLS only
app.run(ssl_certificate_chain="server.crt", ssl_private_key="server.key")
# mTLS
app.run(
ssl_certificate_chain="server.crt",
ssl_private_key="server.key",
ca_certificate="ca.crt",
)
Dependencies
None — grpc.ssl_server_credentials is already part of grpcio.
Feature 2: Graceful Shutdown
Scope
app.py:run_async()method
New Parameter
| Parameter | Type | Default | Description |
|---|---|---|---|
graceful_timeout |
float \| None |
None |
Max seconds to wait for in-flight requests to complete after receiving a termination signal. None means immediate shutdown. |
Logic
- Register
SIGTERM/SIGINTsignal handlers in the asyncio event loop. - On signal: log "Shutting down gracefully...", call
server.stop(graceful_timeout). server.stop(None)stops immediately (preserves backward compatibility).server.stop(30.0)waits up to 30 seconds, then force-terminates.- Replace the blocking
server.wait_for_termination()with anasyncio.Eventthat gets set when a signal is received.
API Example
# Production: wait up to 30s for in-flight requests
app.run(graceful_timeout=30.0)
# Dev/test: immediate shutdown (default)
app.run()
Dependencies
None — pure asyncio + grpc.aio capabilities.
Feature 3: Health Checking
Scope
app.py:run()/run_async()andadd_to_server()
New Parameter
| Parameter | Type | Default | Description |
|---|---|---|---|
health_check |
bool |
False |
Enable the gRPC Health Checking protocol (grpc.health.v1.Health) |
Logic
- When enabled, register the
grpc_health.v1.Healthservicer on the gRPC server. - Servicer reports
SERVING/NOT_SERVINGstatus for each registered service. - Natively compatible with K8s gRPC liveness/readiness probes.
API Example
app.run(health_check=True)
Dependencies
grpcio-health-checking>=1.43.0 — lazy-imported, only required when health_check=True.
Feature 4: Timeout and Deadline Control
Scope
app.py:FastGRPC.__init__()and decorator methodsservice.py:BaseMethod.__init__()andBaseMethod.__call__()context.py:ServiceContext
Three-Layer Timeout Configuration
| Level | Location | Description |
|---|---|---|
| Method | @app.unary_unary(timeout=5.0) |
Per-RPC timeout in seconds |
| Service | Service(name="X", timeout=10.0) |
Default timeout for all methods in a service |
| Global | FastGRPC(timeout=30.0) |
Application-level fallback default |
Priority: method > service > global.
Logic
- Decorators accept an optional
timeout: floatparameter, forwarded toBaseMethod.__init__(). - The timeout resolution (method → service → global) happens in
BaseMethod. - Deadline checking strategies differ by RPC mode:
| RPC Mode | Check Strategy |
|---|---|
| Unary-Unary | Check time_remaining() once before await endpoint(). If expired, abort(DEADLINE_EXCEEDED). |
| Stream-Unary | Check once before iterating the request stream; also check before calling endpoint() after collecting all requests. |
| Unary-Stream | Check before each yield in the async generator. If expired mid-stream, abort(DEADLINE_EXCEEDED). |
| Stream-Stream | Check before each yield AND before processing each incoming message. |
ServiceContext.time_remaining()is already exposed and usable by business logic for custom deadline checks.
API Example
# Global fallback
app = FastGRPC(timeout=30.0)
# Method-level override
@app.unary_unary(timeout=5.0)
async def quick_op(request: HelloRequest) -> HelloReply:
...
# Check remaining deadline in business logic
@app.unary_unary()
async def slow_op(request: HelloRequest, ctx: ServiceContext) -> HelloReply:
remaining = ctx.time_remaining()
if remaining < 1.0:
raise TimeoutError("not enough time")
...
Dependencies
None.
Files Affected
| File | Changes |
|---|---|
fast_grpc/app.py |
New params on __init__, run(), run_async(), decorators; signal handling; TLS setup; health check registration |
fast_grpc/service.py |
timeout field on BaseMethod, deadline check in __call__ |
fast_grpc/context.py |
No changes needed (time_remaining() already exists) |
Backward Compatibility
All new parameters have defaults that preserve existing behavior. No breaking changes.