Repository navigation
fix(distributed): one bad worker result no longer loses the whole run - #288
Merged
Merged
Conversation
_collect_one caught TimeoutError/ConnectionError/OSError, but _deserialize_stats raises ValueError, AttributeError or KeyError on any non-conforming JSON -- a status-code key that is not a number, a list where an object belongs, a missing "stats". One buggy worker turned the entire cluster run into a traceback, and since the control plane is unauthenticated unless --worker-secret is set, the payload need not even come from a worker anyone trusts. The deserialize is now wrapped: a malformed result is logged against the worker that sent it and discarded, and every other node's results are still merged and reported. _deserialize_stats validates the shapes it reads rather than trusting them, and each rejection names the field -- the message is what the master logs, so "status_codes key 'abc' is not a status code" beats "invalid literal for int() with base 10: 'abc'". Closes #237 Signed-off-by: Yurii Riabikov <22548029+kurok@users.noreply.github.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #237
The bug
_collect_onecaughtTimeoutError/ConnectionError/OSError, but_deserialize_statsraisesValueError,AttributeErrororKeyErroron any non-conforming JSON — a status-code key that is not a number, a list where an object belongs, a missing"stats".So one buggy worker turned the entire cluster run into a traceback, taking every other node's results with it. And since the control plane is unauthenticated unless
--worker-secretis set, the payload need not even come from a worker anyone trusts.Reproduced:
_deserialize_stats({"status_codes": {"abc": 1}})→ValueError: invalid literal for int() with base 10: 'abc', propagating straight out ofrun_master.The fix
The deserialize is wrapped. A malformed result is logged against the worker that sent it and discarded; every other node's results are still merged and reported.
_deserialize_statsnow validates the shapes it reads rather than trusting them, and every rejection names the field. That matters because the message is what the master logs, per worker:reads better in an operator's log than the bare
invalid literaldid.latencies must be an array, got str,rps_timeline entry must be an array, got int, and so on.Trueis rejected for a numeric field too —isinstance(True, int)isTruein Python, and a bool intotal_requestsis a bug worth surfacing.Tests
tests/test_audit_distributed.py::TestMasterSurvivesBadWorker::test_master_survives_malformed_result_from_one_worker— two real sockets, one valid result of 250 requests and one{"status_codes": {"abc": 1}}. Assertsmerged.total_requests == 250and that the bad worker was reported. Pre-fix:ValueError: invalid literal for int() with base 10: 'abc'out ofrun_master, no result at all.tests/test_distributed.py::TestMalformedStats— seven malformed shapes as subtests, each asserting the message names its field, plus a round-trip test so the hardening did not break the valid path. All seven subtests fail pre-fix.Checks
ruff format/check clean, mypy clean,
1913 passed.