fix restart recovery active groups
This commit is contained in:
@@ -34,6 +34,7 @@ import {
|
||||
buildRestartAnnouncement,
|
||||
buildInterruptedRestartAnnouncement,
|
||||
consumeRestartContext,
|
||||
getRecoverableInterruptedGroups,
|
||||
getInterruptedRecoveryCandidates,
|
||||
inferRecentRestartContext,
|
||||
type RestartContext,
|
||||
@@ -196,7 +197,9 @@ async function announceRestartRecovery(
|
||||
'Sent explicit restart recovery announcement',
|
||||
);
|
||||
|
||||
for (const interrupted of explicitContext.interruptedGroups ?? []) {
|
||||
for (const interrupted of getRecoverableInterruptedGroups(
|
||||
explicitContext,
|
||||
)) {
|
||||
if (interrupted.chatJid === explicitContext.chatJid) continue;
|
||||
if (hasRecentRestartAnnouncement(interrupted.chatJid, dedupeSince)) {
|
||||
continue;
|
||||
@@ -265,8 +268,8 @@ async function main(): Promise<void> {
|
||||
(
|
||||
status,
|
||||
): status is typeof status & {
|
||||
status: 'processing' | 'waiting';
|
||||
} => status.status !== 'inactive',
|
||||
status: 'processing';
|
||||
} => status.status === 'processing',
|
||||
)
|
||||
.map((status) => ({
|
||||
chatJid: status.jid,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
buildRestartAnnouncement,
|
||||
getRecoverableInterruptedGroups,
|
||||
getInterruptedRecoveryCandidates,
|
||||
type RestartContext,
|
||||
} from './restart-context.js';
|
||||
@@ -16,8 +18,8 @@ function makeGroup(folder: string): RegisteredGroup {
|
||||
};
|
||||
}
|
||||
|
||||
describe('getInterruptedRecoveryCandidates', () => {
|
||||
it('returns only registered interrupted groups and deduplicates by chatJid', () => {
|
||||
describe('restart recovery context', () => {
|
||||
it('returns only registered processing groups and deduplicates by chatJid', () => {
|
||||
const roomBindings: Record<string, RegisteredGroup> = {
|
||||
'dc:1': makeGroup('group-one'),
|
||||
'dc:2': makeGroup('group-two'),
|
||||
@@ -38,8 +40,8 @@ describe('getInterruptedRecoveryCandidates', () => {
|
||||
pendingTasks: 0,
|
||||
},
|
||||
{
|
||||
chatJid: 'dc:1',
|
||||
groupName: 'one-duplicate',
|
||||
chatJid: 'dc:2',
|
||||
groupName: 'two-waiting',
|
||||
status: 'waiting',
|
||||
elapsedMs: null,
|
||||
pendingMessages: false,
|
||||
@@ -47,7 +49,7 @@ describe('getInterruptedRecoveryCandidates', () => {
|
||||
},
|
||||
{
|
||||
chatJid: 'dc:2',
|
||||
groupName: 'two',
|
||||
groupName: 'two-idle',
|
||||
status: 'idle',
|
||||
elapsedMs: null,
|
||||
pendingMessages: false,
|
||||
@@ -61,6 +63,14 @@ describe('getInterruptedRecoveryCandidates', () => {
|
||||
pendingMessages: true,
|
||||
pendingTasks: 0,
|
||||
},
|
||||
{
|
||||
chatJid: 'dc:1',
|
||||
groupName: 'one-duplicate',
|
||||
status: 'processing',
|
||||
elapsedMs: 2000,
|
||||
pendingMessages: false,
|
||||
pendingTasks: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -72,17 +82,40 @@ describe('getInterruptedRecoveryCandidates', () => {
|
||||
pendingMessages: true,
|
||||
pendingTasks: 0,
|
||||
},
|
||||
{
|
||||
chatJid: 'dc:2',
|
||||
groupFolder: 'group-two',
|
||||
status: 'idle',
|
||||
pendingMessages: false,
|
||||
pendingTasks: 0,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns empty when there is no explicit restart context', () => {
|
||||
expect(getInterruptedRecoveryCandidates(null, {})).toEqual([]);
|
||||
});
|
||||
|
||||
it('uses only processing groups for restart announcements', () => {
|
||||
const context: RestartContext = {
|
||||
chatJid: 'dc:main',
|
||||
summary: 'restart',
|
||||
verify: [],
|
||||
writtenAt: '2026-05-21T00:00:00.000Z',
|
||||
interruptedGroups: [
|
||||
{
|
||||
chatJid: 'dc:processing',
|
||||
groupName: 'processing',
|
||||
status: 'processing',
|
||||
elapsedMs: 1000,
|
||||
pendingMessages: false,
|
||||
pendingTasks: 0,
|
||||
},
|
||||
{
|
||||
chatJid: 'dc:waiting',
|
||||
groupName: 'waiting',
|
||||
status: 'waiting',
|
||||
elapsedMs: null,
|
||||
pendingMessages: true,
|
||||
pendingTasks: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getRecoverableInterruptedGroups(context)).toHaveLength(1);
|
||||
expect(buildRestartAnnouncement(context)).toContain('중단 작업 감지: 1개');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -72,6 +72,20 @@ function readRestartContextFile(filePath: string): RestartContext | null {
|
||||
return data;
|
||||
}
|
||||
|
||||
function isRecoverableInterruptedGroup(
|
||||
group: RestartInterruptedGroup,
|
||||
): boolean {
|
||||
return group.status === 'processing';
|
||||
}
|
||||
|
||||
export function getRecoverableInterruptedGroups(
|
||||
context: Pick<RestartContext, 'interruptedGroups'> | null,
|
||||
): RestartInterruptedGroup[] {
|
||||
return (context?.interruptedGroups ?? []).filter(
|
||||
isRecoverableInterruptedGroup,
|
||||
);
|
||||
}
|
||||
|
||||
function getLatestDistBuildTime(): number | null {
|
||||
const distDir = path.join(process.cwd(), 'dist');
|
||||
if (!fs.existsSync(distDir)) return null;
|
||||
@@ -145,7 +159,10 @@ export function writeShutdownRestartContext(
|
||||
signal: string,
|
||||
serviceIds: string[] = [SERVICE_ID],
|
||||
): string[] {
|
||||
if (interruptedGroups.length === 0) return [];
|
||||
const recoverableInterruptedGroups = interruptedGroups.filter(
|
||||
isRecoverableInterruptedGroup,
|
||||
);
|
||||
if (recoverableInterruptedGroups.length === 0) return [];
|
||||
|
||||
fs.mkdirSync(DATA_DIR, { recursive: true });
|
||||
const mainChatJid =
|
||||
@@ -156,8 +173,8 @@ export function writeShutdownRestartContext(
|
||||
const filePath = getRestartContextPath(serviceId);
|
||||
const existing = readRestartContextFile(filePath);
|
||||
const mergedInterrupted = [
|
||||
...(existing?.interruptedGroups ?? []),
|
||||
...interruptedGroups,
|
||||
...getRecoverableInterruptedGroups(existing),
|
||||
...recoverableInterruptedGroups,
|
||||
].filter(
|
||||
(group, index, all) =>
|
||||
all.findIndex((candidate) => candidate.chatJid === group.chatJid) ===
|
||||
@@ -221,8 +238,9 @@ export function consumeRestartContext(): RestartContext | null {
|
||||
|
||||
export function buildRestartAnnouncement(context: RestartContext): string {
|
||||
const lines = ['재시작 완료.', `- 변경: ${context.summary}`];
|
||||
if (context.interruptedGroups && context.interruptedGroups.length > 0) {
|
||||
lines.push(`- 중단 작업 감지: ${context.interruptedGroups.length}개`);
|
||||
const recoverableInterruptedGroups = getRecoverableInterruptedGroups(context);
|
||||
if (recoverableInterruptedGroups.length > 0) {
|
||||
lines.push(`- 중단 작업 감지: ${recoverableInterruptedGroups.length}개`);
|
||||
}
|
||||
if (context.verify.length > 0) {
|
||||
lines.push(`- 검증: ${context.verify.join(', ')}`);
|
||||
@@ -240,7 +258,7 @@ export function getInterruptedRecoveryCandidates(
|
||||
const seen = new Set<string>();
|
||||
const candidates: RestartRecoveryCandidate[] = [];
|
||||
|
||||
for (const interrupted of context.interruptedGroups) {
|
||||
for (const interrupted of getRecoverableInterruptedGroups(context)) {
|
||||
if (seen.has(interrupted.chatJid)) continue;
|
||||
const group = roomBindings[interrupted.chatJid];
|
||||
if (!group) continue;
|
||||
|
||||
Reference in New Issue
Block a user