Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
f7d4c3f
Share the coroutine channel used by resource pools
binaryfire Sep 8, 2026
7ae8bd8
Centralize object and connection pool contracts
binaryfire Sep 8, 2026
7234d4b
Replace pool with the connection-pool package and lifecycle API
binaryfire Sep 8, 2026
1198f65
Normalize immutable object pool duration options
binaryfire Sep 8, 2026
0caa47a
Align object pool borrowing and resource ownership APIs
binaryfire Sep 8, 2026
f66583f
Make object pool manager purges complete after cleanup failures
binaryfire Sep 8, 2026
f6d25a8
Simplify object recycler configuration and preserve cancellation
binaryfire Sep 8, 2026
5051578
Align object pool proxies and concern naming
binaryfire Sep 8, 2026
9d6f6c6
Migrate database pooling and fix accepted pool publication
binaryfire Sep 8, 2026
3313ec8
Migrate Redis pooling and preserve concurrent lifecycle ownership
binaryfire Sep 8, 2026
cc6cdab
Update foundation pool configuration and test teardown
binaryfire Sep 8, 2026
03b59f3
Migrate filesystem pooling consumers and facade APIs
binaryfire Sep 8, 2026
5e126c4
Align broadcasting with the new pool manager APIs
binaryfire Sep 8, 2026
2ac7f02
Align mail transport pooling configuration and APIs
binaryfire Sep 8, 2026
7a859df
Align queue client pooling and deferred resource consumers
binaryfire Sep 8, 2026
1b44e86
Make Sentry pool failures and Redis observations precise
binaryfire Sep 8, 2026
920029d
Correct OpenTelemetry pool occupancy observations
binaryfire Sep 8, 2026
f603d04
Document the object and connection pool APIs and lifecycles
binaryfire Sep 8, 2026
d90203d
Use websocket-server as the Hyperf porting reference
binaryfire Sep 8, 2026
76eb524
Document the pool redesign and verification requirements
binaryfire Sep 8, 2026
06e62a1
Merge 0.4 into feature/pool-redesign
binaryfire Sep 8, 2026
ec5e162
Clarify the connection pool construction contract
binaryfire Sep 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -782,7 +782,7 @@ Read `docs/ai/porting-hyperf.md` only when porting a Hyperf package or update.

If the Hypervel version of the package doesn't exist yet, create the skeleton using an existing package as a template:
- **Porting a Laravel first-party package:** Use the `cache` package as reference
- **Porting a Hyperf package:** Use the `pool` package as reference
- **Porting a Hyperf package:** Use the `websocket-server` package as reference
- **Porting a Laravel-ecosystem third-party package:** Use the `permission` package as a reference

Read the reference package's `composer.json`, `LICENSE.md`, and `README.md` and create equivalents for the new package. Every package must be wired in both places: its own `src/{package}/composer.json` for the subtree split, and the root `composer.json` for monorepo development. Update autoloading, `replace`, and Hypervel provider / alias discovery metadata as needed, and add root dependencies with `composer require` — see Providers and Listeners for where providers should be registered. Create the README using the Package READMEs format under Development Conventions.
Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"Hypervel\\Bus\\": "src/bus/src/",
"Hypervel\\Cache\\": "src/cache/src/",
"Hypervel\\Config\\": "src/config/src/",
"Hypervel\\ConnectionPool\\": "src/connection-pool/src/",
"Hypervel\\Console\\": "src/console/src/",
"Hypervel\\Container\\": "src/container/src/",
"Hypervel\\Context\\": "src/context/src/",
Expand Down Expand Up @@ -66,7 +67,6 @@
"Hypervel\\Pagination\\": "src/pagination/src/",
"Hypervel\\Passkeys\\": "src/passkeys/src/",
"Hypervel\\Pipeline\\": "src/pipeline/src/",
"Hypervel\\Pool\\": "src/pool/src/",
"Hypervel\\Process\\": "src/process/src/",
"Hypervel\\Prompts\\": "src/prompts/src/",
"Hypervel\\Queue\\": "src/queue/src/",
Expand Down Expand Up @@ -236,6 +236,7 @@
"hypervel/concurrency": "self.version",
"hypervel/conditionable": "self.version",
"hypervel/config": "self.version",
"hypervel/connection-pool": "self.version",
"hypervel/console": "self.version",
"hypervel/container": "self.version",
"hypervel/context": "self.version",
Expand Down Expand Up @@ -273,7 +274,6 @@
"hypervel/pagination": "self.version",
"hypervel/passkeys": "self.version",
"hypervel/pipeline": "self.version",
"hypervel/pool": "self.version",
"hypervel/process": "self.version",
"hypervel/prompts": "self.version",
"hypervel/queue": "self.version",
Expand Down
2 changes: 1 addition & 1 deletion docs/ai/porting-hyperf.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ Before migrating a ConfigProvider, read:
- `src/docs/packages.md#class-map-overrides` if the package uses class map replacement
- `Hypervel\Support\ServiceProvider`

