[Bugfix][Frontend] Fix run_batch upload retrying on success and unawaited error body (#50588)
Signed-off-by: Chao Tan <chaos.tc@gmail.com> Co-authored-by: Claude <noreply@anthropic.com>
Tan Chao committed
Aug 24, 2026 at 04:33 UTC
e239947777e18071c8053195ce599b6511717f67
2 files changed
+64
-3
tests/entrypoints/openai/test_run_batch.py
+58
@@ -20,6 +20,7 @@ from vllm.entrypoints.openai.run_batch import (
20
BatchTranscriptionRequest,
21
download_bytes_from_url,
22
make_transcription_wrapper,
23
+ upload_data,
24
)
25
from vllm.exceptions import VLLMValidationError
26
from vllm.utils.mem_constants import MiB_bytes
@@ -1052,3 +1053,60 @@ async def test_transcription_wrapper_rejects_oversized_http_before_handler(
1053
assert isinstance(response, ErrorResponse)
1054
assert "Maximum file size exceeded" in response.error.message
1055
handler.assert_not_awaited()
1056
+
1057
+
1058
+# ---------------------------------------------------------------------------
1059
+# Unit tests for upload_data retry behavior
1060
+# ---------------------------------------------------------------------------
1061
+
1062
+
1063
+def _make_aiohttp_put_session(status: int = 200, body_text: str = ""):
1064
+ """Mock an aiohttp.ClientSession whose PUT returns the given status."""
1065
+ mock_resp = MagicMock()
1066
+ mock_resp.status = status
1067
+ mock_resp.text = AsyncMock(return_value=body_text)
1068
+ mock_resp.__aenter__ = AsyncMock(return_value=mock_resp)
1069
+ mock_resp.__aexit__ = AsyncMock(return_value=False)
1070
+
1071
+ mock_session = MagicMock()
1072
+ mock_session.put = MagicMock(return_value=mock_resp)
1073
+ mock_session.__aenter__ = AsyncMock(return_value=mock_session)
1074
+ mock_session.__aexit__ = AsyncMock(return_value=False)
1075
+ return mock_session
1076
+
1077
+
1078
+@pytest.mark.asyncio
1079
+async def test_upload_data_uploads_once_on_success():
1080
+ """A successful upload must not be retried (regression guard)."""
1081
+ session = _make_aiohttp_put_session(status=200)
1082
+ with patch(
1083
+ "vllm.entrypoints.openai.run_batch.aiohttp.ClientSession",
1084
+ return_value=session,
1085
+ ):
1086
+ await upload_data(
1087
+ "https://example.com/output.jsonl", "payload", from_file=False
1088
+ )
1089
+ assert session.put.call_count == 1
1090
+
1091
+
1092
+@pytest.mark.asyncio
1093
+async def test_upload_data_error_includes_awaited_response_body():
1094
+ """A failed upload must surface the awaited response body, not a coroutine."""
1095
+ session = _make_aiohttp_put_session(status=500, body_text="server-error-detail")
1096
+ with (
1097
+ patch(
1098
+ "vllm.entrypoints.openai.run_batch.aiohttp.ClientSession",
1099
+ return_value=session,
1100
+ ),
1101
+ patch(
1102
+ "vllm.entrypoints.openai.run_batch.asyncio.sleep",
1103
+ AsyncMock(),
1104
+ ),
1105
+ pytest.raises(Exception) as exc_info,
1106
+ ):
1107
+ await upload_data(
1108
+ "https://example.com/output.jsonl", "payload", from_file=False
1109
+ )
1110
+ message = str(exc_info.value)
1111
+ assert "server-error-detail" in message
1112
+ assert "coroutine" not in message
vllm/entrypoints/openai/run_batch.py
+6
-3
@@ -393,20 +393,23 @@ async def upload_data(output_url: str, data_or_file: str, from_file: bool) -> No
393
with open(data_or_file, "rb") as file:
394
async with session.put(output_url, data=file) as response:
395
if response.status != 200:
396
+ error_text = await response.text()
397
raise Exception(
398
f"Failed to upload file.\n"
399
f"Status: {response.status}\n"
399
- f"Response: {response.text()}"
400
+ f"Response: {error_text}"
401
)
402
else:
403
async with session.put(output_url, data=data_or_file) as response:
404
if response.status != 200:
405
+ error_text = await response.text()
406
raise Exception(
407
f"Failed to upload data.\n"
408
f"Status: {response.status}\n"
407
- f"Response: {response.text()}"
409
+ f"Response: {error_text}"
410
)
409
-
411
+ # Upload succeeded; do not retry.
412
+ return
413
except Exception as e:
414
if attempt < max_retries:
415
logger.error(