diff --git a/src/kernel/backends/occt/occtException.test.ts b/src/kernel/backends/occt/occtException.test.ts index 7824e8afa..cb9bec5dc 100644 --- a/src/kernel/backends/occt/occtException.test.ts +++ b/src/kernel/backends/occt/occtException.test.ts @@ -115,6 +115,13 @@ describe('isOcctWasmPoisoned', () => { expect(isOcctWasmPoisoned(err)).toBe(true); }); + it('recognizes a broken wasm function table', () => { + const err = new Error('null function or function signature mismatch'); + err.name = 'RuntimeError'; + expect(isOcctWasmPoisoned(err)).toBe(true); + expect(isOcctWasmPoisoned(new Error('RuntimeError: null function or function signature mismatch'))).toBe(true); + }); + it('recognizes worker-wrapped Error messages', () => { expect(isOcctWasmPoisoned(new Error('RuntimeError: memory access out of bounds'))).toBe(true); }); diff --git a/src/modeling/compute/recomputeEngine.ts b/src/modeling/compute/recomputeEngine.ts index a7115f0dc..542a1fbe9 100644 --- a/src/modeling/compute/recomputeEngine.ts +++ b/src/modeling/compute/recomputeEngine.ts @@ -594,24 +594,12 @@ export class RecomputeEngine { return await this.runPass(records, opts); } catch (retryErr) { if (!isOcctWasmPoisoned(retryErr)) throw retryErr; - const message = + // Throw, do not return a per-feature diagnostic. A returned failure + // leaves this dead heap in service and the viewer never gets a mesh. + throw new Error( `${WASM_POISON_MARKER}: ${describeOcctThrow(retryErr)}. ` + - 'OCCT was reset and the full recompute retried; still failing. Restart the host process.'; - return { - shapes: new Map(), - diagnostics: [{ - target: this.lowerer.target, - code: 'recompute.lowering.exception', - severity: 'error', - message, - hint: - 'The OCCT wasm heap was corrupted (often by an embind double-free). ' + - 'Reset + retry did not recover; restart the Node/export process or reload Studio.', - }], - health: new Map(), - mechanism: 'unverified', - mechanismFailures: [], - }; + 'OCCT was reset and the full recompute retried; still failing. Restart the host process.', + ); } } } diff --git a/src/shared/occt/wasmPoison.ts b/src/shared/occt/wasmPoison.ts index f41805ac0..2a18a7bb2 100644 --- a/src/shared/occt/wasmPoison.ts +++ b/src/shared/occt/wasmPoison.ts @@ -19,6 +19,11 @@ function looksLikeWasmPoisonText(text: string): boolean { // Emscripten abort() after unrecoverable wasm failure (truncated BREP, etc.). if (t.includes('aborted()') || t.includes('aborted(')) return true; if (t.includes('runtimeerror') && t.includes('aborted')) return true; + // Function-table corruption. Prod OCCT workers threw + // "RuntimeError: null function or function signature mismatch" and every + // later feature was recorded as a normal compile failure, so the worker + // stayed up and the viewer never painted. + if (t.includes('function signature mismatch') || t.includes('null function')) return true; return false; }