Skip to content

Add database pool heartbeat support#399

Merged
binaryfire merged 16 commits into
0.4from
feat/database-pool-heartbeat
Jun 24, 2026
Merged

Add database pool heartbeat support#399
binaryfire merged 16 commits into
0.4from
feat/database-pool-heartbeat

Conversation

@binaryfire

@binaryfire binaryfire commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • add opt-in heartbeat sweeps for idle database pool connections
  • validate minimum idle connections with raw PDO pings that bypass query instrumentation
  • discard failed, expired, timed-out, or invalid pooled connections so the next borrow reconnects cleanly
  • replace stale lastUseTime = 0.0 invalidation with explicit invalid state for database and Redis pooled connections
  • document heartbeat_timeout and remove the completed Boost todo

Testing

  • ./vendor/bin/phpunit tests/Integration/Database/Sqlite/DbPoolHeartbeatTest.php
  • ./vendor/bin/phpunit tests/Integration/Database/PooledConnectionTest.php
  • ./vendor/bin/phpunit tests/Redis/RedisConnectionTest.php
  • composer fix

Summary by CodeRabbit

  • New Features
    • Added configurable heartbeat_timeout support for database connection pools.
    • Implemented pooled connection heartbeat validation to keep minimum connections warm and discard expired/idle connections.
  • Bug Fixes
    • Improved pooled connection health handling by invalidating problematic connections and resetting per-borrow execution error counters for the next checkout.
  • Documentation
    • Updated pool configuration docs to describe heartbeat_timeout and heartbeat behavior.
  • Tests
    • Expanded integration test coverage for heartbeat behavior, recycling/discard rules, and error handling.

Add a heartbeat_timeout pool option alongside the existing heartbeat interval.

Expose the timeout through the pool option contract and concrete PoolOption class so database pools can use a dedicated validation timeout without reusing driver query or read timeouts.

Keep the default at 1 second and preserve existing pool behavior when the option is not configured.
Replace the implicit stale timestamp sentinel with an explicit invalid flag on base pooled connections.

Check invalid state before idle-time freshness so a recently released wrapper cannot mask a failed connection.

Keep reconnect implementations responsible for clearing invalid state when they successfully rebuild the underlying connection.
Mark Redis connections invalid after retry reconnect failures instead of zeroing lastUseTime.

Clear the invalid flag when standalone or cluster Redis reconnects successfully.

This prevents a fresh release timestamp from making a failed pooled Redis connection look reusable.
Reset database connection error counts as part of resetForPool().

Pooled database connections are reused across requests, so execution error counts must belong to the current borrow window instead of accumulating for the worker lifetime.

PooledConnection snapshots the count before reset in a later commit so the stale-connection threshold still works.
Add the PooledConnection support needed by database pool heartbeats.

Track invalid wrappers explicitly, split idle eviction time from checkout freshness, and add a raw PDO ping path that uses only already-open PDO handles.

Run pings in a static child coroutine with a buffered result channel so timeout handling cannot requeue discarded wrappers or retain the full connection wrapper graph.

Snapshot database error counts before resetForPool() so the stale threshold is still honored while resetting counts for the next borrow window.
Start one heartbeat timer per worker-local database pool when heartbeat is configured above zero.

Sweep only idle channel entries, keep borrowed connections untouched, evict expired idle extras above the minimum pool size, and validate minimum idle connections through the PooledConnection raw ping path.

Discard failed or timed-out wrappers cleanly so the next borrow opens a fresh connection, and clear heartbeat timers when pools are flushed or destroyed.
Add a Laravel-style configuration section for database connection pools.

Add heartbeat_timeout to the configured database pool blocks so applications can tune heartbeat validation separately from connection and wait timeouts.

Leave Redis pool blocks unchanged because this implementation only adds database pool heartbeat behavior.
Document database pool heartbeat_timeout and clarify how heartbeat validation works.

Explain that heartbeat pings keep minimum idle connections warm, bypass query instrumentation, and only evict idle extras above the minimum pool size.

Remove the completed heartbeat todo and keep the follow-up max-lifetime recycling item for a separate decision.
Add integration coverage for database pool heartbeat behavior.

Cover disabled timers, timer cleanup, minimum connection validation, expired idle extra eviction, lazy PDO preservation, instrumentation bypass, borrowed connection isolation, failed ping discard, invalid wrapper discard, and timeout handling without late requeue.

Use file-backed SQLite and direct heartbeat invocation for deterministic parallel-safe coverage.
Add regression coverage for explicit invalid pooled connection state.

