diff --git a/src/Auth.php b/src/Auth.php index c45ca3a..c031e59 100644 --- a/src/Auth.php +++ b/src/Auth.php @@ -9,7 +9,22 @@ final class Auth { /** Cache por request del row de users del usuario autenticado */ private static ?array $userRowCache = null; - public static function startSession(): void { + /** Marca si $_SESSION ya fue cargado en este request (incluye modo read_and_close). */ + private static bool $sessionLoaded = false; + + /** + * Abre la sesión. + * + * Por defecto usa read_and_close: carga $_SESSION en memoria y libera de + * inmediato el lock del archivo de sesión. Sin esto, session_start() retiene + * un flock() exclusivo durante todo el request, y los requests paralelos del + * mismo usuario (p.ej. el polling de /escrituras/) se serializan y agotan el + * pool de php-fpm hasta tumbar la app. + * + * Pasar $forWrite=true SOLO en los flujos que modifican la sesión (login + * OAuth, logout): toma el lock de escritura normal, liberado al fin del request. + */ + public static function startSession(bool $forWrite = false): void { if (session_status() === PHP_SESSION_ACTIVE) return; $secure = ($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https' @@ -23,7 +38,13 @@ final class Auth { 'httponly' => true, 'samesite' => 'Lax', ]); - session_start(); + + if ($forWrite) { + session_start(); // lock exclusivo hasta fin del request + } elseif (!self::$sessionLoaded) { + session_start(['read_and_close' => true]); // lee $_SESSION y libera el lock ya + } + self::$sessionLoaded = true; } public static function isAuthenticated(): bool { @@ -102,7 +123,7 @@ final class Auth { } public static function buildLoginUrl(?string $returnTo = null): string { - self::startSession(); + self::startSession(true); $state = bin2hex(random_bytes(16)); $_SESSION['oauth_state'] = $state; $_SESSION['oauth_return_to'] = $returnTo ?: ($_SERVER['REQUEST_URI'] ?? '/'); @@ -176,7 +197,7 @@ final class Auth { // No bloqueamos el login por un fallo de DB no crítico } - self::startSession(); + self::startSession(true); session_regenerate_id(true); $_SESSION['authenticated'] = true; $_SESSION['user_email'] = $email; @@ -189,7 +210,7 @@ final class Auth { } public static function logout(): void { - self::startSession(); + self::startSession(true); $email = $_SESSION['user_email'] ?? ''; $name = $_SESSION['user_name'] ?? ''; if ($email !== '') { @@ -204,14 +225,14 @@ final class Auth { } public static function popReturnTo(): string { - self::startSession(); + self::startSession(true); $to = $_SESSION['oauth_return_to'] ?? '/'; unset($_SESSION['oauth_return_to']); return $to; } public static function checkState(string $state): bool { - self::startSession(); + self::startSession(true); $expected = $_SESSION['oauth_state'] ?? ''; unset($_SESSION['oauth_state']); return $state !== '' && hash_equals((string)$expected, $state);