mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 12:10:46 +00:00
e1d31ef1ee
If this PR fixes an issue, link it below. If not, delete these two lines. Resolves #2868 ## Description: This PR addresses a critical memory leak in the Master server process (causing ~30GB RAM usage). The issue was caused by `setInterval` calling `fetchLobbies()` every 100ms. When `fetchLobbies` took longer than 100ms to complete (due to network latency or load), requests would pile up indefinitely, creating a massive queue of pending Promises and open sockets. I have refactored the polling logic into a generic `startPolling` utility (in `src/server/PollingLoop.ts`) that uses a recursive `setTimeout` pattern. This ensures that the next `fetchLobbies` call is only scheduled *after* the previous one has completed (successfully or failed), preventing any request pile-up. ## Please complete the following: - [x] I have added screenshots for all UI updates (N/A - backend only) - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file (N/A - no user facing text) - [x] I have added relevant tests to the test directory (`tests/PollingLoop.test.ts`) - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: codimo
78 lines
1.9 KiB
TypeScript
78 lines
1.9 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { startPolling } from "../../src/server/PollingLoop";
|
|
|
|
vi.mock("../../src/server/Logger", () => ({
|
|
logger: {
|
|
child: () => ({
|
|
error: vi.fn(),
|
|
}),
|
|
},
|
|
}));
|
|
|
|
describe("PollingLoop", () => {
|
|
beforeEach(() => {
|
|
vi.useFakeTimers();
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it("should not start the next task until the previous one completes", async () => {
|
|
let taskCallCount = 0;
|
|
let resolveTask: ((value?: void) => void) | undefined;
|
|
|
|
const task = vi.fn().mockImplementation(() => {
|
|
taskCallCount++;
|
|
return new Promise<void>((resolve) => {
|
|
resolveTask = resolve;
|
|
});
|
|
});
|
|
|
|
startPolling(task, 100);
|
|
|
|
// Initial call
|
|
expect(taskCallCount).toBe(1);
|
|
|
|
// Advance time past the interval - should NOT trigger next call yet
|
|
await vi.advanceTimersByTimeAsync(200);
|
|
expect(taskCallCount).toBe(1);
|
|
|
|
// Resolve the first task
|
|
if (resolveTask) resolveTask();
|
|
|
|
// Wait for microtasks (promise callbacks, finally block) to run
|
|
await new Promise(process.nextTick);
|
|
|
|
// NOW advance time to trigger the scheduled continuation
|
|
await vi.advanceTimersByTimeAsync(100);
|
|
|
|
expect(taskCallCount).toBe(2);
|
|
});
|
|
|
|
it("should continue polling even if a task fails", async () => {
|
|
let taskCallCount = 0;
|
|
const task = vi.fn().mockImplementation(async () => {
|
|
taskCallCount++;
|
|
if (taskCallCount === 1) {
|
|
throw new Error("Task failed");
|
|
}
|
|
});
|
|
|
|
startPolling(task, 100);
|
|
|
|
// First call
|
|
expect(taskCallCount).toBe(1);
|
|
|
|
// Wait for rejection and finally block
|
|
await new Promise(process.nextTick);
|
|
await new Promise(process.nextTick);
|
|
|
|
// Advance time
|
|
await vi.advanceTimersByTimeAsync(100);
|
|
|
|
// Second call
|
|
expect(taskCallCount).toBe(2);
|
|
});
|
|
});
|