Verify database wrappers reconnect even with a fresh release timestamp, snapshot error counts before reset, and reset smaller error counts for the next borrow window.

Verify Redis retry failures mark connections invalid, reconnect clears invalid state, and fresh release timestamps do not mask invalid Redis wrappers.
@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@binaryfire, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 37 minutes and 18 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 33448d14-4035-4894-86ed-522a7b945b22

📥 Commits

Reviewing files that changed from the base of the PR and between 6710fc2 and e1c8940.

📒 Files selected for processing (1)
  • tests/Integration/Database/Sqlite/DbPoolHeartbeatTest.php
📝 Walkthrough

Walkthrough

Adds a heartbeat_timeout pool option and timer-driven heartbeat mechanism to DbPool that periodically sweeps idle connections, pinging or evicting them based on age and health. Introduces an invalid boolean flag to the base pool Connection class with validity toggle helpers, propagates this to PooledConnection (which refactors ping to run asynchronously via coroutine channels) and Redis connections, and resets errorCount on connection return to pool. Also adds Coroutine::cancelById() for explicit coroutine cancellation with optional exception throwing.

Changes

DB Pool Heartbeat and Connection Validity

Layer / File(s) Summary
Pool validity contract and heartbeat_timeout option
src/pool/src/Connection.php, src/contracts/src/Pool/PoolOptionInterface.php, src/pool/src/PoolOption.php, src/pool/src/Pool.php
Adds protected bool $invalid, markInvalid()/markValid(), and updated check() idle-expiry logic to base Connection. Adds getHeartbeatTimeout() to PoolOptionInterface, extends PoolOption with heartbeatTimeout constructor parameter and getter/setter, and wires heartbeat_timeout (default 1.0) into Pool::initOption().
PooledConnection invalidation, errorCount reset, and async ping
src/database/src/Connection.php, src/database/src/Pool/PooledConnection.php
Resets errorCount in Connection::resetForPool() for fresh borrow window. Adds $invalid field to PooledConnection, updates check() and new isIdleExpired() to use max(lastReleaseTime, lastUseTime), refactors ping() to collect and ping open PDOs asynchronously via Channel and go() coroutines, marks connections invalid on too-many-errors or release exception, and adds hasOpenTransaction(), getOpenPdos(), pingPdos(), markInvalid()/markValid() helpers.
DbPool heartbeat scheduling and sweep logic
src/database/src/Pool/DbPool.php
Adds Timer-based heartbeat: constructor initializes timer and calls startHeartbeat() after parent setup; __destruct() and flushAll() call clearHeartbeat(). startHeartbeat() schedules periodic sweeps; heartbeat() inspects channel connections; heartbeatConnection() evicts idle-expired or pings/releases/discards; discardHeartbeatConnection() decrements active count and closes with logging; getLogger() conditionally returns StdoutLoggerInterface from container.
Redis connection validity propagation
src/redis/src/PhpRedisConnection.php, src/redis/src/PhpRedisClusterConnection.php, src/redis/src/RedisConnection.php
PhpRedisConnection and PhpRedisClusterConnection call markValid() after successful reconnect. RedisConnection::retry() calls markInvalid() instead of zeroing lastUseTime when reconnect fails.
Configuration defaults and documentation
src/foundation/config/database.php, src/boost/docs/database.md, src/boost/todo.md
Adds heartbeat_timeout => 1.0 to mysql, mariadb, pgsql, and pgsql-pooled pool configs with comment section on pool array usage and heartbeat validation semantics. Updates database.md examples and pooling description for heartbeat_timeout. Updates todo from heartbeat support to max-lifetime idle recycling requirement.
Heartbeat integration and unit tests
tests/Integration/Database/PooledConnectionTest.php, tests/Integration/Database/Sqlite/DbPoolHeartbeatTest.php
Adds PooledConnectionTest cases for invalid-state reconnect and errorCount reset. Adds 12-case DbPoolHeartbeatTest covering disabled/enabled timer, min-connection warm/evict, lazy PDO non-realization, ping instrumentation isolation, idle-only sweep, failed/invalid/timeout discard, logger-throw handling, and post-flush discard. Includes test stubs for failure, flush, slow, and transaction scenarios.
Redis validity unit tests
tests/Redis/RedisConnectionTest.php
Adds : void return types across test methods. Renames retry-failure test to assert markInvalid() via isInvalidForTest() helper. Adds testReconnectClearsInvalidState and testInvalidStateIsNotMaskedByFreshReleaseTime to verify reconnect() clears invalid flag and check() respects invalid state with fresh release time.