Use existing Hypervel packages as pattern references. For low-level Swoole / Hyperf-style infrastructure, useful references include `pool`, `object-pool`, `engine`, `server`, `signal`, and `sentry`. The `database` package is a good reference for translating Hyperf provider patterns into Hypervel provider code.
Use `websocket-server` as the package skeleton reference for Composer metadata, licensing, and provider discovery. For provider patterns, see `engine` for contract bindings and `signal` for worker-lifecycle listeners. The `database` package is a useful reference for more involved provider registration. Adapt the relevant pattern rather than copying a package's runtime-specific setup.

### Categorize the ConfigProvider entries

Expand Down
416 changes: 416 additions & 0 deletions docs/plans/2026-09-08-0816-pool-api-and-lifecycle-redesign.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/broadcasting/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@
"symfony/http-kernel": "^8.1",
"hypervel/bus": "^0.4",
"hypervel/collections": "^0.4",
"hypervel/connection-pool": "^0.4",
"hypervel/container": "^0.4",
"hypervel/contracts": "^0.4",
"hypervel/foundation": "^0.4",
"hypervel/http": "^0.4",
"hypervel/object-pool": "^0.4",
"hypervel/pool": "^0.4",
"hypervel/queue": "^0.4",
"hypervel/routing": "^0.4",
"hypervel/support": "^0.4"
Expand Down
12 changes: 6 additions & 6 deletions src/broadcasting/src/BroadcastManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@
use Hypervel\Contracts\Cache\Repository as Cache;
use Hypervel\Contracts\Container\Container;
use Hypervel\Contracts\Foundation\CachesRoutes;
use Hypervel\Contracts\ObjectPool\Factory as PoolFactory;
use Hypervel\Contracts\Queue\Factory as Queue;
use Hypervel\Contracts\Redis\Factory as RedisFactory;
use Hypervel\Foundation\Http\Middleware\PreventRequestForgery;
use Hypervel\Http\Request;
use Hypervel\ObjectPool\Contracts\Factory as PoolFactory;
use Hypervel\ObjectPool\Traits\HasPoolProxy;
use Hypervel\ObjectPool\Concerns\HasPoolProxy;
use Hypervel\Queue\Attributes\Connection as ConnectionAttribute;
use Hypervel\Queue\Attributes\Queue as QueueAttribute;
use Hypervel\Queue\Attributes\ReadsQueueAttributes;
Expand Down Expand Up @@ -70,7 +70,7 @@ class BroadcastManager implements BroadcastingFactoryContract
/**
* The array of drivers which will be wrapped as pool proxies.
*/
protected array $poolables = [];
protected array $poolableDrivers = [];

