Async vs sync
fastgrpc auto-detects whether to run on the async (grpc.aio) or sync (grpc.server thread-pool) gRPC stack based on your handler signatures.
The rule
| Handlers | Server stack |
|---|---|
Any async def |
grpc.aio.server() |
All plain def |
grpc.server(ThreadPoolExecutor) |
| Mixed | ValidationError raised at startup |
The CLI prints which stack was chosen on startup:
or
Why no mixing
The two grpcio stacks are not interoperable. A single server cannot host both sync handlers (which run on a thread pool) and async handlers (which run on an event loop). fastgrpc fails fast with a message that names every offending method:
ValidationError: cannot mix async and sync RPC methods in the same app:
async: UserService.async_method
sync: UserService.sync_method
If you need both, run two services on different ports.
When to pick each
Use async when...
- You call other async APIs (databases like asyncpg, HTTP via httpx, other gRPC services)
- You expect high concurrency with mostly I/O-bound handlers
- You want the standard FastAPI-style ergonomics
@service
class UserService:
@rpc
async def get_user(self, user_id: int) -> User:
async with db.session() as s:
row = await s.fetch_one("SELECT ...", user_id)
return User(**row)
Use sync when...
- Your business logic is CPU-bound (data crunching, ML inference)
- You're calling sync libraries that don't have async equivalents
- You're integrating into an existing sync codebase
@service
class InferenceService:
@rpc
def predict(self, features: list[float]) -> Prediction:
result = model.run(features) # blocking, CPU-bound
return Prediction(score=result.score)
Performance characteristics
| sync stack | async stack | |
|---|---|---|
| Underlying executor | thread pool (max_workers in App) |
asyncio event loop |
| Best for | CPU-bound, blocking I/O via threads | high-concurrency I/O |
| GIL impact | One thread per concurrent request | One coroutine per concurrent request, single thread |
| Cold-start latency | Lower | Slightly higher (event loop setup) |
Both stacks share the same gRPC C core for the wire protocol, so the difference is in the executor model, not the network layer.