diff --git a/app/process/GameWebSocketServer.php b/app/process/GameWebSocketServer.php index 1890a7a..79c5d90 100644 --- a/app/process/GameWebSocketServer.php +++ b/app/process/GameWebSocketServer.php @@ -47,6 +47,8 @@ class GameWebSocketServer private static bool $eventBusConsumerStarted = false; private static bool $adminSnapshotTickerStarted = false; private static bool $heartbeatCheckerStarted = false; + /** @var bool */ + private static bool $liveStateTickerStarted = false; /** 客户端 60s 内无任何上行报文(含 ping)即被主动断开 */ private const HEARTBEAT_IDLE_SECONDS = 60; @@ -87,6 +89,7 @@ class GameWebSocketServer self::ensureEventBusConsumer(); self::ensureAdminLiveSnapshotTicker(); self::ensureHeartbeatChecker(); + self::ensureLiveStateTicker(); $queryString = self::extractQueryString($connection, $request); $query = GameWebSocketAuthHelper::parseQueryString($queryString); @@ -351,6 +354,33 @@ class GameWebSocketServer ]); } + /** + * 兜底:当 gameLiveTicker 未运行/卡死时,WS 进程也能驱动对局推进,避免对局长期卡在派彩中(status=3)。 + * + * - 每秒执行一次 recoverLiveRoundState()(内部:finalizePayoutGrace + tickAutoDraw) + * - 幂等且有锁保护,和 gameLiveTicker 并存也不会破坏一致性 + */ + private static function ensureLiveStateTicker(): void + { + if (self::$liveStateTickerStarted) { + return; + } + self::$liveStateTickerStarted = true; + + Timer::add(1, static function (): void { + try { + GameLiveService::recoverLiveRoundState(); + } catch (Throwable $e) { + Log::channel('ws')->error('live state ticker failed', [ + 'error' => $e->getMessage(), + ]); + } + }); + Log::channel('ws')->info('live state ticker started', [ + 'tick_interval_seconds' => 1, + ]); + } + /** * 后台联调:订阅赔率相关主题后立即推送演示帧(库内样例玩家,带 is_test / preview)。 *