diff --git a/packages/mosaic/src/lease-broker/lease-broker.acceptance.spec.ts b/packages/mosaic/src/lease-broker/lease-broker.acceptance.spec.ts index db9b6ec..d2fbc29 100644 --- a/packages/mosaic/src/lease-broker/lease-broker.acceptance.spec.ts +++ b/packages/mosaic/src/lease-broker/lease-broker.acceptance.spec.ts @@ -333,6 +333,77 @@ describe('authenticated external lease broker', () => { silent.destroy(); }); + test('queued silent peers cannot serialize the next valid registration', async () => { + const { socket } = await startBroker(); + const silentConnections = await Promise.all( + Array.from( + { length: 4 }, + () => + new Promise((resolve, reject) => { + const connection = createConnection(socket); + connection.once('connect', () => resolve(connection)); + connection.once('error', reject); + }), + ), + ); + + try { + await new Promise((resolve) => setTimeout(resolve, 100)); + const started = performance.now(); + const registered = await withTimeout( + request(socket, { action: 'register_anchor', runtime_generation: 1 }), + 'registration behind queued silent connections', + 6_000, + ); + const elapsed = performance.now() - started; + + expect(registered.ok).toBe(true); + expect(elapsed).toBeLessThan(1_500); + } finally { + for (const connection of silentConnections) connection.destroy(); + } + }); + + test('silent peers are reaped at the concurrency bound and their slots are reclaimed', async () => { + const { socket } = await startBroker(); + const concurrencyCap = 16; + const peers = Array.from({ length: concurrencyCap }, () => { + const connection = createConnection(socket); + return { + connection, + connected: new Promise((resolve, reject) => { + connection.once('connect', resolve); + connection.once('error', reject); + }), + closed: new Promise((resolve) => connection.once('close', () => resolve())), + }; + }); + + try { + await Promise.all(peers.map(({ connected }) => connected)); + await new Promise((resolve) => setTimeout(resolve, 100)); + const started = performance.now(); + const registration = withTimeout( + request(socket, { action: 'register_anchor', runtime_generation: 1 }), + 'registration while silent peers hold the concurrency bound', + 2_500, + ); + const reaping = withTimeout( + Promise.all(peers.map(({ closed }) => closed)), + 'silent peer deadline reaping', + 2_500, + ); + const [registered] = await Promise.all([registration, reaping]); + const elapsed = performance.now() - started; + + expect(registered.ok).toBe(true); + expect(elapsed).toBeGreaterThan(500); + expect(elapsed).toBeLessThan(2_500); + } finally { + for (const { connection } of peers) connection.destroy(); + } + }); + test('newline-only client without half-close gets no success and cannot block next request', async () => { const { socket } = await startBroker(); const incomplete = createConnection(socket);