diff --git a/app/common/service/GameLiveService.php b/app/common/service/GameLiveService.php index ac6bf3f..8fe4072 100644 --- a/app/common/service/GameLiveService.php +++ b/app/common/service/GameLiveService.php @@ -697,7 +697,7 @@ final class GameLiveService 'update_time' => time(), ]); if (GameRecordService::isLiveRuntimeEnabled()) { - GameRecordService::createNextRecordRow(); + GameRecordService::createNextRecordRowIfNoActive(); } Db::commit(); } catch (Throwable $e) { diff --git a/app/common/service/GameRecordService.php b/app/common/service/GameRecordService.php index 6e3e167..4a82e64 100644 --- a/app/common/service/GameRecordService.php +++ b/app/common/service/GameRecordService.php @@ -14,6 +14,7 @@ final class GameRecordService public const KEY_MANUAL_CREATE = 'period_manual_create_enabled'; private const ACTIVE_STATUSES = [0, 1, 2, 3]; + private const AUTO_CREATE_LOCK_KEY = 'auto-create-next-record'; public static function getConfigBool(string $key): bool { @@ -53,11 +54,8 @@ final class GameRecordService if (!self::getConfigBool(self::KEY_AUTO_CREATE)) { return; } - if (self::hasActiveRecord()) { - return; - } try { - self::createNextRecordRow(); + self::createNextRecordRowIfNoActive(); } catch (Throwable) { } } @@ -84,10 +82,7 @@ final class GameRecordService if (!self::getConfigBool(self::KEY_AUTO_CREATE)) { return null; } - if (self::hasActiveRecord()) { - return null; - } - return self::createNextRecordRow(); + return self::createNextRecordRowIfNoActive(); } /** @@ -111,11 +106,8 @@ final class GameRecordService if (!self::getConfigBool(self::KEY_AUTO_CREATE)) { return; } - if (self::hasActiveRecord()) { - return; - } try { - self::createNextRecordRow(); + self::createNextRecordRowIfNoActive(); } catch (Throwable) { } } @@ -132,6 +124,27 @@ final class GameRecordService */ public static function createNextRecordRow(): string { + $created = self::createNextRecordRowIfNoActive(); + if ($created === null) { + throw new \RuntimeException((string) __('There is an unfinished round; cannot create a new one')); + } + + return $created; + } + + /** + * 幂等插入下一期:有进行中局则不插入,返回 null。 + */ + public static function createNextRecordRowIfNoActive(): ?string + { + $lock = GameHotDataLock::tryAcquireWithWait(GameHotDataLock::TYPE_GAME_RECORD, self::AUTO_CREATE_LOCK_KEY, 1500); + if (!$lock['acquired']) { + return null; + } + try { + if (self::hasActiveRecord()) { + return null; + } $periodNo = self::generatePeriodNo(); $now = time(); Db::name('game_record')->insert([ @@ -145,6 +158,9 @@ final class GameRecordService ]); GameHotDataCoordinator::afterGameRecordCommitted(null); return $periodNo; + } finally { + GameHotDataLock::release(GameHotDataLock::TYPE_GAME_RECORD, self::AUTO_CREATE_LOCK_KEY, $lock['token'], $lock['redis_lock']); + } } private static function generatePeriodNo(): string