Coroutine Cancellation Support

Layer / File(s) Summary
Coroutine cancellation interface and implementation
src/contracts/src/Engine/CoroutineInterface.php, src/engine/src/Coroutine.php
Updates CoroutineInterface to declare cancelById(int $id, bool $throwException = false): bool. Implements Coroutine::cancelById() forwarding to SwooleCo::cancel with optional exception throwing. Changes Coroutine::exists() to require non-null int $id instead of optional parameter.
Coroutine cancellation tests
tests/Engine/CoroutineTest.php
Updates test methods with explicit : void return types. Updates testCoroutineCancelById to catch CanceledException and call Coroutine::cancelById(..., throwException: true), asserting cancellation via channel/exists checks.

Sequence Diagram(s)

sequenceDiagram
  participant Timer
  participant DbPool
  participant PooledConnection
  participant Channel
  participant PDO

  rect rgba(135, 206, 235, 0.5)
    Note over Timer,DbPool: Heartbeat sweep (periodic tick)
    Timer-->>DbPool: tick callback
    DbPool->>DbPool: heartbeat()
    loop each channel connection
      DbPool->>PooledConnection: isIdleExpired()
      alt idle-expired
        DbPool->>DbPool: discardHeartbeatConnection()
      else valid candidate
        DbPool->>PooledConnection: ping(heartbeatTimeout)
        PooledConnection->>PooledConnection: getOpenPdos()
        PooledConnection->>Channel: new Channel(count)
        loop each PDO
          PooledConnection->>Channel: go { SELECT 1 }
        end
        Channel-->>PooledConnection: collect results
        alt ping success and generation match
          DbPool->>DbPool: release back to pool
        else ping failed or post-flush
          DbPool->>DbPool: discardHeartbeatConnection()
        end
      end
    end
  end

  rect rgba(144, 238, 144, 0.5)
    Note over DbPool: flushAll() or destruct
    DbPool->>Timer: clearHeartbeat()
    DbPool->>DbPool: parent::flushAll()
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Poem

🐇 Hark! The heartbeat ticks and hums,
Each idle socket swept and checked—
If PDOs grow cold and numb,
markInvalid() takes effect!
Through channels swift, the ping runs true,
The pool stays fresh, connections pruned anew! 🕐

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.94% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add database pool heartbeat support' clearly and concisely summarizes the main change—adding heartbeat functionality to database connection pools, which is the primary feature across the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/database-pool-heartbeat

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jun 23, 2026

Copy link
Copy Markdown

Greptile Summary

This PR wires up the long-planned opt-in heartbeat feature for database connection pools: a per-pool timer fires at the configured interval, inspects every idle connection, evicts expired extras above min_connections, cancels timed-out pings via Coroutine::cancelById, and discards anything that fails.

  • Heartbeat sweep (DbPool): timer-driven loop pops idle connections, tests them with a raw SELECT 1 that bypasses query instrumentation, and uses a generation counter to safely discard connections that outlive a concurrent flushAll().
  • Explicit invalid state (Pool\Connection, PooledConnection, Redis connections): replaces the fragile lastUseTime = 0.0 invalidation sentinel with a proper $invalid flag; check() now also uses max(lastReleaseTime, lastUseTime) for the idle-time calculation.
  • Error-count fix (PooledConnection::release()): snapshots getErrorCount() before resetForPool() clears it, so the stale-threshold check sees the pre-reset value; resetForPool() now zeroes errorCount for each new borrow window.

Confidence Score: 5/5

Safe to merge — the heartbeat feature is opt-in (disabled by default with heartbeat: -1) and the correctness fixes to the invalid-flag and error-count paths are well-isolated with solid regression coverage.

All identified edge cases are handled: double-decrement is impossible because discardHeartbeatConnection cannot throw (logger calls are wrapped in logHeartbeatError); goroutine accumulation under sustained timeouts is prevented by Coroutine::cancelById with throwException: true; flush-during-ping is caught by the generation counter; and the error-count snapshot ensures the stale threshold evaluates the right value. The test suite covers all of these scenarios including the coroutine-cancellation regression.

No files require special attention. The most complex logic is in DbPool::heartbeatConnection() and PooledConnection::ping(), both of which are thoroughly covered by the new integration tests in DbPoolHeartbeatTest.

Important Files Changed

