solver-pool.ts 1.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657
  1. import { fork, type ChildProcess } from 'node:child_process';
  2. import { fileURLToPath } from 'node:url';
  3. import type { CachedResult } from '@wordle/shared';
  4. interface SolverTask {
  5. target: string;
  6. allWords: string[];
  7. mode: 'fast' | 'full';
  8. }
  9. interface WorkerMessage {
  10. ok: boolean;
  11. value?: CachedResult;
  12. error?: string;
  13. }
  14. const workerPath = fileURLToPath(new URL('./solver-worker.ts', import.meta.url));
  15. /**
  16. * Run solver computation in a forked child process so long-running entropy
  17. * calculations never block the main event loop.
  18. *
  19. * Uses child_process.fork() rather than worker_threads because the solver
  20. * modules are TypeScript and need tsx's --import hook — which fork() can
  21. * inject via execArgv while worker_threads cannot.
  22. *
  23. * Spawns a short-lived child per call; ~100 ms overhead is negligible
  24. * compared to the 14 s entropy run.
  25. */
  26. export function runSolverInWorker(task: SolverTask): Promise<CachedResult> {
  27. return new Promise((resolve, reject) => {
  28. let child: ChildProcess | null = null;
  29. const done = (err: Error | null, result?: CachedResult) => {
  30. if (child) { child.kill(); child = null; }
  31. if (err) reject(err);
  32. else resolve(result!);
  33. };
  34. child = fork(workerPath, [], {
  35. execArgv: ['--import', 'tsx'],
  36. stdio: 'pipe',
  37. });
  38. child.on('message', (msg: WorkerMessage) => {
  39. if (msg.ok) done(null, msg.value);
  40. else done(new Error(msg.error ?? 'unknown worker error'));
  41. });
  42. child.on('error', (err) => done(err));
  43. child.on('exit', (code, signal) => {
  44. if (child) done(new Error(`Worker exited code=${code} signal=${signal}`));
  45. });
  46. child.send(task);
  47. });
  48. }