Limit status checks to two per host
This commit is contained in:
@@ -75,7 +75,7 @@ describe('database fleet and container readiness', () => {
|
||||
expect(getServer('managed')).toMatchObject({ last_error: 'timeout:manual failure', last_seen_at: null, last_checked_at: new Date(at + 10_000).toISOString() });
|
||||
});
|
||||
|
||||
it('refreshes a ten-server fleet while one check is slow, without duplicating or exceeding four checks', async () => {
|
||||
it('refreshes a ten-server host while one check is slow, without duplicating or exceeding two checks', async () => {
|
||||
const { db, inspect, rcon, fanoutStatus, getServer } = await setup();
|
||||
const { subscribe } = await import('../../src/lib/server/pipeline/bus.js');
|
||||
vi.useFakeTimers({ toFake: ['Date'] });
|
||||
@@ -109,7 +109,7 @@ describe('database fleet and container readiness', () => {
|
||||
await vi.waitFor(() => expect(Date.parse(getServer('fast-8')?.last_seen_at ?? '')).toBeGreaterThanOrEqual(now + 30_000));
|
||||
expect(rcon).toHaveBeenCalledTimes(18);
|
||||
expect(inspect.mock.calls.filter(([name]) => name === 'managed')).toHaveLength(1);
|
||||
expect(peak).toBe(4);
|
||||
expect(peak).toBe(2);
|
||||
release({ ok: true, state: 'running' });
|
||||
await Promise.all([first, second]);
|
||||
expect(rcon).toHaveBeenCalledTimes(19);
|
||||
@@ -120,6 +120,64 @@ describe('database fleet and container readiness', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it.each(['native', 'komodo'])('gives each %s host two slots through inspection and RCON, independently of a stalled host', async backend => {
|
||||
const { db, inspect, rcon, fanoutStatus, getServer } = await setup();
|
||||
db.prepare("DELETE FROM servers WHERE name='managed'").run();
|
||||
const hosts = await import('../../src/lib/server/hosts/index.js');
|
||||
for (let host = 0; host < 3; host++) {
|
||||
const registered = hosts.createHost({ name: `Host ${host}` });
|
||||
hosts.enrollHost(hosts.createEnrollment(registered.id).token, { protocolVersion: 1 });
|
||||
for (let instance = 0; instance < 4; instance++) {
|
||||
const name = `host-${host}-${instance}`;
|
||||
db.prepare('INSERT INTO servers (name, komodo_name, komodo_server) VALUES (?, ?, ?)')
|
||||
.run(name, name, backend === 'komodo' ? registered.id : null);
|
||||
if (backend === 'native') db.prepare(`INSERT INTO native_instances (id,server,host_id,library_id,image,port,created_at)
|
||||
VALUES (?, ?, ?, 'library', 'image', ?, ?)`).run(name, name, registered.id, 28960 + instance, new Date().toISOString());
|
||||
}
|
||||
}
|
||||
let releaseSlow!: () => void;
|
||||
let releaseFast!: () => void;
|
||||
const slow = new Promise<void>(resolve => { releaseSlow = resolve; });
|
||||
const fast = new Promise<void>(resolve => { releaseFast = resolve; });
|
||||
const active = new Map<string, number>();
|
||||
const peak = new Map<string, number>();
|
||||
inspect.mockResolvedValue({ ok: true, state: 'running' });
|
||||
rcon.mockImplementation(async name => {
|
||||
const host = name.slice(0, -2);
|
||||
active.set(host, (active.get(host) ?? 0) + 1);
|
||||
peak.set(host, Math.max(peak.get(host) ?? 0, active.get(host)!));
|
||||
try {
|
||||
await (host === 'host-0' ? slow : fast);
|
||||
if (name === 'host-0-0') throw new Error('simulated failed probe');
|
||||
return { ok: true, endpoint: { host: 'localhost', port: 28960 }, rawReply: 'status',
|
||||
status: { players: 2, map: 'zombie_theater', gametype: 'zom', roster: [] } };
|
||||
} finally { active.set(host, active.get(host)! - 1); }
|
||||
});
|
||||
const passes = [fanoutStatus()];
|
||||
try {
|
||||
await vi.waitFor(() => expect(rcon).toHaveBeenCalledTimes(6));
|
||||
// A slot covers the whole check: waiting for RCON cannot admit a third inspection.
|
||||
expect(inspect).toHaveBeenCalledTimes(6);
|
||||
expect([...active.values()]).toEqual([2, 2, 2]);
|
||||
passes.push(fanoutStatus());
|
||||
releaseFast();
|
||||
await vi.waitFor(() => {
|
||||
expect(getServer('host-1-3')?.last_players).toBe(2);
|
||||
expect(getServer('host-2-3')?.last_players).toBe(2);
|
||||
});
|
||||
expect(getServer('host-0-2')?.container_checked_at).toBeNull();
|
||||
expect(rcon).toHaveBeenCalledTimes(10);
|
||||
} finally {
|
||||
releaseSlow(); releaseFast();
|
||||
await Promise.all(passes);
|
||||
}
|
||||
expect([...peak.values()]).toEqual([2, 2, 2]);
|
||||
expect(inspect).toHaveBeenCalledTimes(12);
|
||||
expect(rcon).toHaveBeenCalledTimes(12);
|
||||
expect(getServer('host-0-0')?.last_error).toBe('send-error:simulated failed probe');
|
||||
expect(getServer('host-0-3')?.last_players).toBe(2);
|
||||
});
|
||||
|
||||
it('expires early results before recording a fleet collection delayed by a slow check', async () => {
|
||||
const { db, inspect, rcon, fanoutStatus } = await setup();
|
||||
vi.useFakeTimers({ toFake: ['Date'] });
|
||||
|
||||
Reference in New Issue
Block a user