`loadLanguage` 中的竞争条件导致未处理的崩溃 `TypeError: 无法读取 undefined 的属性 (读取 'resolve')`

作者: codeCraft-Ritik创建于 2026年9月13日更新于 2026年9月13日

Step 1: Concurrency in loadLanguage

In src/worker-script/index.js:

const loadAndGunzipFile = async (_lang) => {
  ...
  if (TessModule) {
    if (dataPath) {
      try {
        TessModule.FS.mkdir(dataPath);
      } catch (err) {
        if (res) res.reject(err.toString()); // <--- Flaw 1: Dispatches 'reject' but continues execution!
      }
    }
    TessModule.FS.writeFile(`${dataPath || '.'}/${lang}.traineddata`, data);
  }
  ...
};

try {
  await Promise.all(langsArr.map(loadAndGunzipFile));
  if (res) res.resolve(langs); // <--- Flaw 2: Dispatches 'resolve' for the exact same jobId!
} catch (err) {
  if (res) res.reject(err.toString());
}
  1. Promise.all(langsArr.map(loadAndGunzipFile)) runs file loading for all languages in parallel.
  2. For the second language, TessModule.FS.mkdir(dataPath) throws ErrnoError: FS error: File exists (EEXIST).
  3. In catch (err), res.reject(...) sends { status: 'reject', jobId, action: 'loadLanguage' } to the host.
  4. Crucially, it does not return or rethrow. Execution continues to FS.writeFile, and when Promise.all completes, line 196 calls res.resolve(langs)!
  5. The worker has now dispatched both reject and resolve packets for the exact same jobId.

Step 2: Crash on Host

In src/createWorker.js:

  1. The host receives { status: 'reject' } first:
    promises[promiseId].reject(data);
    delete promises[promiseId]; // <--- promiseId is deleted!
    
  2. The host receives the duplicate { status: 'resolve' } message immediately after:
    promises[promiseId].resolve({ jobId, data }); // <--- promises[promiseId] is undefined!
    
  3. This throws `TypeError: Cannot read …

内容来源: naptha/tesseract.js