Filename Overview
src/database/src/Pool/DbPool.php Adds heartbeat timer support: start/clear lifecycle, per-sweep connection inspection, generation-counter guard for flush-during-ping, and logger-isolated discard.
src/database/src/Pool/PooledConnection.php Adds invalid flag, ping() with child-coroutine cancellation on timeout, isIdleExpired(), getOpenPdos()/pingPdos() helpers, and error-count snapshot fix before resetForPool.
src/pool/src/Connection.php Adds invalid flag, markInvalid()/markValid() helpers, and fixes idle-time check to use max(lastReleaseTime, lastUseTime) instead of only lastUseTime.
src/pool/src/PoolOption.php Adds heartbeatTimeout field with getter/setter; constructor default is 1.0 seconds.
src/pool/src/Pool.php Threads heartbeat_timeout option through initOption() to PoolOption.
src/engine/src/Coroutine.php Adds cancelById() wrapping SwooleCo::cancel(); aligns exists() signature to int $id (removing unused ?int = null default that conflicted with the interface).
src/contracts/src/Engine/CoroutineInterface.php Adds cancelById(int $id, bool $throwException = false): bool to the coroutine contract.
src/database/src/Connection.php Resets errorCount to 0 in resetForPool() so each borrow window starts with a clean counter.
src/redis/src/RedisConnection.php Replaces the stale lastUseTime = 0.0 invalidation pattern with markInvalid() in the retry-failure path.
src/redis/src/PhpRedisConnection.php Calls markValid() on successful reconnect to clear any previously set invalid flag.
src/redis/src/PhpRedisClusterConnection.php Calls markValid() on successful reconnect, mirroring the PhpRedisConnection fix.
tests/Integration/Database/Sqlite/DbPoolHeartbeatTest.php New 481-line integration test covering disabled/enabled timer lifecycle, idle eviction above minimum, lazy PDO non-realization, instrumentation bypass, flush-during-ping discard, timeout/cancel, and double-decrement regression.
tests/Integration/Database/PooledConnectionTest.php Adds three new tests: invalid flag overrides fresh release time, error-count snapshot before reset, and error-count reset enabling same-connection reuse.
tests/Engine/CoroutineTest.php Adds testCoroutineCancelById verifying that cancelById with throwException: true delivers CanceledException and the coroutine no longer exists; also adds return-type annotations to all existing tests.
tests/Redis/RedisConnectionTest.php Renames retry-failure test to testRetryFailureMarksConnectionInvalid, adds testReconnectClearsInvalidState and testInvalidStateIsNotMaskedByFreshReleaseTime; adds return-type annotations throughout.

Reviews (3): Last reviewed commit: "test(database): harden heartbeat timeout..." | Re-trigger Greptile

Comment thread src/database/src/Pool/DbPool.php
Comment thread src/database/src/Pool/PooledConnection.php

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
tests/Integration/Database/Sqlite/DbPoolHeartbeatTest.php (1)

323-382: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Scope helper classes to a test-specific namespace.

Lines 323-382 add reusable helper classes in a shared namespace; this increases collision risk across the suite.

As per coding guidelines, tests/**/*Test.php: “Use test-specific namespaces including the test class name for helper classes to avoid Cannot redeclare class errors”.

♻️ Suggested direction
- class InspectableHeartbeatDbPool extends DbPool
+ class DbPoolHeartbeatTestInspectableHeartbeatDbPool extends DbPool

- class FailingHeartbeatDbPool extends InspectableHeartbeatDbPool
+ class DbPoolHeartbeatTestFailingHeartbeatDbPool extends DbPoolHeartbeatTestInspectableHeartbeatDbPool

- class SlowHeartbeatDbPool extends InspectableHeartbeatDbPool
+ class DbPoolHeartbeatTestSlowHeartbeatDbPool extends DbPoolHeartbeatTestInspectableHeartbeatDbPool

