From 64653023ac05c365b6ab706efa245dd027428f90 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 09:43:53 +0000 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=A7=B9=20[code=20health=20improvement?= =?UTF-8?q?]=20Refactor=20broad=20exception=20handling=20in=20=5Fatomic=5F?= =?UTF-8?q?write=5Fjson=5Fsync?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: sheepdestroyer <1377479+sheepdestroyer@users.noreply.github.com> --- router/main.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/router/main.py b/router/main.py index 556a51b..ca3756a 100644 --- a/router/main.py +++ b/router/main.py @@ -627,17 +627,17 @@ def _atomic_write_json_sync(path: str, data) -> None: try: try: f = os.fdopen(fd, "w", encoding="utf-8") - except Exception: + except OSError: os.close(fd) raise with f: json.dump(data, f, indent=2) os.replace(tmp_path, path) - except Exception: + except (OSError, TypeError, ValueError): try: os.unlink(tmp_path) - except Exception: + except OSError: pass raise From 483039f0a1ab839cbe068d426c8d6372af6b17f5 Mon Sep 17 00:00:00 2001 From: boy Date: Fri, 24 Jul 2026 12:37:53 +0200 Subject: [PATCH 2/2] fix(router): prevent fd and temp file leaks on all exception types in atomic JSON write - Use try/finally pattern in _atomic_write_json_sync to guarantee temp file unlinking on any exception. - Catch all exception types during os.fdopen to close raw file descriptor prior to re-raising. - Add unit tests for non-OSError fdopen failures and unexpected dump exceptions. --- router/main.py | 16 +++++++++------- tests/test_atomic_write.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/router/main.py b/router/main.py index ca3756a..1b23603 100644 --- a/router/main.py +++ b/router/main.py @@ -624,22 +624,24 @@ def _atomic_write_json_sync(path: str, data) -> None: """Synchronously write JSON data to path using atomic temp-file + os.replace.""" os.makedirs(os.path.dirname(path), exist_ok=True) fd, tmp_path = tempfile.mkstemp(dir=os.path.dirname(path), suffix=".tmp") + written = False try: try: f = os.fdopen(fd, "w", encoding="utf-8") - except OSError: + except Exception: os.close(fd) raise with f: json.dump(data, f, indent=2) os.replace(tmp_path, path) - except (OSError, TypeError, ValueError): - try: - os.unlink(tmp_path) - except OSError: - pass - raise + written = True + finally: + if not written: + try: + os.unlink(tmp_path) + except OSError: + pass async def _atomic_write_json_async(path: str, data) -> None: diff --git a/tests/test_atomic_write.py b/tests/test_atomic_write.py index 99d220e..c944c69 100644 --- a/tests/test_atomic_write.py +++ b/tests/test_atomic_write.py @@ -229,3 +229,35 @@ def test_atomic_write_json_sync_unlink_error(mock_unlink, mock_replace, tmp_path # Verify target file wasn't created assert not target_file.exists() + + +@patch("router.main.os.close") +@patch("router.main.os.fdopen") +def test_atomic_write_json_sync_fdopen_non_os_error(mock_fdopen, mock_close, tmp_path): + """Test that non-OSError in fdopen closes fd and unlinks temp file without leaking.""" + target_file = tmp_path / "data.json" + data = {"key": "value"} + + mock_fdopen.side_effect = RuntimeError("Mocked fdopen runtime error") + + with pytest.raises(RuntimeError, match="Mocked fdopen runtime error"): + _atomic_write_json_sync(str(target_file), data) + + assert mock_close.called + assert not target_file.exists() + assert list(tmp_path.iterdir()) == [] + + +@patch("router.main.json.dump") +def test_atomic_write_json_sync_unexpected_exception_cleanup(mock_dump, tmp_path): + """Test cleanup on unexpected exception types (e.g. RecursionError).""" + target_file = tmp_path / "data.json" + data = {"key": "value"} + + mock_dump.side_effect = RecursionError("Mocked recursion limit") + + with pytest.raises(RecursionError, match="Mocked recursion limit"): + _atomic_write_json_sync(str(target_file), data) + + assert not target_file.exists() + assert list(tmp_path.iterdir()) == []