lthn.io/app/Mod/Gateway/Services/GatewayRegistry.php
Claude 0220be23a4
fix: medium code review findings
- NodeSelector: array_values inside cache closure (fix key gaps)
- GatewayRegistry: prune expired entries from live_list on access
- Removed orphaned lethean::names view (replaced by names::index)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 08:10:58 +01:00

117 lines
3.4 KiB
PHP

<?php
declare(strict_types=1);
namespace Mod\Gateway\Services;
use Illuminate\Support\Facades\Cache;
/**
* Tracks paired gateways — their capacity, status, and last heartbeat.
*
* $registry = app(GatewayRegistry::class);
* $registry->register('charon', [...]);
* $gateway = $registry->get('charon');
* $live = $registry->liveGateways('proxy');
*/
class GatewayRegistry
{
private const TTL = 300; // 5 min — gateway must heartbeat within this
public function register(string $name, array $data): void
{
$entry = [
'name' => $name,
'capabilities' => $data['capabilities'] ?? [],
'region' => $data['region'] ?? 'unknown',
'bandwidth_mbps' => $data['bandwidth_mbps'] ?? 0,
'max_connections' => $data['max_connections'] ?? 0,
'current_connections' => 0,
'current_load' => 0,
'wireguard_endpoint' => $data['wireguard_endpoint'] ?? '',
'proxy_endpoint' => $data['proxy_endpoint'] ?? '',
'paired_at' => now()->toIso8601String(),
'last_heartbeat' => now()->toIso8601String(),
'status' => 'online',
];
Cache::put("gw:node:{$name}", $entry, self::TTL);
// Track in the live gateway list
$list = Cache::get('gw:live_list', []);
$list[$name] = true;
Cache::put('gw:live_list', $list, 86400);
}
public function heartbeat(string $name, array $stats = []): bool
{
$entry = Cache::get("gw:node:{$name}");
if (! $entry) {
return false;
}
$entry['last_heartbeat'] = now()->toIso8601String();
$entry['current_connections'] = $stats['connections'] ?? $entry['current_connections'];
$entry['current_load'] = $stats['load'] ?? $entry['current_load'];
$entry['bytes_served'] = ($entry['bytes_served'] ?? 0) + ($stats['bytes_since_last'] ?? 0);
$entry['status'] = 'online';
Cache::put("gw:node:{$name}", $entry, self::TTL);
return true;
}
public function get(string $name): ?array
{
return Cache::get("gw:node:{$name}");
}
/**
* Get all live (heartbeat within TTL) gateways, optionally filtered by capability.
*/
public function liveGateways(string $capability = ''): array
{
$list = Cache::get('gw:live_list', []);
$result = [];
$pruned = false;
foreach (array_keys($list) as $name) {
$entry = Cache::get("gw:node:{$name}");
if (! $entry) {
unset($list[$name]);
$pruned = true;
continue; // expired — gateway missed heartbeat
}
if ($capability && ! in_array($capability, $entry['capabilities'] ?? [])) {
continue;
}
$result[] = $entry;
}
// Prune expired entries from the live list
if ($pruned) {
Cache::put('gw:live_list', $list, 86400);
}
return $result;
}
/**
* Select best gateway for a connection — least loaded first.
*/
public function selectBest(string $capability): ?array
{
$gateways = $this->liveGateways($capability);
if (empty($gateways)) {
return null;
}
// Sort by current load (ascending)
usort($gateways, fn ($a, $b) => ($a['current_load'] ?? 0) <=> ($b['current_load'] ?? 0));
return $gateways[0];
}
}