Also update createPool() default $poolClass and call sites (Line 226, Line 267) to the renamed classes (or move these helpers into a namespace that includes DbPoolHeartbeatTest).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Integration/Database/Sqlite/DbPoolHeartbeatTest.php` around lines 323 -
382, The helper classes (InspectableHeartbeatDbPool, FailingHeartbeatDbPool,
FailingHeartbeatPooledConnection, SlowHeartbeatDbPool,
SlowHeartbeatPooledConnection, and SlowHeartbeatPdo) are defined in a shared
namespace and risk colliding with other tests in the suite. Move these classes
into a test-specific namespace that includes DbPoolHeartbeatTest to comply with
coding guidelines. Update all references to these classes throughout the test
file, including the default $poolClass parameter in the createPool() method and
any call sites that instantiate or reference these helper classes, to use the
new namespaced class names.

Source: Coding guidelines

tests/Redis/RedisConnectionTest.php (1)

2112-2112: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add explicit : void return types to the new test methods.

Lines 2112, 2170, and 2211 introduce test methods without return types.

As per coding guidelines, tests/**/*.php: “Add : void return types to test methods”.

♻️ Suggested fix
-    public function testRetryFailureMarksConnectionInvalid()
+    public function testRetryFailureMarksConnectionInvalid(): void

-    public function testReconnectClearsInvalidState()
+    public function testReconnectClearsInvalidState(): void

-    public function testInvalidStateIsNotMaskedByFreshReleaseTime()
+    public function testInvalidStateIsNotMaskedByFreshReleaseTime(): void

Also applies to: 2170-2170, 2211-2211

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Redis/RedisConnectionTest.php` at line 2112, Add explicit `: void`
return type declarations to the three test methods in RedisConnectionTest.php:
testRetryFailureMarksConnectionInvalid at line 2112, and the two additional test
methods at lines 2170 and 2211. Each test method signature should be updated
from `public function methodName()` to `public function methodName(): void` to
comply with the coding guidelines requiring return types on all test methods.

Source: Coding guidelines

src/database/src/Pool/DbPool.php (1)

74-76: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add the required title docblock for __destruct().

As per coding guidelines, “Add Laravel-style title docblocks to methods” and “method docblocks (title only) are always added.”

