Skip to content

Commit da5ab27

Browse files
dbortfacebook-github-bot
authored andcommitted
Save flatc input files when serialization fails
Summary: It's been tough to debug `flatc` serialization problems because the files are deleted after the call. Now, if the serialization fails, copy the input files to a directory where we can look at them, if the environment var `ET_EXIR_SAVE_FLATC_INPUTS_ON_FAILURE=1` is set. Reviewed By: lucylq Differential Revision: D53736362 fbshipit-source-id: 4ecd57f13ec46ba6ee0d31528f59300bc3947fd0
1 parent b5ed075 commit da5ab27

File tree

2 files changed

+97
-2
lines changed

2 files changed

+97
-2
lines changed

exir/_serialize/_flatbuffer.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,19 @@
99
import importlib.resources
1010
import os
1111
import re
12+
import shutil
1213
import subprocess
1314
import tempfile
1415

1516
from dataclasses import dataclass
1617
from typing import Callable, Dict, List, Optional, Sequence
1718

1819

20+
# If this environment variable is set to true, save the flatc input files when
21+
# serialization fails.
22+
_SAVE_FLATC_ENV: str = "ET_EXIR_SAVE_FLATC_INPUTS_ON_FAILURE"
23+
24+
1925
def _is_valid_alignment(alignment: int) -> bool:
2026
"""Returns True if the alignment is valid, or is None."""
2127
if alignment is None:
@@ -278,7 +284,33 @@ def _program_json_to_flatbuffer(
278284
with open(json_path, "wb") as json_file:
279285
json_file.write(program_json.encode("ascii"))
280286

281-
_flatc_compile(temp_dir, schema_info.root_path, json_path)
287+
try:
288+
_flatc_compile(temp_dir, schema_info.root_path, json_path)
289+
except Exception as err:
290+
# It's helpful to save the breaking files for debugging. Optionally
291+
# move them out of the auto-deleting temporary directory. Don't do
292+
# this by default because some input files can be many GB in size,
293+
# and these copies won't be auto-deleted.
294+
should_save = os.getenv(_SAVE_FLATC_ENV, "").strip() not in {"", "0"}
295+
extra_message = ""
296+
if should_save:
297+
try:
298+
saved_dir = tempfile.mkdtemp(prefix="exir-saved-flatc-")
299+
for f in os.listdir(temp_dir):
300+
shutil.move(src=os.path.join(temp_dir, f), dst=saved_dir)
301+
extra_message += f" Moved input files to '{saved_dir}'."
302+
except Exception as err2:
303+
extra_message += (
304+
f" (Failed to save input files for debugging: {err2})"
305+
)
306+
else:
307+
extra_message += (
308+
f" Set {_SAVE_FLATC_ENV}=1 to save input files on failure."
309+
)
310+
311+
raise RuntimeError(
312+
f"Failed to compile {json_path} to {output_path}." + extra_message
313+
) from err
282314
with open(output_path, "rb") as output_file:
283315
return _FlatbufferResult(
284316
data=output_file.read(), max_alignment=schema_info.max_alignment

exir/_serialize/test/test_flatbuffer.py

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,19 @@
66
# LICENSE file in the root directory of this source tree.
77

88
import os
9+
import re
10+
import shutil
911
import tempfile
1012
import unittest
1113
from typing import Dict, Optional, Sequence
1214
from unittest.mock import patch
1315

1416
from executorch.exir._serialize import _flatbuffer
15-
from executorch.exir._serialize._flatbuffer import _ResourceFiles, _SchemaInfo
17+
from executorch.exir._serialize._flatbuffer import (
18+
_program_json_to_flatbuffer,
19+
_ResourceFiles,
20+
_SchemaInfo,
21+
)
1622

1723

1824
def read_file(dir: str, filename: str) -> bytes:
@@ -266,3 +272,60 @@ def test_bad_delegate_alignment_fails(self) -> None:
266272
out_dir,
267273
delegate_alignment=bad_alignment,
268274
)
275+
276+
277+
class TestProgramJsonToFlatbuffer(unittest.TestCase):
278+
@patch.dict(os.environ, {_flatbuffer._SAVE_FLATC_ENV: "1"})
279+
def test_save_json_on_failure(self) -> None:
280+
err_msg: Optional[str] = None
281+
try:
282+
_program_json_to_flatbuffer("} some bad json {")
283+
self.fail("Should have raised an exception")
284+
except RuntimeError as err:
285+
err_msg = err.args[0]
286+
287+
self.assertIsNotNone(err_msg)
288+
match = re.search(r"Moved input files to '(.*?)'", err_msg)
289+
self.assertTrue(match, msg=f"Unexpected error message: {err_msg}")
290+
path = match.group(1)
291+
292+
files = frozenset(os.listdir(path))
293+
# Delete the files otherwise they'll accumulate every time the
294+
# test is run.
295+
shutil.rmtree(path)
296+
# Check for a couple of the files that should be there.
297+
self.assertIn("data.json", files)
298+
self.assertIn("program.fbs", files)
299+
300+
@patch.dict(os.environ, {_flatbuffer._SAVE_FLATC_ENV: "1"})
301+
def test_unable_to_save_json_on_failure(self) -> None:
302+
err_msg: Optional[str] = None
303+
try:
304+
with patch.object(
305+
_flatbuffer.shutil,
306+
"move",
307+
side_effect=Exception("shutil.move mock failure"),
308+
):
309+
_program_json_to_flatbuffer("} some bad json {")
310+
self.fail("Should have raised an exception")
311+
except RuntimeError as err:
312+
err_msg = err.args[0]
313+
314+
self.assertIsNotNone(err_msg)
315+
self.assertIn("Failed to save input files", err_msg)
316+
317+
@patch.dict(os.environ, {_flatbuffer._SAVE_FLATC_ENV: ""})
318+
def test_no_save_json_on_failure(self) -> None:
319+
err_msg: Optional[str] = None
320+
try:
321+
_program_json_to_flatbuffer("} some bad json {")
322+
self.fail("Should have raised an exception")
323+
except RuntimeError as err:
324+
err_msg = err.args[0]
325+
326+
self.assertIsNotNone(err_msg)
327+
self.assertIn(
328+
f"Set {_flatbuffer._SAVE_FLATC_ENV}=1 to save input files", err_msg
329+
)
330+
self.assertNotIn("Moved input files", err_msg)
331+
self.assertNotIn("Failed to save input files", err_msg)

0 commit comments

Comments
 (0)