mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-29 14:45:24 +00:00
fix: high cpu from status probes and full database saves on every sample (#1300)
This commit is contained in:
1 parent
11a22d30eb
commit
79bd10a1b0
10 files changed
+241
-22
No files matched your search
@@ -0,0 +1,104 @@
|
||||
import { EventEmitter } from "node:events";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { JumpProbePool } from "../../../hosts/status/jump-probe-pool.js";
|
||||
|
||||
function fakeClient(reachable = true) {
|
||||
const client = Object.assign(new EventEmitter(), {
|
||||
end: vi.fn(),
|
||||
forwardOut: vi.fn(
|
||||
(
|
||||
_srcIp: string,
|
||||
_srcPort: number,
|
||||
_host: string,
|
||||
_port: number,
|
||||
callback: (error: Error | null, stream?: { destroy(): void }) => void,
|
||||
) => {
|
||||
callback(
|
||||
reachable ? null : new Error("refused"),
|
||||
reachable ? { destroy: () => {} } : undefined,
|
||||
);
|
||||
},
|
||||
),
|
||||
});
|
||||
return client;
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
describe("JumpProbePool", () => {
|
||||
it("shares one chain across hosts behind the same hops", async () => {
|
||||
const client = fakeClient();
|
||||
const open = vi.fn(async () => client as never);
|
||||
const pool = new JumpProbePool(open);
|
||||
const hops = [{ hostId: 1 }, { hostId: 2 }];
|
||||
|
||||
const results = await Promise.all([
|
||||
pool.ping(hops, "u1", "10.0.0.3", 22),
|
||||
pool.ping(hops, "u1", "10.0.0.4", 22),
|
||||
pool.ping(hops, "u1", "10.0.0.5", 22),
|
||||
]);
|
||||
|
||||
expect(results).toEqual([true, true, true]);
|
||||
expect(open).toHaveBeenCalledTimes(1);
|
||||
expect(client.forwardOut).toHaveBeenCalledTimes(3);
|
||||
expect(client.end).not.toHaveBeenCalled();
|
||||
pool.closeAll();
|
||||
});
|
||||
|
||||
it("keeps chains apart per user and hop list", async () => {
|
||||
const open = vi.fn(async () => fakeClient() as never);
|
||||
const pool = new JumpProbePool(open);
|
||||
await pool.ping([{ hostId: 1 }], "u1", "a", 22);
|
||||
await pool.ping([{ hostId: 1 }], "u2", "a", 22);
|
||||
await pool.ping([{ hostId: 2 }], "u1", "a", 22);
|
||||
expect(open).toHaveBeenCalledTimes(3);
|
||||
pool.closeAll();
|
||||
});
|
||||
|
||||
it("reports an unreachable target without dropping the chain", async () => {
|
||||
const client = fakeClient(false);
|
||||
const open = vi.fn(async () => client as never);
|
||||
const pool = new JumpProbePool(open);
|
||||
expect(await pool.ping([{ hostId: 1 }], "u1", "a", 22)).toBe(false);
|
||||
expect(await pool.ping([{ hostId: 1 }], "u1", "b", 22)).toBe(false);
|
||||
expect(open).toHaveBeenCalledTimes(1);
|
||||
pool.closeAll();
|
||||
});
|
||||
|
||||
it("retries a chain that failed to open", async () => {
|
||||
const open = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(null)
|
||||
.mockResolvedValueOnce(fakeClient());
|
||||
const pool = new JumpProbePool(open);
|
||||
expect(await pool.ping([{ hostId: 1 }], "u1", "a", 22)).toBe(false);
|
||||
expect(await pool.ping([{ hostId: 1 }], "u1", "a", 22)).toBe(true);
|
||||
pool.closeAll();
|
||||
});
|
||||
|
||||
it("opens a new chain after the old one closed or sat idle", async () => {
|
||||
vi.useFakeTimers();
|
||||
const first = fakeClient();
|
||||
const second = fakeClient();
|
||||
const third = fakeClient();
|
||||
const open = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(first)
|
||||
.mockResolvedValueOnce(second)
|
||||
.mockResolvedValueOnce(third);
|
||||
const pool = new JumpProbePool(open, 1000);
|
||||
|
||||
await pool.ping([{ hostId: 1 }], "u1", "a", 22);
|
||||
first.emit("close");
|
||||
await pool.ping([{ hostId: 1 }], "u1", "a", 22);
|
||||
expect(open).toHaveBeenCalledTimes(2);
|
||||
|
||||
await vi.advanceTimersByTimeAsync(1500);
|
||||
expect(second.end).toHaveBeenCalled();
|
||||
await pool.ping([{ hostId: 1 }], "u1", "a", 22);
|
||||
expect(open).toHaveBeenCalledTimes(3);
|
||||
pool.closeAll();
|
||||
});
|
||||
});
|
||||
@@ -71,6 +71,17 @@ describe("DatabaseSaveTrigger", () => {
|
||||
expect(DatabaseSaveTrigger.getStatus().pendingSave).toBe(false);
|
||||
});
|
||||
|
||||
it("saves within the max wait even while writes keep coming", async () => {
|
||||
vi.useFakeTimers();
|
||||
const save = vi.fn().mockResolvedValue(undefined);
|
||||
DatabaseSaveTrigger.initialize(save);
|
||||
for (let second = 0; second < 40; second++) {
|
||||
void DatabaseSaveTrigger.triggerSave("sample");
|
||||
await vi.advanceTimersByTimeAsync(1000);
|
||||
}
|
||||
expect(save).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("queues a force save behind an in-flight save", async () => {
|
||||
let finishFirstSave: (() => void) | undefined;
|
||||
const firstSave = new Promise<void>((resolve) => {
|
||||
|
||||
Reference in new issue
Block a user