Lifecycle and shutdown¶
Open client mechanics¶
Checks that cache a client (Redis, Mongo, Kafka, OpenSearch, URL, RabbitMQ, PostgreSQL) keep one connection per instance. Such checks implement aclose() and are said to hold an open client. Open clients are not closed inside run_probe; they are closed only by the documented shutdown path below.
- Which checks have open clients: Any check that has an
aclosemethod (e.g. those usingClientCachingMixin). Function-based checks and checks withoutaclosedo not hold open clients. - Which shutdown path closes them: Only
healthcheck_shutdown(probes)orclose_probes(probes)(path Y). On cancellation or timeout ofrun_probe, cached clients are not closed; the caller is responsible for calling the shutdown path (path X) so that Y runs.
Cleanup paths (X and Y)¶
- X (when cleanup runs): The caller invokes
healthcheck_shutdown(probes)(orclose_probes(probes)) after using the probes—typically in the framework’s lifespan/shutdown hook. On cancellation or timeout ofrun_probe,run_probedoes not close cached clients; the caller should still call the shutdown path so that resources are closed. - Y (what closes open clients):
close_probes(probes)(and thushealthcheck_shutdown(probes)) callsaclose()on each check that has it. Cached clients are closed only by this path, not insiderun_probe.
After cancel or timeout there are no dangling background tasks from run_probe (the probe’s check tasks are cancelled); a cached client may remain open until the caller invokes Y.
flowchart TD
A[Application Start] --> B[Probes Created]
B --> C{Run Probes?}
C -->|Success| D[Results Returned]
C -->|Cancel/Timeout| E[Path X: Caller invokes healthcheck_shutdown]
D --> F[Path X: Caller invokes healthcheck_shutdown]
E --> G{Open Clients?}
F --> G
G -->|Yes| H[Path Y: close_probes calls aclose]
G -->|No| I[Cleanup Complete]
H --> I
J[App Shutdown] --> K[Path Y: close_probes calls aclose]
K --> I
style H fill:#90EE90
style K fill:#90EE90
style I fill:#98FB98
ProbeRunner (Advanced)¶
ProbeRunner is a context manager that automatically manages probe lifecycle and cleanup:
from fast_healthchecks import ProbeRunner, RunPolicy, run_probe
from fast_healthchecks.checks import http
async def main():
async with ProbeRunner() as runner:
probes = [
http("https://api.example.com/health", timeout_ms=1000),
]
results = await runner.run_all(probes)
# Cached clients are automatically closed when exiting the context
# Resources are already cleaned up here
Benefits¶
- Automatic cleanup: Cached clients are closed automatically when exiting the
async withblock—no need to callhealthcheck_shutdown()manually. - Reusable runner: The same
ProbeRunnerinstance can be reused across multiplerun_all()calls within the context. - Timeout control: Pass
RunPolicyto customize execution:
async with ProbeRunner(
policy=RunPolicy(
probe_timeout_ms=2000,
execution="all", # or "first"
result_on_error="unhealthy"
)
) as runner:
results = await runner.run_all(probes)
When to use¶
- Use
ProbeRunnerwhen: You want reusable runner with automatic resource cleanup, or need fine-grained control viaRunPolicy. - Use
run_probe()when: Simple one-off probe execution is sufficient (cleanup still requireshealthcheck_shutdown()).
Manual resource management¶
If you prefer explicit control instead of using async with, you can manage the runner manually:
from fast_healthchecks import ProbeRunner
async def main():
runner = ProbeRunner()
try:
results = await runner.run_all(probes)
finally:
await runner.close()
ProbeRunner with ASGI lifespan¶
For integration with ASGI applications (FastAPI, Starlette, Litestar), you can combine ProbeRunner with the framework's lifespan:
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fast_healthchecks import ProbeRunner, run_probe
from fast_healthchecks.checks import http
probes = [http("https://api.example.com/health")]
@asynccontextmanager
async def lifespan(app: FastAPI):
runner = ProbeRunner()
app.state.runner = runner
yield
await runner.close()
app = FastAPI(lifespan=lifespan)
@app.get("/health")
async def health():
runner = app.state.runner
return await runner.run_all(probes)
This pattern ensures proper cleanup when the application shuts down.
Framework shutdown¶
To close health check resources on app shutdown:
- FastAPI: Store the router and call
await router.close()in your lifespan context manager (afteryield), or usehealthcheck_shutdown(probes)and call the returned callback in lifespan. - FastStream: Pass the callback from
healthcheck_shutdown(probes)into your app'son_shutdownlist, e.g.AsgiFastStream(..., on_shutdown=[healthcheck_shutdown(probes)]). - Litestar: Pass the callback from
healthcheck_shutdown(probes)into the app'son_shutdownlist, e.g.Litestar(..., on_shutdown=[healthcheck_shutdown(probes)]).
Import healthcheck_shutdown from fast_healthchecks.integrations.fastapi, fast_healthchecks.integrations.faststream, or fast_healthchecks.integrations.litestar.