mirror of
https://github.com/misenhower/splatoon3.ink.git
synced 2026-08-20 07:34:28 -05:00
Restart daily to refresh nxapi config
This commit is contained in:
25
app/cron.mjs
25
app/cron.mjs
@@ -1,4 +1,5 @@
|
||||
import { CronJob } from 'cron';
|
||||
import { setTimeout as sleep } from 'node:timers/promises';
|
||||
import { update } from './data/index.mjs';
|
||||
import { warmCaches } from './splatnet/index.mjs';
|
||||
import { sendStatuses } from './social/index.mjs';
|
||||
@@ -6,6 +7,7 @@ import { archiveData } from './data/DataArchiver.mjs';
|
||||
import { updateAvatars } from './social/updateAvatars.mjs';
|
||||
|
||||
let updating = false;
|
||||
const restartGracePeriod = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
async function updateIfNotUpdating(mode) {
|
||||
if (updating) {
|
||||
@@ -25,6 +27,28 @@ async function updateIfNotUpdating(mode) {
|
||||
}
|
||||
}
|
||||
|
||||
// nxapi caches remote config for the life of the process, so restart daily to refresh it.
|
||||
async function restartToRefreshConfig() {
|
||||
const restartDeadline = Date.now() + restartGracePeriod;
|
||||
|
||||
if (updating) {
|
||||
console.log('[Cron] Restart pending; waiting for in-progress update to finish...');
|
||||
|
||||
while (updating && Date.now() < restartDeadline) {
|
||||
await sleep(5000);
|
||||
}
|
||||
}
|
||||
|
||||
if (updating) {
|
||||
console.warn('[Cron] Update did not finish within 5 minutes; restarting anyway');
|
||||
}
|
||||
|
||||
updating = true;
|
||||
|
||||
console.log('[Cron] Exiting to refresh nxapi remote config; container will restart');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
export default function() {
|
||||
new CronJob('5,20,35,50 * * * *', warmCaches, null, true);
|
||||
new CronJob('15 0,1,2,3,4 * * * *', () => {
|
||||
@@ -38,4 +62,5 @@ export default function() {
|
||||
}, null, true);
|
||||
|
||||
new CronJob('30 * * * *', updateAvatars, null, true);
|
||||
new CronJob('0 55 4 * * *', restartToRefreshConfig, null, true);
|
||||
}
|
||||
|
||||
78
app/cron.test.mjs
Normal file
78
app/cron.test.mjs
Normal file
@@ -0,0 +1,78 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
archiveData: vi.fn(),
|
||||
jobs: new Map(),
|
||||
sendStatuses: vi.fn(),
|
||||
update: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('cron', () => ({
|
||||
CronJob: class {
|
||||
constructor(cronTime, onTick) {
|
||||
mocks.jobs.set(cronTime, onTick);
|
||||
}
|
||||
},
|
||||
}));
|
||||
vi.mock('node:timers/promises', () => ({
|
||||
setTimeout: ms => new Promise(resolve => setTimeout(resolve, ms)),
|
||||
}));
|
||||
vi.mock('./data/index.mjs', () => ({ update: mocks.update }));
|
||||
vi.mock('./splatnet/index.mjs', () => ({ warmCaches: vi.fn() }));
|
||||
vi.mock('./social/index.mjs', () => ({ sendStatuses: mocks.sendStatuses }));
|
||||
vi.mock('./data/DataArchiver.mjs', () => ({ archiveData: mocks.archiveData }));
|
||||
vi.mock('./social/updateAvatars.mjs', () => ({ updateAvatars: vi.fn() }));
|
||||
|
||||
async function loadJobs() {
|
||||
const { default: startCron } = await import('./cron.mjs');
|
||||
startCron();
|
||||
}
|
||||
|
||||
describe('cron update coordination', () => {
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
mocks.jobs.clear();
|
||||
mocks.update.mockReset();
|
||||
mocks.sendStatuses.mockReset();
|
||||
mocks.archiveData.mockReset();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('allows another update after an update rejects', async () => {
|
||||
mocks.update.mockRejectedValueOnce(new Error('update failed'));
|
||||
await loadJobs();
|
||||
|
||||
const update = mocks.jobs.get('15 0,1,2,3,4 * * * *');
|
||||
await expect(update()).rejects.toThrow('update failed');
|
||||
await update();
|
||||
|
||||
expect(mocks.update).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('restarts after a bounded wait when an update never settles', async () => {
|
||||
vi.useFakeTimers();
|
||||
mocks.update.mockReturnValue(new Promise(() => {}));
|
||||
vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
const exit = vi.spyOn(process, 'exit').mockImplementation(() => {});
|
||||
await loadJobs();
|
||||
|
||||
const update = mocks.jobs.get('15 0,1,2,3,4 * * * *');
|
||||
const restart = mocks.jobs.get('0 55 4 * * *');
|
||||
update();
|
||||
const restarting = restart();
|
||||
await vi.advanceTimersByTimeAsync(5 * 60 * 1000 - 1);
|
||||
|
||||
expect(exit).not.toHaveBeenCalled();
|
||||
|
||||
await vi.advanceTimersByTimeAsync(1);
|
||||
|
||||
expect(warn).toHaveBeenCalledWith('[Cron] Update did not finish within 5 minutes; restarting anyway');
|
||||
expect(exit).toHaveBeenCalledWith(0);
|
||||
await restarting;
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user