Phase 1: lock cron, reload chaud, argon2, providers, IMDb lookup, cache LRU, /health, /metrics, rate limit, UI dark, biome
This commit is contained in:
@@ -2,18 +2,23 @@
|
||||
// queries across them. Workers are kept alive between requests so the chunks
|
||||
// stay loaded in memory (replaces the per-request `php searchmultithreads.php`
|
||||
// fork from the PHP version).
|
||||
//
|
||||
// A filesystem watcher detects when the cron rewrites the chunks and recycles
|
||||
// the worker pool transparently — no server restart needed.
|
||||
|
||||
import { Worker } from 'node:worker_threads';
|
||||
import { join } from 'node:path';
|
||||
import { existsSync, watch } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname } from 'node:path';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { TMDBINTEGRAL_DIR, NB_WORKERS } from '../config.js';
|
||||
import { Worker } from 'node:worker_threads';
|
||||
import { NB_WORKERS, TMDBINTEGRAL_DIR } from '../config.js';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const WORKER_PATH = join(__dirname, 'searchWorker.js');
|
||||
|
||||
const pools = new Map();
|
||||
let watcher = null;
|
||||
let reloadTimer = null;
|
||||
const RELOAD_DEBOUNCE_MS = 5000;
|
||||
|
||||
class WorkerPool {
|
||||
constructor(type) {
|
||||
@@ -55,10 +60,45 @@ class WorkerPool {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async terminate() {
|
||||
await Promise.allSettled(this.workers.map((w) => w.terminate()));
|
||||
this.workers = [];
|
||||
}
|
||||
}
|
||||
|
||||
async function reloadAllPools() {
|
||||
const types = [...pools.keys()];
|
||||
console.log(`Reloading search pools: ${types.join(', ')}`);
|
||||
for (const type of types) {
|
||||
const old = pools.get(type);
|
||||
pools.set(type, new WorkerPool(type));
|
||||
old.terminate().catch(() => {
|
||||
/* ignore */
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function ensureWatcher() {
|
||||
if (watcher) return;
|
||||
try {
|
||||
watcher = watch(TMDBINTEGRAL_DIR, (_event, filename) => {
|
||||
if (!filename) return;
|
||||
if (!/^search(movie|tv)\d+\.json$/.test(filename)) return;
|
||||
clearTimeout(reloadTimer);
|
||||
reloadTimer = setTimeout(reloadAllPools, RELOAD_DEBOUNCE_MS);
|
||||
});
|
||||
watcher.unref();
|
||||
} catch (err) {
|
||||
console.warn(`Cannot watch ${TMDBINTEGRAL_DIR} for chunk reload:`, err.message);
|
||||
}
|
||||
}
|
||||
|
||||
export function getPool(type) {
|
||||
if (!pools.has(type)) pools.set(type, new WorkerPool(type));
|
||||
if (!pools.has(type)) {
|
||||
pools.set(type, new WorkerPool(type));
|
||||
ensureWatcher();
|
||||
}
|
||||
return pools.get(type);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user