fix: support closed-server picture migration

This commit is contained in:
2026-08-07 16:44:02 +00:00
parent dc56ee1c06
commit d9ad14c027
2 changed files with 56 additions and 7 deletions
+48 -5
View File
@@ -10,7 +10,7 @@ if (PHP_SAPI !== 'cli') {
exit(1); exit(1);
} }
$options = getopt('', ['help', 'server:', 'status', 'apply', 'backup:']); $options = getopt('', ['help', 'server:', 'status', 'apply', 'backup:', 'server-closed']);
if (isset($options['help'])) { if (isset($options['help'])) {
pictureMigrationUsage(); pictureMigrationUsage();
} }
@@ -47,6 +47,7 @@ function pictureMigrationUsage(int $exitCode = 0): void
Usage: Usage:
php scripts/migrate-general-picture.php --server=PREFIX --status php scripts/migrate-general-picture.php --server=PREFIX --status
php scripts/migrate-general-picture.php --server=PREFIX --apply --backup=/absolute/path/to/pre-migration.sql php scripts/migrate-general-picture.php --server=PREFIX --apply --backup=/absolute/path/to/pre-migration.sql
php scripts/migrate-general-picture.php --server=PREFIX --apply --server-closed --backup=/absolute/path/to/pre-migration.sql
PREFIX is one configured game directory such as che, kwe, or hwe. Run status, PREFIX is one configured game directory such as che, kwe, or hwe. Run status,
backup, apply, and verification separately for every game database; this script backup, apply, and verification separately for every game database; this script
@@ -57,7 +58,10 @@ chief picture columns to VARCHAR(64), adds nullable l12imgsvr through l5imgsvr,
and backfills only uniquely matched historical values from ng_old_generals. and backfills only uniquely matched historical values from ng_old_generals.
It requires a pre-existing, non-empty SQL backup whenever a schema or data It requires a pre-existing, non-empty SQL backup whenever a schema or data
change is needed. Stop web and daemon traffic before applying; MariaDB/Aria DDL change is needed. Stop web and daemon traffic before applying; MariaDB/Aria DDL
is not transactional. Unmatched or ambiguous historical values remain NULL. is not transactional. Normally the script acquires and releases the GAME lock.
Use --server-closed only after independently stopping web and daemon traffic;
that flag skips the GAME lock without changing its existing state. Unmatched or
ambiguous historical values remain NULL.
TEXT); TEXT);
exit($exitCode); exit($exitCode);
@@ -245,6 +249,22 @@ function requirePictureMigrationBackup(mixed $backup): string
return $backup; return $backup;
} }
function acquirePictureMigrationLock(\MeekroDB $db, string $server): bool
{
return (int)$db->queryFirstField(
'SELECT GET_LOCK(%s, 0)',
"sammo-picture-migration-$server",
) === 1;
}
function releasePictureMigrationLock(\MeekroDB $db, string $server): void
{
$db->queryFirstField(
'SELECT RELEASE_LOCK(%s)',
"sammo-picture-migration-$server",
);
}
$db = DB::db(); $db = DB::db();
if (isset($options['status'])) { if (isset($options['status'])) {
exit(printPictureMigrationStatus($db, $server) === 'unsupported' ? 2 : 0); exit(printPictureMigrationStatus($db, $server) === 'unsupported' ? 2 : 0);
@@ -264,12 +284,32 @@ if ($state === 'ready' && $recoverableBefore === 0) {
} }
requirePictureMigrationBackup($options['backup'] ?? null); requirePictureMigrationBackup($options['backup'] ?? null);
if (!\sammo\tryLock()) { $serverClosed = isset($options['server-closed']);
fwrite(STDERR, "Unable to acquire the GAME lock.\n"); if ($serverClosed) {
fwrite(
STDERR,
"WARNING: --server-closed skips the GAME lock. Continue only if web and daemon traffic for $server is already stopped.\n",
);
}
if (!acquirePictureMigrationLock($db, $server)) {
fwrite(STDERR, "Another picture migration is already running for $server.\n");
exit(3); exit(3);
} }
$backfilled = 0; $backfilled = 0;
$acquiredGameLock = false;
if (!$serverClosed && !\sammo\tryLock()) {
releasePictureMigrationLock($db, $server);
fwrite(
STDERR,
"Unable to acquire the GAME lock. If the server is intentionally closed and all web/daemon traffic is stopped, rerun with --server-closed.\n",
);
exit(3);
}
if (!$serverClosed) {
$acquiredGameLock = true;
}
try { try {
if (pictureColumnCapacity($db, 'general', 'picture') === 40) { if (pictureColumnCapacity($db, 'general', 'picture') === 40) {
$db->query('ALTER TABLE general MODIFY picture VARCHAR(64) NOT NULL'); $db->query('ALTER TABLE general MODIFY picture VARCHAR(64) NOT NULL');
@@ -292,7 +332,10 @@ try {
$backfilled = backfillEmperiorImgsvr($db); $backfilled = backfillEmperiorImgsvr($db);
} finally { } finally {
\sammo\unlock(); if ($acquiredGameLock) {
\sammo\unlock();
}
releasePictureMigrationLock($db, $server);
} }
if (printPictureMigrationStatus($db, $server) !== 'ready') { if (printPictureMigrationStatus($db, $server) !== 'ready') {
+8 -2
View File
@@ -70,13 +70,18 @@ final class GeneralPictureSchemaTest extends TestCase
self::assertStringContainsString("ALTER TABLE emperior", $migration); self::assertStringContainsString("ALTER TABLE emperior", $migration);
self::assertStringContainsString("ADD COLUMN `\$imgsvrField` INT(1) NULL DEFAULT NULL", $migration); self::assertStringContainsString("ADD COLUMN `\$imgsvrField` INT(1) NULL DEFAULT NULL", $migration);
self::assertStringContainsString('HAVING COUNT(*) = 1', $migration); self::assertStringContainsString('HAVING COUNT(*) = 1', $migration);
self::assertStringContainsString('Unmatched or ambiguous historical values remain NULL', $migration); self::assertStringContainsString('ambiguous historical values remain NULL', $migration);
self::assertStringContainsString("? 'picture_capacity'", $migration); self::assertStringContainsString("? 'picture_capacity'", $migration);
self::assertStringContainsString("['help', 'server:', 'status', 'apply', 'backup:']", $migration); self::assertStringContainsString("['help', 'server:', 'status', 'apply', 'backup:', 'server-closed']", $migration);
self::assertStringContainsString("require \$serverDirectory . '/lib.php'", $migration); self::assertStringContainsString("require \$serverDirectory . '/lib.php'", $migration);
self::assertStringContainsString("require \$serverDirectory . '/func.php'", $migration); self::assertStringContainsString("require \$serverDirectory . '/func.php'", $migration);
self::assertStringNotContainsString("'/hwe/lib.php'", $migration); self::assertStringNotContainsString("'/hwe/lib.php'", $migration);
self::assertStringNotContainsString("'/hwe/func.php'", $migration); self::assertStringNotContainsString("'/hwe/func.php'", $migration);
self::assertStringContainsString('SELECT GET_LOCK(%s, 0)', $migration);
self::assertStringContainsString('SELECT RELEASE_LOCK(%s)', $migration);
self::assertStringContainsString('if (!$serverClosed && !\\sammo\\tryLock())', $migration);
self::assertStringContainsString('if ($acquiredGameLock)', $migration);
self::assertStringContainsString('--server-closed skips the GAME lock', $migration);
self::assertStringNotContainsString('UPDATE general', $migration); self::assertStringNotContainsString('UPDATE general', $migration);
self::assertStringContainsString("\$state === 'ready'", $migration); self::assertStringContainsString("\$state === 'ready'", $migration);
} }
@@ -104,6 +109,7 @@ final class GeneralPictureSchemaTest extends TestCase
[$helpExit, $helpOutput, $helpError] = $this->runMigrationCommand(['--help']); [$helpExit, $helpOutput, $helpError] = $this->runMigrationCommand(['--help']);
self::assertSame(0, $helpExit); self::assertSame(0, $helpExit);
self::assertStringContainsString('--server=PREFIX', $helpOutput); self::assertStringContainsString('--server=PREFIX', $helpOutput);
self::assertStringContainsString('--server-closed', $helpOutput);
self::assertSame('', $helpError); self::assertSame('', $helpError);
[$missingExit, $missingOutput, $missingError] = $this->runMigrationCommand(['--status']); [$missingExit, $missingOutput, $missingError] = $this->runMigrationCommand(['--status']);