Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions src/executorlib/task_scheduler/file/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,22 @@ def backend_write_file(file_name: str, output: Any, runtime: float) -> None:
"""
file_name_out = os.path.splitext(file_name)[0][:-2]
os.rename(file_name, file_name_out + "_r.h5")
if "result" in output:
dump(
file_name=file_name_out + "_r.h5",
data_dict={"output": output["result"], "runtime": runtime},
)
else:
try:
if "result" in output:
dump(
file_name=file_name_out + "_r.h5",
data_dict={"output": output["result"], "runtime": runtime},
)
else:
dump(
file_name=file_name_out + "_r.h5",
data_dict={"error": output["error"], "runtime": runtime},
)
except Exception as serialize_error:
# Serialization failed — store the error so the job is not stuck
dump(
file_name=file_name_out + "_r.h5",
data_dict={"error": output["error"], "runtime": runtime},
data_dict={"error": serialize_error, "runtime": runtime},
)
Comment on lines +59 to 64
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make fallback error payload always serializable.

At Line 63, writing serialize_error directly can fail again for non-serializable exception objects, which breaks the new recovery path. Persist a string payload (repr/traceback) instead.

Proposed fix
+import traceback
@@
-    except Exception as serialize_error:
+    except Exception as serialize_error:
         # Serialization failed — store the error so the job is not stuck
         dump(
             file_name=file_name_out + "_r.h5",
-            data_dict={"error": serialize_error, "runtime": runtime},
+            data_dict={
+                "error": f"{type(serialize_error).__name__}: {serialize_error}",
+                "error_traceback": traceback.format_exc(),
+                "runtime": runtime,
+            },
         )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
except Exception as serialize_error:
# Serialization failed — store the error so the job is not stuck
dump(
file_name=file_name_out + "_r.h5",
data_dict={"error": output["error"], "runtime": runtime},
data_dict={"error": serialize_error, "runtime": runtime},
)
except Exception as serialize_error:
# Serialization failed — store the error so the job is not stuck
dump(
file_name=file_name_out + "_r.h5",
data_dict={
"error": f"{type(serialize_error).__name__}: {serialize_error}",
"error_traceback": traceback.format_exc(),
"runtime": runtime,
},
)
🧰 Tools
🪛 Ruff (0.15.12)

[warning] 59-59: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/executorlib/task_scheduler/file/backend.py` around lines 59 - 64, The
fallback writes the caught exception object (serialize_error) directly, which
may itself be non-serializable; change the recovery payload to store a
serializable string (e.g., traceback.format_exc() or repr(serialize_error))
instead: in the except block where dump(...) is called (references:
serialize_error, file_name_out, runtime, dump), replace the "error" value with a
string representation of the exception (and optionally include a short
tag/context) so the dump always receives a serializable payload.

os.rename(file_name_out + "_r.h5", file_name_out + "_o.h5")

Expand Down
26 changes: 25 additions & 1 deletion tests/unit/task_scheduler/file/test_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@


try:
from executorlib.task_scheduler.file.backend import backend_execute_task_in_file
from executorlib.task_scheduler.file.backend import backend_execute_task_in_file, backend_write_file
from executorlib.task_scheduler.file.shared import _check_task_output, _convert_args_and_kwargs, FutureItem
from executorlib.standalone.hdf import dump, get_runtime
from executorlib.standalone.serialize import serialize_funct
Expand Down Expand Up @@ -64,6 +64,30 @@ def test_execute_function_mixed(self):
self.assertTrue(future_file_obj.done())
self.assertEqual(future_file_obj.result(), 3)

def test_backend_write_file(self):
cache_directory = os.path.abspath("executorlib_cache")
os.makedirs(cache_directory, exist_ok=True)
file_name = os.path.join(cache_directory, "test_file_i.h5")
dump(file_name=file_name, data_dict={"fn": my_funct, "args": [1], "kwargs": {"b": 2}})
backend_write_file(file_name=file_name, output={"result": 3}, runtime=0.1)
future_file_obj = FutureItem(
file_name=os.path.join(cache_directory, "test_file_o.h5")
)
self.assertTrue(future_file_obj.done())
self.assertEqual(future_file_obj.result(), 3)

def test_backend_write_file_serialization_error(self):
cache_directory = os.path.abspath("executorlib_cache")
os.makedirs(cache_directory, exist_ok=True)
file_name = os.path.join(cache_directory, "test_file_i.h5")
dump(file_name=file_name, data_dict={"fn": my_funct, "args": [1], "kwargs": {"b": 2}})
backend_write_file(file_name=file_name, output={"result": Future()}, runtime=0.1)
future_file_obj = FutureItem(
Comment on lines +82 to +85
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

This test does not set up the input file required by backend_write_file.

At Line 83, backend_write_file renames <task>_i.h5 first, so this test can fail with FileNotFoundError before hitting the serialization-error branch. Create the input HDF file in setup for this test.

Proposed fix
     def test_backend_write_file_serialization_error(self):
         cache_directory = os.path.abspath("executorlib_cache")
         os.makedirs(cache_directory, exist_ok=True)
         file_name = os.path.join(cache_directory, "test_file_i.h5")
+        dump(file_name=file_name, data_dict={"fn": my_funct, "args": [1], "kwargs": {"b": 2}})
         backend_write_file(file_name=file_name, output={"result": Future()}, runtime=0.1)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/task_scheduler/file/test_backend.py` around lines 82 - 84, The
test calls backend_write_file which first renames the input "<task>_i.h5" and
therefore must create that HDF5 input file before invoking backend_write_file;
update the test setup to create the expected input HDF file (using the same
cache_directory/name pattern used for file_name) so backend_write_file can find
and rename it, then proceed to call backend_write_file and construct FutureItem
as before (reference backend_write_file and FutureItem to locate the change).

file_name=os.path.join(cache_directory, "test_file_o.h5")
)
with self.assertRaises(Exception):
future_file_obj.result()
Comment on lines +88 to +89
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid blind exception assertion in test.

At Line 87, assertRaises(Exception) is too broad and can mask unrelated regressions. Assert a specific expected exception type (or a stable base type used by your serialization layer).

🧰 Tools
🪛 Ruff (0.15.12)

[warning] 87-87: Do not assert blind exception: Exception

(B017)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/task_scheduler/file/test_backend.py` around lines 87 - 88, The
test currently uses a broad with self.assertRaises(Exception) around
future_file_obj.result(), which can hide other failures; change this to assert
the specific exception the serialization layer throws (e.g., the concrete
exception class used when futures from future_file_obj.result() fail — replace
Exception with that type, or use the stable project-specific base like
SerializationError/ValueError/RuntimeError used elsewhere), and optionally
assert the error message contents to make the expectation explicit; update the
test in tests/unit/task_scheduler/file/test_backend.py to call
self.assertRaises(ExpectedException) around future_file_obj.result() (and add an
assert on str(e) if a message check is needed).


def test_execute_function_mixed_selector_convert(self):
cache_directory = os.path.abspath("executorlib_cache")
os.makedirs(cache_directory, exist_ok=True)
Expand Down
Loading