/**
* Create a new manager instance.
Expand Down Expand Up @@ -310,7 +310,7 @@ protected function resolve(string $name): Broadcaster

$constructionConfig = Arr::except($config, ['pool']);

return in_array($config['driver'], $this->poolables, true)
return in_array($config['driver'], $this->poolableDrivers, true)
? $this->createPoolProxy(
$config['driver'],
fn () => $this->doResolve(null, $constructionConfig),
Expand Down Expand Up @@ -530,7 +530,7 @@ public function purge(UnitEnum|string|null $name = null): void

$config = $this->getConfig($name);

if (is_null($config) || ! in_array($config['driver'], $this->poolables, true)) {
if (is_null($config) || ! in_array($config['driver'], $this->poolableDrivers, true)) {
return;
}

Expand All @@ -541,7 +541,7 @@ public function purge(UnitEnum|string|null $name = null): void
$constructionConfig,
);

$this->poolFactory()->remove($definition->identity);
$this->poolFactory()->purge($definition->identity);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/broadcasting/src/Broadcasters/RedisBroadcaster.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
namespace Hypervel\Broadcasting\Broadcasters;

use Hypervel\Broadcasting\BroadcastException;
use Hypervel\ConnectionPool\Exceptions\ConnectionException;
use Hypervel\Contracts\Container\Container;
use Hypervel\Contracts\Redis\Factory as Redis;
use Hypervel\Http\Request;
use Hypervel\Pool\Exceptions\ConnectionException;
use Hypervel\Support\Arr;
use RedisClusterException;
use RedisException;
Expand Down
File renamed without changes.
6 changes: 2 additions & 4 deletions src/pool/README.md → src/connection-pool/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
Pool for Hypervel
Connection Pool for Hypervel
===

[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/hypervel/pool)
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/hypervel/connection-pool)

Documentation: https://hypervel.org/docs/pools#connection-pools

Ported from: https://github.com/hyperf/hyperf/tree/master/src/pool
4 changes: 2 additions & 2 deletions src/pool/composer.json → src/connection-pool/composer.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "hypervel/pool",
"name": "hypervel/connection-pool",
"type": "library",
"description": "Connection pooling for Hypervel packages.",
"license": "MIT",
Expand All @@ -26,7 +26,7 @@
},
"autoload": {
"psr-4": {
"Hypervel\\Pool\\": "src/"
"Hypervel\\ConnectionPool\\": "src/"
}
},
"require": {
Expand Down
124 changes: 124 additions & 0 deletions src/connection-pool/src/BorrowRateTracker.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<?php

declare(strict_types=1);

namespace Hypervel\ConnectionPool;

use Hypervel\Contracts\ConnectionPool\UsageTracker;

/**
* Track recent borrowing activity to shrink pools during periods of low usage.
*/
class BorrowRateTracker implements UsageTracker
{
/**
* @var array<int, int>
*/
protected array $borrows = [];

protected int $window = 10;

protected int $threshold = 5;

protected int $cooldown = 60;

protected ?int $startedAt = null;

protected ?int $lastTrimAt = null;

protected ?int $lastPrunedAt = null;

protected int $borrowCount = 0;

/**
* Record a successful connection borrow.
*/
public function recordBorrow(): void
{
$now = $this->currentTime();

if ($this->startedAt === null) {
$this->startedAt = $now;
$this->lastTrimAt = $now;
}

$this->prune($now);
$this->borrows[$now] = ($this->borrows[$now] ?? 0) + 1;
++$this->borrowCount;
}

/**
* Return the average number of borrows per sampled second.
*/
public function getBorrowRate(): float
{
if ($this->startedAt === null) {
return 0.0;
}

$this->prune($this->currentTime());
$sampleCount = count($this->borrows);

return $sampleCount === 0 ? 0.0 : $this->borrowCount / $sampleCount;
}

/**
* Determine whether low usage and the cooldown permit trimming.
*/
public function shouldTrimExcessIdle(): bool
{
if ($this->lastTrimAt === null) {
return false;
}

$now = $this->currentTime();

if ($this->lastTrimAt + $this->cooldown >= $now) {
return false;
}

$this->prune($now);
$sampleCount = count($this->borrows);

if (($sampleCount === 0 ? 0.0 : $this->borrowCount / $sampleCount) < $this->threshold) {
$this->lastTrimAt = $now;

return true;
}

return false;
}

/**
* Remove expired samples and fill completed seconds without borrows.
*/
protected function prune(int $now): void
{
if ($this->lastPrunedAt === $now) {
return;
}

$latest = $now - $this->window + 1;

foreach ($this->borrows as $second => $count) {
if ($second < $latest) {
$this->borrowCount -= $count;
unset($this->borrows[$second]);
}
}

for ($second = max($this->startedAt, $latest); $second < $now; ++$second) {
$this->borrows[$second] ??= 0;
}

$this->lastPrunedAt = $now;
}

/**
* Return the current sampling second.
*/
protected function currentTime(): int
{
return intdiv(hrtime(true), 1_000_000_000);
}
}
Loading