* fix(draw-image): use pid-based tmp svg and cleanup in finally * test(draw-image): decouple e2e.optional from leftover tmp svg * test(draw-image): add cleanup unit test for tmp svg lifecycle * docs(draw-image): add handoff and ADR for tmp svg race fix * docs(handoff): set PR number * docs(project-map): update after structural changes --------- Co-authored-by: opencode-agent <agent@opencode.local>
2.9 KiB
ADR-075: Race condition on shared .tmp-render.svg breaks parallel renders
Статус
Accepted (2026-07-31)
Контекст
cli.ts рендерил SVG-обложки через двухфазный процесс:
writeFileSync(path.join(__dirname, ".tmp-render.svg"), svg)— запись в единственный fixed-path файл в каталоге пакета.spawnSync("node", ["render.mjs", tmpSvg, outPath])— дочерний процесс читает файл и рендерит PNG через sharp.
При параллельном запуске нескольких CLI-процессов (vitest concurrently запускает несколько test-файлов, каждый вызывает runCli через spawnSync) процессы разделяли один .tmp-render.svg: один процесс обнулял/перезаписывал файл, пока другой его читал → render.mjs падал с Input Buffer is empty (пустой буфер SVG). Дополнительно leftover .tmp-render.svg от прошлых ручных прогонов засорял git status и мог содержать неактуальный SVG, вызывая ложные падения тестов.
Решение
Использовать уникальный temp-файл с PID в имени под системным os.tmpdir():
const tmpSvg = path.join(os.tmpdir(), `draw-image-${process.pid}.svg`)
и удалять его в finally-блоке после рендера (cleanup гарантирован даже при process.exit(1)). render.mjs не изменён — он уже принимает path к SVG как argv, уникальность обеспечивает cli.ts. .gitignore уже содержал .tmp-render.svg для обратной совместимости.
Альтернативы
- Передача SVG через stdin в
render.mjs: избегает temp-файла целиком, но требует переписыванияrender.mjs(чтение из stdin вместоreadFileSync(argv)), что выходит за рамки спеки #160 и ломает контракт «render.mjsпринимает path как argv». - Уникальный temp-каталог на процесс вместо PID-суффикса:
fs.mkdtempSync(path.join(os.tmpdir(), "draw-image-"))— даёт больше изоляции, но избыточен для одного SVG-файла; PID-суффикс достаточен, т.к.spawnSyncсинхронный и один процесс не запускает два рендера одновременно. - Atomic write (write-rename):
writeFileSync(tmp + ".part")+rename(tmp + ".part", tmp)— не решает race (два процесса всё равно пишут в один путь), нужен уникальный путь.