File size: 5,489 Bytes
050141c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
/* tools/lib/pool.mjs β€” ν•œ λŒ€μ˜ κΈ°κ³„μ—μ„œ N개 μžμ‹ ν”„λ‘œμ„ΈμŠ€λ‘œ μž‘μ—…μ„ ν˜λ €λ³΄λ‚΄λŠ” ν’€.

   이 μ €μž₯μ†Œμ˜ κ³„κΈ°νŒμ€ μ „λΆ€ (슬둯 Γ— μ‹œλ“œ Γ— 쌍) 같은 **μ„œλ‘œ 독립인 μž‘μ—…**을 순차둜 λŒλ¦°λ‹€.
   β‘€ 증λͺ…κΈ°κ°€ κ·Έ ν•œκ³„μ— λ¨Όμ € λΆ€λ”ͺν˜”λ‹€ β€” 쌍 ν•˜λ‚˜μ— μ΅œλŒ€ 150초, 슬둯 32개면 ν•˜λ£¨κ°€ κ°„λ‹€.
   μ—¬κΈ°μ„œ λ³‘λ ¬ν™”μ˜ λ‹¨μœ„λŠ” **μž‘μ—…**이지 파일이 μ•„λ‹ˆλ‹€. μž‘μ—…λ§ˆλ‹€ ν”„λ‘œμ„ΈμŠ€λ₯Ό μƒˆλ‘œ λ„μš°λ©΄
   engine.js(1.5MB) λ₯Ό 맀번 λ‹€μ‹œ μ½μœΌλ―€λ‘œ, μ›Œμ»€λŠ” **μ˜μ†**이고 μž‘μ—…λ§Œ ν˜λ €λ³΄λ‚Έλ‹€.

   ν”„λ‘œν† μ½œμ€ stdout 의 JSON ν•œ 쀄이닀:
     λΆ€λͺ¨ -> μ›Œμ»€   {"id": 7, "job": {...}}
     μ›Œμ»€ -> λΆ€λͺ¨   {"id": 7, "ok": true, "result": {...}}  λ˜λŠ”  {"id":7,"ok":false,"err":"..."}
   μ›Œμ»€λŠ” **stdout 에 κ·Έ μ™Έ 아무것도 μ“°λ©΄ μ•ˆ λœλ‹€**. μ§„ν–‰ λ‘œκ·ΈλŠ” stderr 둜 보낸닀
   (λΆ€λͺ¨κ°€ κ·ΈλŒ€λ‘œ ν˜λ €λ³΄λ‚΄λ―€λ‘œ μ‚¬λžŒμ€ κ·ΈλŒ€λ‘œ μ½λŠ”λ‹€).

   μ‹œκ°„ ν•œκ³„λ₯Ό κ°€μ§„ μž‘μ—…μ˜ 주의 (measured, 2026-08-01): λ³‘λ ¬λ‘œ 돌리면 같은 μž‘μ—…μ΄ 더 였래
   κ±Έλ¦°λ‹€. κ·Έλž˜μ„œ 이 μ €μž₯μ†Œμ˜ νŒμ •μ€ **μƒνƒœ ν•œκ³„**(결정적)λ₯Ό 1μˆœμœ„λ‘œ 두고 μ‹œκ°„ ν•œκ³„λŠ”
   μ•ˆμ „λ§μœΌλ‘œλ§Œ μ“΄λ‹€. μ–΄λŠ ν•œκ³„μ— κ±Έλ ΈλŠ”μ§€ 결과에 μ λŠ” 것은 μ›Œμ»€μ˜ μ±…μž„μ΄λ‹€ β€” κ·Έλž˜μ•Ό
   "8개둜 λŒλ Έλ”λ‹ˆ λ―ΈνŒμ •μ΄ λŠ˜μ—ˆλ‹€"κ°€ 발견이 μ•„λ‹ˆλΌ λΆ€ν•˜λΌλŠ” 것을 ꡬ별할 수 μžˆλ‹€. */
import { spawn } from 'node:child_process';
import os from 'node:os';

export function poolSize(req) {
  const n = Number(req) || 0;
  if (n > 0) return n;
  return Math.max(2, Math.min(8, os.cpus().length - 2));
}

/* runPool(jobs, workerPath, opts) -> Promise<Array<result|null>>
   κ²°κ³ΌλŠ” **μž…λ ₯ μˆœμ„œ**둜 λŒμ•„μ˜¨λ‹€(μ™„λ£Œ μˆœμ„œκ°€ μ•„λ‹ˆλ‹€ β€” 보고가 μ‹€ν–‰ μˆœμ„œμ— 흔듀리면
   같은 λͺ…령이 λ‹€λ₯Έ ν‘œλ₯Ό λ‚Έλ‹€). opts.onDone(i, result) 은 μ™„λ£Œ μ¦‰μ‹œ λΆˆλ¦°λ‹€(μ§„ν–‰ ν‘œμ‹œμš©).
   μ›Œμ»€κ°€ 죽으면 κ·Έ μž‘μ—…μ€ null 이고, 남은 μž‘μ—…μ€ λ‹€λ₯Έ μ›Œμ»€κ°€ 계속 κ°€μ Έκ°„λ‹€. */
