fix(understand): a truncated DXF is refused by the snapshot, not left hanging
ezdxf's tag reader raises StopIteration on a file that ends early, for example after an interrupted save or a partial copy. read_snapshot caught only OSError and DXFError, so the StopIteration escaped. asyncio.to_thread cannot hand StopIteration back to an awaiting caller, so every track H tool reading such a file would have hung instead of refusing it. The review of drawing_diff found this in its own reader. The snapshot now does the same: every parse or walk failure except MemoryError becomes the existing "is not a readable DXF file" refusal. A new test checks this both directly and through a worker thread with a 10 s timeout. It failed with StopIteration before this change and passes after it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
704a44b190
commit
efa1ea8182
@@ -297,11 +297,16 @@ def records_from_doc(doc, *, source: str) -> Snapshot:
|
||||
|
||||
|
||||
def _read(path: str, *, source: str) -> Snapshot:
|
||||
# Every parse or walk failure is one refusal. A truncated file makes ezdxf's
|
||||
# tag reader raise StopIteration, which asyncio.to_thread cannot hand back to
|
||||
# an awaiting tool - uncaught, the tool would never answer.
|
||||
try:
|
||||
doc = ezdxf.readfile(path)
|
||||
except (OSError, ezdxf.DXFError) as exc:
|
||||
raise ValueError(f"{path} is not a readable DXF file: {exc}") from exc
|
||||
return records_from_doc(doc, source=source)
|
||||
return records_from_doc(ezdxf.readfile(path), source=source)
|
||||
except MemoryError:
|
||||
raise
|
||||
except Exception as exc: # noqa: BLE001 - see above
|
||||
reason = str(exc) or type(exc).__name__
|
||||
raise ValueError(f"{path} is not a readable DXF file: {reason}") from exc
|
||||
|
||||
|
||||
def read_snapshot(path: str) -> Snapshot:
|
||||
|
||||
@@ -8,6 +8,7 @@ behaves the way AutoCAD 2026 was measured to behave (the extension must be
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import math
|
||||
from pathlib import Path
|
||||
|
||||
@@ -173,6 +174,23 @@ def test_read_snapshot_refuses_missing_and_unreadable_files(tmp_path):
|
||||
assert "not a readable DXF" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_a_truncated_dxf_is_refused_not_left_hanging(tmp_path):
|
||||
# An interrupted save: the header opens and the file ends. ezdxf's tag reader
|
||||
# raises StopIteration here, which asyncio.to_thread cannot hand back to an
|
||||
# awaiting tool - the call would never answer. It must be the same refusal
|
||||
# as any other unreadable file, also through a worker thread.
|
||||
half = tmp_path / "half.dxf"
|
||||
half.write_text(" 0\nSECTION\n 2\nHEADER\n 9\n$ACADVER\n 1\nAC1015\n", encoding="utf-8")
|
||||
with pytest.raises(ValueError, match="not a readable DXF"):
|
||||
read_snapshot(str(half))
|
||||
|
||||
async def through_a_thread():
|
||||
return await asyncio.wait_for(asyncio.to_thread(read_snapshot, str(half)), timeout=10)
|
||||
|
||||
with pytest.raises(ValueError, match="not a readable DXF"):
|
||||
asyncio.run(through_a_thread())
|
||||
|
||||
|
||||
# -- take_snapshot on the headless engine ---------------------------------------
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user