@@ -23,31 +23,94 @@ It guarantees **no blocking**, **no busy-waiting**, and **safe use from multiple
2323
2424## Initialization
2525
26+ ` PgPool ` now optionally supports a background health checker for periodic connection validation.
27+
2628``` cpp
2729usub::pg::PgPool::init_global (
2830 host, port, user, db, password,
29- max_pool_size, queue_capacity
31+ max_pool_size, queue_capacity,
32+ usub::pg::PgHealthConfig{
33+ .enabled = true,
34+ .interval_ms = 3000
35+ }
3036);
3137```
3238
33- Example:
39+ When ` .enabled = true ` , the pool automatically spawns an internal coroutine that periodically runs lightweight
40+ ` SELECT 1; ` probes on temporary connections to ensure the database is reachable.
41+
42+ If ` .enabled = false ` (default), the health checker is disabled.
43+
44+ You can access runtime statistics at any time via:
3445
3546``` cpp
36- usub::pg::PgPool::init_global (
37- "localhost",
38- "5432",
39- "postgres",
40- "mydb",
41- "password",
42- 32, // max_pool_size
43- 64 // queue_capacity
44- );
47+ auto & hc = usub::pg::PgPool::instance().health_checker();
48+ auto & stats = hc.stats();
49+
50+ std::cout
51+ << " checks=" << stats.iterations.load()
52+ << " ok=" << stats.ok_checks.load()
53+ << " failed=" << stats.failed_checks.load()
54+ << std::endl;
55+ ```
56+
57+ ---
58+
59+ ## Health Checker
60+
61+ ` PgHealthChecker ` is a lightweight background monitor built into the pool.
62+ It ensures early detection of connection failures without impacting performance.
63+
64+ | Field | Type | Description |
65+ | -----------------| --------------------| -------------------------------------------------|
66+ | ` enabled ` | ` bool ` | Whether the health checker coroutine should run |
67+ | ` interval_ms ` | ` uint64_t ` | Delay between health probes |
68+ | ` iterations ` | ` atomic<uint64_t> ` | Total number of health iterations |
69+ | ` ok_checks ` | ` atomic<uint64_t> ` | Successful ` SELECT 1 ` responses |
70+ | ` failed_checks ` | ` atomic<uint64_t> ` | Failed or unreachable checks |
71+
72+ ### Behavior
4573
46- auto & pool = usub::pg::PgPool::instance();
74+ * When enabled, a coroutine runs forever in the background.
75+ * Every interval:
76+
77+ 1 . Acquires a fresh connection from the pool.
78+ 2 . Executes ` SELECT 1; ` using non-blocking I/O.
79+ 3 . Updates internal counters.
80+ 4 . Returns the connection back to the pool.
81+ * Failures are silent — they do not throw or disrupt normal queries.
82+ * Intended for observability and proactive reconnection handling.
83+
84+ ### Example
85+
86+ ``` cpp
87+ task::Awaitable<void > print_pg_health ()
88+ {
89+ auto& pool = usub::pg::PgPool::instance();
90+ auto& hc = pool.health_checker();
91+
92+ for (;;)
93+ {
94+ auto& s = hc.stats();
95+ std::cout
96+ << "[Health] iter=" << s.iterations.load()
97+ << " ok=" << s.ok_checks.load()
98+ << " fail=" << s.failed_checks.load()
99+ << std::endl;
100+
101+ co_await usub::uvent::system::this_coroutine::sleep_for(
102+ std::chrono::seconds (5)
103+ );
104+ }
105+ }
47106```
48107
49- This must be called ** once** during startup.
50- ` instance() ` returns the global singleton thereafter.
108+ ### Notes
109+
110+ * Uses existing event loop and coroutine scheduler (` uvent ` ).
111+ * Generates negligible load on the database (` SELECT 1 ` ).
112+ * Default interval: ** 600 000 ms** .
113+ * Can be disabled completely if unnecessary.
51114
52115---
53116
@@ -201,10 +264,6 @@ This provides natural backpressure and keeps memory footprint predictable.
201264
202265---
203266
204- ## Error transparency (since v1.0.1)
205-
206- All queries and internal operations now report structured diagnostic codes.
207-
208267### Example: Connection loss
209268
210269``` cpp
@@ -244,25 +303,26 @@ if (!res.ok && res.code == PgErrorCode::ServerError)
244303
245304## Best practices
246305
247- - ✅ Initialize pool early (before spawning coroutines).
248- - ✅ Use ` query_awaitable() ` for short-lived queries.
249- - ✅ Use manual ` acquire_connection() ` for long-running listeners or transactions.
250- - ✅ Check ` QueryResult.code ` and ` ok ` for every query.
251- - ✅ Release connections when finished — don’t hold them across awaits.
252- - ❌ Don’t share one connection across threads.
253- - ❌ Don’t assume immediate connection creation — first queries may take longer.
306+ * ✅ Initialize pool early (before spawning coroutines).
307+ * ✅ Use ` query_awaitable() ` for short-lived queries.
308+ * ✅ Use manual ` acquire_connection() ` for long-running listeners or transactions.
309+ * ✅ Check ` QueryResult.code ` and ` ok ` for every query.
310+ * ✅ Release connections when finished — don’t hold them across awaits.
311+ * ❌ Don’t share one connection across threads.
312+ * ❌ Don’t assume immediate connection creation — first queries may take longer.
254313
255314---
256315
257316## Summary
258317
259- | Feature | Description |
260- | --------------| -------------------------------------------|
261- | Asynchronous | Non-blocking coroutine API |
262- | Safe | Lock-free concurrent access |
263- | Lazy | Creates connections only as needed |
264- | Bounded | Max live connections controlled by config |
265- | Transparent | Every error is classified and traceable |
318+ | Feature | Description |
319+ | ----------------| -------------------------------------------|
320+ | Asynchronous | Non-blocking coroutine API |
321+ | Safe | Lock-free concurrent access |
322+ | Lazy | Creates connections only as needed |
323+ | Bounded | Max live connections controlled by config |
324+ | Transparent | Every error is classified and traceable |
325+ | Health Checker | Background connection heartbeat loop |
266326
267327` PgPool ` is the backbone of the upq runtime — it provides scalable, coroutine-friendly database access with
268328deterministic failure semantics.
0 commit comments