export async function runPool(jobs, workerPath, opts = {}) {
  const N = Math.min(poolSize(opts.concurrency), Math.max(1, jobs.length));
  const results = new Array(jobs.length).fill(null);
  let cursor = 0;
  let done = 0;
  const kids = [];

  const startKid = () => {
    const kid = spawn(process.execPath, [workerPath, ...(opts.argv || [])], {
      cwd: opts.cwd || process.cwd(),
      env: { ...process.env, ...(opts.env || {}) },
      stdio: ['pipe', 'pipe', 'inherit'],
    });
    const st = { kid, busy: -1, buf: '' };
    kid.stdout.on('data', (d) => {
      st.buf += d;
      let nl;
      while ((nl = st.buf.indexOf('\n')) !== -1) {
        const line = st.buf.slice(0, nl); st.buf = st.buf.slice(nl + 1);
        if (!line.trim()) continue;
        let msg;
        try { msg = JSON.parse(line); }
        catch { process.stderr.write(`[pool] μ›Œμ»€κ°€ JSON μ•„λ‹Œ 쀄을 stdout 에 썼닀: ${line}\n`); continue; }
        results[msg.id] = msg.ok ? msg.result : { error: msg.err || 'unknown' };
        done++;
        if (opts.onDone) opts.onDone(msg.id, results[msg.id], done, jobs.length);
        st.busy = -1;
        feed(st);
      }
    });
    kid.on('exit', (code) => {
      if (st.busy >= 0) {                       // μ£½μœΌλ©΄μ„œ λ¬Όκ³  κ°„ μž‘μ—…μ€ μ‹€νŒ¨λ‘œ μ λŠ”λ‹€
        results[st.busy] = { error: `worker exited (${code})` };
        done++;
        st.busy = -1;
      }
      st.dead = true;
      // μ‚΄μ•„ μžˆλŠ” μ›Œμ»€κ°€ ν•˜λ‚˜λ„ μ—†λŠ”λ° μž‘μ—…μ΄ λ‚¨μ•˜μœΌλ©΄ 더 λͺ» λˆλ‹€ β€” 남은 것은 null 둜 λλ‚œλ‹€.
      if (kids.every(k => k.dead) && cursor < jobs.length) {
        for (let i = cursor; i < jobs.length; i++) { results[i] = { error: 'no worker' }; done++; }
        cursor = jobs.length;
      }
      settleMaybe();
    });
    return st;
  };

  let resolveAll;
  const all = new Promise((res) => { resolveAll = res; });
  const settleMaybe = () => {
    if (done >= jobs.length) {
      for (const k of kids) if (!k.dead) k.kid.stdin.end();
      resolveAll(results);
    }
  };
  const feed = (st) => {
    if (st.dead) return;
    if (cursor >= jobs.length) { st.kid.stdin.end(); settleMaybe(); return; }
    const i = cursor++;
    st.busy = i;
    st.kid.stdin.write(JSON.stringify({ id: i, job: jobs[i] }) + '\n');
  };

  for (let k = 0; k < N; k++) kids.push(startKid());
  for (const st of kids) feed(st);
  const out = await all;
  return out;
}

/* μ›Œμ»€ μͺ½ 헬퍼 β€” stdin 의 JSON 쀄을 λ°›μ•„ handler 에 λ„˜κΈ°κ³  κ²°κ³Όλ₯Ό ν•œ μ€„λ‘œ λŒλ €μ€€λ‹€.
   handler κ°€ λ˜μ§€λ©΄ κ·Έ μž‘μ—…λ§Œ μ‹€νŒ¨λ‘œ λŒμ•„κ°€κ³  μ›Œμ»€λŠ” 계속 μ‚°λ‹€(ν•œ 슬둯의 버그가 λ‚˜λ¨Έμ§€
   31개의 츑정을 λͺ» μ§€μš°κ²Œ). */
export function serveWorker(handler) {
  let buf = '';
  process.stdin.setEncoding('utf8');
  process.stdin.on('data', async (d) => {
    buf += d;
    let nl;
    const lines = [];
    while ((nl = buf.indexOf('\n')) !== -1) { lines.push(buf.slice(0, nl)); buf = buf.slice(nl + 1); }
    for (const line of lines) {
      if (!line.trim()) continue;
      const { id, job } = JSON.parse(line);
      try {
        const result = await handler(job);
        process.stdout.write(JSON.stringify({ id, ok: true, result }) + '\n');
      } catch (e) {
        process.stdout.write(JSON.stringify({ id, ok: false, err: (e && e.stack) || String(e) }) + '\n');
      }
    }
  });
  process.stdin.on('end', () => process.exit(0));
}