parse-worker.ts 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
  1. /**
  2. * Parse Worker
  3. *
  4. * Runs tree-sitter parsing in a separate thread so the main thread
  5. * stays unblocked and the UI animation renders smoothly.
  6. */
  7. import { parentPort } from 'worker_threads';
  8. import { extractFromSource } from './tree-sitter';
  9. import { detectLanguage, loadGrammarsForLanguages, resetParser } from './grammars';
  10. import type { Language, ExtractionResult } from '../types';
  11. const PARSER_RESET_INTERVAL = 5000;
  12. const parseCounts = new Map<Language, number>();
  13. parentPort!.on('message', async (msg: { type: string; id?: number; filePath?: string; content?: string; languages?: Language[] }) => {
  14. if (msg.type === 'load-grammars') {
  15. await loadGrammarsForLanguages(msg.languages!);
  16. parentPort!.postMessage({ type: 'grammars-loaded' });
  17. } else if (msg.type === 'parse') {
  18. const { id, filePath, content } = msg;
  19. try {
  20. const language = detectLanguage(filePath!, content);
  21. const result: ExtractionResult = extractFromSource(filePath!, content!, language);
  22. // Periodic parser reset to reclaim WASM heap memory
  23. const count = (parseCounts.get(language) ?? 0) + 1;
  24. parseCounts.set(language, count);
  25. if (count % PARSER_RESET_INTERVAL === 0) {
  26. resetParser(language);
  27. }
  28. parentPort!.postMessage({ type: 'parse-result', id, result });
  29. } catch (err) {
  30. const message = err instanceof Error ? err.message : String(err);
  31. // WASM memory errors leave the module in a corrupted state — all
  32. // subsequent parses would also fail (cascading failures). Crash the
  33. // worker so the main thread spawns a fresh one with a clean heap.
  34. if (message.includes('memory access out of bounds') || message.includes('out of memory')) {
  35. process.exit(1);
  36. }
  37. parentPort!.postMessage({
  38. type: 'parse-result',
  39. id,
  40. result: {
  41. nodes: [],
  42. edges: [],
  43. unresolvedReferences: [],
  44. errors: [{ message: `Parse worker error: ${message}`, filePath: filePath!, severity: 'error', code: 'parse_error' }],
  45. durationMs: 0,
  46. } satisfies ExtractionResult,
  47. });
  48. }
  49. } else if (msg.type === 'shutdown') {
  50. parentPort!.postMessage({ type: 'shutdown-ack' });
  51. }
  52. });