screenshot-service/src/container-pool.ts

111 lines
2.6 KiB
TypeScript
Raw Normal View History

2022-06-19 13:33:25 +02:00
import { hasShape, predicates, is } from "@sealcode/ts-predicates";
import {
ChildProcessWithoutNullStreams,
spawn,
spawnSync,
} from "child_process";
import { concurrency } from "../config.json";
import { IMAGE_NAME } from "./docker-args";
2022-06-17 09:37:27 +02:00
2022-06-19 13:33:25 +02:00
export class Container {
callbacks: Array<() => void> = [];
ready = false;
id: string;
output: "";
bg_process: ChildProcessWithoutNullStreams;
2022-06-17 09:37:27 +02:00
constructor() {
this.id = spawnSync(
"docker",
["run", "-d", "-v", `${process.cwd()}/static:/opt/static`, IMAGE_NAME],
{
cwd: process.cwd(),
}
)
.stdout.toString()
.replace("\n", "");
this.bg_process = spawn("docker", ["logs", "-f", this.id]);
2022-06-19 13:33:25 +02:00
this.bg_process.stdout.on("data", (d: Buffer) => {
2022-06-17 09:37:27 +02:00
try {
2022-06-19 13:33:25 +02:00
const parsed = JSON.parse(d.toString()) as unknown;
if (
is(parsed, predicates.object) &&
hasShape({ code: predicates.string }, parsed) &&
parsed.code == "ready"
) {
2022-06-17 09:37:27 +02:00
this.ready = true;
this.signalReady();
}
2022-06-19 13:33:25 +02:00
} catch (e) {
// noop
}
2022-06-17 09:37:27 +02:00
this.output += d.toString();
});
}
2022-06-19 13:33:25 +02:00
signalReady(): void {
this.callbacks.forEach((callback) => callback());
2022-06-17 09:37:27 +02:00
}
2022-06-19 13:33:25 +02:00
onReady(callback: () => void): void {
2022-06-17 09:37:27 +02:00
this.ready ? callback() : this.callbacks.push(callback);
}
2022-06-19 13:33:25 +02:00
async waitReady(): Promise<void> {
2022-06-17 09:37:27 +02:00
if (this.ready) {
return;
}
2022-06-19 13:33:25 +02:00
return new Promise<void>((resolve) => {
2022-06-17 09:37:27 +02:00
this.onReady(resolve);
});
}
2022-06-19 13:33:25 +02:00
close(): void {
2022-06-17 09:37:27 +02:00
spawn("docker", ["rm", "-f", this.id]);
}
2022-06-17 11:36:18 +02:00
2022-06-19 13:33:25 +02:00
closeSync(): void {
2022-06-17 11:36:18 +02:00
spawnSync("docker", ["rm", "-f", this.id]);
console.log("doker rm done", this.id);
}
2022-06-17 09:37:27 +02:00
}
2022-06-19 13:33:25 +02:00
export default new (class ContainerPool {
pool: Container[] = [];
constructor(public concurrency: number) {
2022-06-17 09:37:27 +02:00
this.concurrency = concurrency;
for (let i = 1; i <= this.concurrency; i++) {
this.generateContainer();
}
2022-06-17 11:36:18 +02:00
process.on("SIGINT", () => {
console.log("SIGINT");
this.clear();
});
2022-06-17 09:37:27 +02:00
}
generateContainer() {
this.pool.push(new Container());
}
getContainer() {
if (!this.pool.length) {
throw new Error("pool is empty, try again!");
}
const container = this.pool.shift(); // get and remove from pool the oldest container
2022-06-19 13:33:25 +02:00
if (!container) {
throw new Error("Pool was somehow empty!");
}
2022-06-17 09:37:27 +02:00
this.generateContainer();
return container;
}
2022-06-17 11:36:18 +02:00
clear() {
console.log("Removing all containers from the pool");
for (const container of this.pool) {
container.closeSync();
}
console.log("Removing containers done");
process.exit(0);
}
2022-06-17 09:37:27 +02:00
})(concurrency);