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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | 8x 8x 8x 20x 20x 20x 20x 27x 27x 20x 20x 20x 20x 7x 7x 20x 20x 20x 21x 20x 20x 20x 7x 7x 15x 17x 15x 6x 2x 2x 6x | /**
* DockerCompileSemaphore
*
* A lightweight FIFO counting semaphore used to limit the number of Docker
* containers that may be in the compile phase simultaneously.
*
* This prevents CPU starvation that occurs when many g++ processes compete for
* resources on the host machine. The semaphore is acquired before spawning a
* Docker container and released when the compile phase transitions to runtime
* (i.e. when [[RUNTIME_START]] is detected in stdout) or when the container
* exits with an error.
*
* Environment variable: DOCKER_COMPILE_CONCURRENT (default 8)
*/
import { config } from "../../config";
export class DockerCompileSemaphore {
private readonly queue: Array<{ attempt: () => void; timer: NodeJS.Timeout }> = [];
private _active = 0;
constructor(private readonly max: number) {}
/**
* Acquire one compile slot.
*
* @param onQueued Optional callback invoked exactly once when this caller is
* placed in the queue (i.e. no slot is immediately available).
* @returns A release function. Must be called exactly once.
*/
acquire(onQueued?: () => void, timeoutMs = 60_000): Promise<() => void> {
return new Promise<() => void>((resolve, reject) => {
let settled = false;
let attempt: () => void;
const timer = setTimeout(() => {
if (settled) return;
const index = this.queue.findIndex((entry) => entry.attempt === attempt);
if (index !== -1) this.queue.splice(index, 1);
settled = true;
reject(new Error(`Docker compile slot timeout after ${timeoutMs}ms`));
}, timeoutMs);
attempt = () => {
Iif (settled) return;
if (this._active < this.max) {
settled = true;
clearTimeout(timer);
this._active++;
resolve(this._makeRelease());
} else {
onQueued?.();
this.queue.push({ attempt, timer });
}
};
attempt();
});
}
private _makeRelease(): () => void {
let released = false;
return () => {
if (released) return; // idempotent
released = true;
this._active--;
if (this.queue.length > 0) {
const next = this.queue.shift();
next?.attempt();
}
};
}
get activeCount(): number {
return this._active;
}
get queueLength(): number {
return this.queue.length;
}
}
// ─── Singleton factory ────────────────────────────────────────────────────────
let _instance: DockerCompileSemaphore | null = null;
/**
* Returns (or lazily creates) the global DockerCompileSemaphore singleton.
*
* The concurrency limit is read from the DOCKER_COMPILE_CONCURRENT env var at
* first call. Passing `maxOverride` replaces the env-var value and resets the
* singleton – useful in tests.
*/
export function getDockerCompileSemaphore(maxOverride?: number): DockerCompileSemaphore {
if (maxOverride !== undefined || _instance === null) {
const max =
maxOverride ?? config.compilation.dockerCompileConcurrent;
_instance = new DockerCompileSemaphore(max);
}
return _instance;
}
|