Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | 25x 25x 25x 25x 25x 58x 2x 2x 56x 2x 2x 54x 54x 54x 54x 50x 50x 49x 49x 49x 49x 61x 8x 10x 10x | import { randomUUID } from "node:crypto";
import { config } from "../config";
export type SimulationReservation = Readonly<{
id: string;
subject: string;
}>;
export type AdmissionResult =
| { admitted: true; reservation: SimulationReservation }
| { admitted: false; reason: "identity" | "capacity" };
/**
* Process-local admission control. reserve() and release() mutate synchronously,
* so each decision is atomic within Node's event loop. Reservation IDs make a
* delayed release from an old run harmless.
*/
export class SimulationAdmissionController {
private readonly reservationsBySubject = new Map<string, SimulationReservation>();
private readonly reservationsById = new Map<string, SimulationReservation>();
private capacityRejectedTotal = 0;
private identityRejectedTotal = 0;
constructor(private readonly maxReservations = config.server.simulationAdmissionMax) {}
reserve(subject: string): AdmissionResult {
if (this.reservationsBySubject.has(subject)) {
this.identityRejectedTotal++;
return { admitted: false, reason: "identity" };
}
if (this.reservationsById.size >= this.maxReservations) {
this.capacityRejectedTotal++;
return { admitted: false, reason: "capacity" };
}
const reservation = Object.freeze({ id: randomUUID(), subject });
this.reservationsBySubject.set(subject, reservation);
this.reservationsById.set(reservation.id, reservation);
return { admitted: true, reservation };
}
release(reservation: SimulationReservation): boolean {
const current = this.reservationsById.get(reservation.id);
if (current !== reservation) return false;
Iif (this.reservationsBySubject.get(reservation.subject) !== reservation) {
return false;
}
this.reservationsById.delete(reservation.id);
this.reservationsBySubject.delete(reservation.subject);
return true;
}
getStats() {
return {
active: this.reservationsById.size,
max: this.maxReservations,
capacityRejectedTotal: this.capacityRejectedTotal,
identityRejectedTotal: this.identityRejectedTotal,
};
}
}
let instance: SimulationAdmissionController | null = null;
export function getSimulationAdmissionController(): SimulationAdmissionController {
instance ??= new SimulationAdmissionController();
return instance;
}
|