Repository navigation
fix(utils): report port-scan exhaustion instead of returning a busy port - #10264
Open
Lesereingrape wants to merge 1 commit into
Open
Lesereingrape wants to merge 1 commit into
Lesereingrape wants to merge 1 commit into
Conversation
…t exhausts its range
This branch has not been deployed
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.
PR type
PR information
Closes #10263
find_free_port()reused its loop variable as the return value, so when every bind inside[start_port, start_port + retry)failed the function returned the last candidate that had justraised
EADDRINUSE/WinError 10048— a busy port reported as free:DeployArguments.__post_init__(swift/arguments/deploy_args.py:57) andAppArguments.__post_init__(
swift/arguments/app_args.py:50) assign that value straight to the server port, soswift deploy/swift appon an occupied port window come up configured for a port that is not theirs and only faillater inside uvicorn/gradio, with an error that no longer mentions the port scan. The sibling path
retry <= 0never runs the loop body, soreturn portraisesUnboundLocalError: cannot access local variable 'port'.The fix keeps the successful path identical — return the port as soon as the bind succeeds, which is
also how the repository's own private helper
_find_free_port()(
swift/ray/megatron/checkpoint_engine/base.py:21) is written — and turns an exhausted window into theerror a caller would already have received from
socket.bind, now naming the range that was tried:try: sock.bind(('', port)) - port = sock.getsockname()[1] - break + return sock.getsockname()[1] except OSError: pass - return port + raise OSError(f'No free port in the range [{start_port}, {start_port + retry}).')tests/utils/test_find_free_port.pycovers both broken paths and pins three controls (the defaultephemeral call, a free explicitly requested port, and a partially used window) so the happy path cannot
change silently. The tests only use
socket; no model, GPU or network access is needed.Prior art: #9870 went in the same direction and was closed unmerged by its own author on 2026-08-12
without maintainer review. This is an independent minimal change plus tests; it deliberately does not
add the extra
start_portvalidation that PR carried.Disclosure: an AI agent prepared and submitted this PR on the account of @Lesereingrape; every number
below is measured output, and no human reviewed the diff before submission.
Experiment results
python -m unittest tests.utils.test_find_free_port -von Windows 11 / Python 3.12, same interpreterfor both legs:
dbc395ff:Ran 5 tests ... FAILED (failures=1, errors=1)—test_exhausted_range_does_not_report_a_busy_portfails withAssertionError: OSError not raised(the call returns the busy last candidate), and
test_zero_retry_raises_oserrorerrors withUnboundLocalError: cannot access local variable 'port' where it is not associated with a value.The three control tests already pass, which is what pins the unchanged happy path.
Ran 5 tests ... OK.Direct symptom check on base, occupying 47110-47112 and asking for a free port in that window:
find_free_port(47110, retry=3)returned47112, and binding the returned port raisedOSError [WinError 10048].Parity for the module directory, same interpreter in both legs
(
python -m unittest discover -s tests/utils -p "test_*.py" -t .):dbc395ffRan 162 tests in 319.987s/FAILED (failures=2, errors=7, skipped=22)Ran 167 tests in 348.006s/FAILED (failures=2, errors=7, skipped=22)The
+5are exactly the five tests added here and the counts are unchanged; the sorted lists of the 9failing test ids are identical (
diffis empty), so nothing that passed on base fails here. All 9 arelocal-environment cases that already fail on base: 5 test modules do not import for optional trainer
dependencies (
msgspec, andpytestfor one of them), 2 needsklearn, and 2 are Windows filesystembehaviour (
test_copy_fileson a nested directory copy,test_last_checkpoint_symlinkwhereos.symlinkis unavailable without the symlink privilege).