Merge branch 'main' into audit/lint-test-baseline-20260726

# Conflicts:
#	app/game-api/src/battleSim/worker.ts
#	app/game-api/src/router/general/index.ts
This commit is contained in:
2026-07-26 05:57:45 +00:00
80 changed files with 9334 additions and 1428 deletions
+42 -24
View File
@@ -18,7 +18,11 @@ const parseBlPopValue = (result: RedisBlPopResult): string | null => {
return result.element ?? null;
};
export const runBattleSimWorker = async (): Promise<void> => {
export interface BattleSimWorkerOptions {
signal?: AbortSignal;
}
export const runBattleSimWorker = async (options: BattleSimWorkerOptions = {}): Promise<void> => {
const config = resolveGameApiConfigFromEnv();
const redis = createRedisConnector(resolveRedisConfigFromEnv());
await redis.connect();
@@ -30,35 +34,49 @@ export const runBattleSimWorker = async (): Promise<void> => {
resultTtlSeconds: config.battleSimResultTtlSeconds,
});
const handleExit = async () => {
await redis.disconnect();
let stopped = options.signal?.aborted ?? false;
const handleExit = () => {
stopped = true;
};
const handleAbort = () => {
stopped = true;
};
process.on('SIGINT', handleExit);
process.on('SIGTERM', handleExit);
options.signal?.addEventListener('abort', handleAbort, { once: true });
while (true) {
const item = await redis.client.blPop(keys.queueKey, 0);
const raw = parseBlPopValue(item);
if (!raw) {
continue;
}
try {
while (!stopped) {
// A finite block lets SIGTERM and test AbortSignal stop the worker without
// leaving a Redis operation or a detached lifecycle process behind.
const item = await redis.client.blPop(keys.queueKey, 1);
const raw = parseBlPopValue(item);
if (!raw) {
continue;
}
let job: BattleSimJob;
try {
job = JSON.parse(raw) as BattleSimJob;
} catch {
continue;
}
let job: BattleSimJob;
try {
job = JSON.parse(raw) as BattleSimJob;
} catch {
continue;
}
try {
const result = processBattleSimJob(job.payload);
await transport.pushResult(job.jobId, result);
} catch (error) {
const reason = error instanceof Error ? error.message : '전투 시뮬레이션 오류';
await transport.pushResult(job.jobId, {
result: false,
reason,
});
try {
const result = processBattleSimJob(job.payload);
await transport.pushResult(job.jobId, job.requesterUserId, result);
} catch (error) {
const reason = error instanceof Error ? error.message : '전투 시뮬레이션 오류';
await transport.pushResult(job.jobId, job.requesterUserId, {
result: false,
reason,
});
}
}
} finally {
process.off('SIGINT', handleExit);
process.off('SIGTERM', handleExit);
options.signal?.removeEventListener('abort', handleAbort);
await redis.disconnect();
}
};