Proposed fix
+    /**
+     * Clear the heartbeat timer.
+     */
     public function __destruct()
     {
         $this->clearHeartbeat();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/database/src/Pool/DbPool.php` around lines 74 - 76, The `__destruct()`
method in the DbPool class is missing a required title docblock. Add a
Laravel-style title docblock above the `__destruct()` method definition that
briefly describes what the method does, such as describing its cleanup
responsibility for heartbeat functionality.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/database/src/Pool/DbPool.php`:
- Around line 131-134: The clearHeartbeat() call in flushAll() does not prevent
in-flight heartbeat coroutines from requeueing connections back into the pool
after the flush completes. Implement a generation counter that increments each
time flushAll() is called, and capture the current generation value before
initiating any heartbeat ping operation. After a heartbeat ping completes
successfully but before calling release() to requeue the connection, check if
the generation has changed since the ping started. If the generation has changed
(indicating a concurrent flushAll() occurred), discard the connection instead of
calling release() to prevent stale connections from being requeued into the
flushed pool.
- Around line 146-149: The arrow closure in the heartbeat timer callback is
ignoring the `$isClosing` parameter provided by Timer::tick() and strongly
capturing `$this`, which prevents proper pool cleanup during shutdown. Modify
the closure to accept the `$isClosing` boolean parameter and return early when
it is true to stop the timer immediately during shutdown. Additionally, use a
weak reference to `$this` instead of a strong capture to avoid keeping the pool
alive unnecessarily and to ensure `__destruct()` can be called when the pool is
dropped elsewhere. Update the closure definition in the tick method call to
implement these shutdown-aware behaviors.

---

Nitpick comments:
In `@src/database/src/Pool/DbPool.php`:
- Around line 74-76: The `__destruct()` method in the DbPool class is missing a
required title docblock. Add a Laravel-style title docblock above the
`__destruct()` method definition that briefly describes what the method does,
such as describing its cleanup responsibility for heartbeat functionality.

In `@tests/Integration/Database/Sqlite/DbPoolHeartbeatTest.php`:
- Around line 323-382: The helper classes (InspectableHeartbeatDbPool,
FailingHeartbeatDbPool, FailingHeartbeatPooledConnection, SlowHeartbeatDbPool,
SlowHeartbeatPooledConnection, and SlowHeartbeatPdo) are defined in a shared
namespace and risk colliding with other tests in the suite. Move these classes
into a test-specific namespace that includes DbPoolHeartbeatTest to comply with
coding guidelines. Update all references to these classes throughout the test
file, including the default $poolClass parameter in the createPool() method and
any call sites that instantiate or reference these helper classes, to use the
new namespaced class names.

In `@tests/Redis/RedisConnectionTest.php`:
- Line 2112: Add explicit `: void` return type declarations to the three test
methods in RedisConnectionTest.php: testRetryFailureMarksConnectionInvalid at
line 2112, and the two additional test methods at lines 2170 and 2211. Each test
method signature should be updated from `public function methodName()` to
`public function methodName(): void` to comply with the coding guidelines
requiring return types on all test methods.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9406202d-5cef-47a8-82bb-d697fb658a74

📥 Commits

Reviewing files that changed from the base of the PR and between 2030851 and a876198.

📒 Files selected for processing (16)
  • src/boost/docs/database.md
  • src/boost/todo.md
  • src/contracts/src/Pool/PoolOptionInterface.php
  • src/database/src/Connection.php
  • src/database/src/Pool/DbPool.php
  • src/database/src/Pool/PooledConnection.php
  • src/foundation/config/database.php
  • src/pool/src/Connection.php
  • src/pool/src/Pool.php
  • src/pool/src/PoolOption.php
  • src/redis/src/PhpRedisClusterConnection.php
  • src/redis/src/PhpRedisConnection.php
  • src/redis/src/RedisConnection.php
  • tests/Integration/Database/PooledConnectionTest.php
  • tests/Integration/Database/Sqlite/DbPoolHeartbeatTest.php
  • tests/Redis/RedisConnectionTest.php

Comment thread src/database/src/Pool/DbPool.php
Comment thread src/database/src/Pool/DbPool.php
Remove the dev-only swoole/ide-helper dependency from the components repository because the package is behind the current Swoole runtime signatures and can mislead static analysis and IDEs.

Also remove the Boost installation docs sentence that said the application skeleton includes the helper, since that is no longer true.
Add a low-level cancelById wrapper to the engine coroutine contract and Swoole-backed implementation so infrastructure code can cancel a specific coroutine without going through the high-level coroutine facade.

Narrow the engine exists signature to match Swoole's required coroutine id parameter and add regression coverage proving cancelById throws cancellation into the target coroutine and prevents late work.

Keep the direct Swoole 6.2 cancel call and use a local PHPStan ignore for the stale bundled Swoole stub until that upstream stub exposes the throw_exception parameter.
Cancel timed-out heartbeat ping coroutines with exception delivery so the child coroutine does not keep holding PDO references until the underlying socket operation returns.

Track heartbeat generations on the pool so a successful in-flight heartbeat cannot requeue a connection after flush or shutdown has already cleared the timer.

Make heartbeat cleanup logging best-effort while keeping the connection count synchronized with the discard decision before any yielding close work can run.

Add regression coverage for timeout cancellation, flush-during-ping requeue prevention, and logger failures during heartbeat discard cleanup.
Add missing void return types to Redis connection test methods touched by the PR review cleanup.

This is a mechanical test-only typing cleanup with no Redis behavior changes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/Integration/Database/Sqlite/DbPoolHeartbeatTest.php`:
- Around line 283-288: The assertions checking elapsed time and coroutine
existence are too strict and fail intermittently due to scheduler timing
variations. In the test method containing these assertions, increase the elapsed
time threshold in the assertLessThan call (the 0.05 value is too tight), and
instead of directly asserting that Coroutine::exists returns false immediately,
implement an eventual check that retries the assertion multiple times within a
reasonable window (such as checking in a loop for up to 1 second) to account for
scheduler jitter before considering the coroutine properly cleaned up.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5dc2be5c-e8a3-440a-a910-c9bf3475872f

📥 Commits

Reviewing files that changed from the base of the PR and between a876198 and 6710fc2.

📒 Files selected for processing (7)
  • src/contracts/src/Engine/CoroutineInterface.php
  • src/database/src/Pool/DbPool.php
  • src/database/src/Pool/PooledConnection.php
  • src/engine/src/Coroutine.php
  • tests/Engine/CoroutineTest.php
  • tests/Integration/Database/Sqlite/DbPoolHeartbeatTest.php
  • tests/Redis/RedisConnectionTest.php
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/database/src/Pool/DbPool.php
  • src/database/src/Pool/PooledConnection.php

Comment thread tests/Integration/Database/Sqlite/DbPoolHeartbeatTest.php Outdated
Increase the fake slow heartbeat query delay so the timeout regression test has a clear timing gap between the correct cancellation path and the broken no-cancel path.

Keep the elapsed assertion strict enough to fail if timeout cancellation stops working, while avoiding CI noise from a threshold that matched the old fake delay too closely.

Add a short bounded cleanup wait before asserting the timed-out child coroutine is gone, keeping the wait below the fake query delay so broken cancellation still fails.
@binaryfire binaryfire merged commit 6b02d31 into 0.4 Jun 24, 2026
35 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant