Skip to content

Commit 28d796e

Browse files
vastzubyclaude
andcommitted
fix(deployments): harden workload_calculator against bad return values
A user-supplied workload_calculator now runs on the live request path (count_workload at backend.py:443) and the benchmark path. Without a guard, a calculator that raises or returns nan/inf/negative would 500 every request and poison the autoscaler's load sums (workload_pending += ... never recovers from a nan). Fall back to the default 100 and warn. Also rename the kwargs test (it never exercised positional args) and add coverage for the raise / non-finite / negative fallbacks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent ddca34d commit 28d796e

2 files changed

Lines changed: 59 additions & 6 deletions

File tree

tests/serverless/test_remote_workload_calculator.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ async def mul(a, b):
4848
assert entry.workload_calculator is calc
4949

5050

51-
def test_workload_calculator_receives_deserialized_args() -> None:
51+
def test_workload_calculator_receives_deserialized_kwargs() -> None:
5252
d = Deployment(name="wl-args")
5353

5454
@d.remote(workload_calculator=lambda a, b: float(len(a) * len(b)))
@@ -76,6 +76,39 @@ async def mul(a, b):
7676
assert payload.count_workload() == 100.0
7777

7878

79+
def test_workload_calculator_falls_back_when_it_raises() -> None:
80+
d = Deployment(name="wl-raises")
81+
82+
def boom(a, b):
83+
raise ValueError("bad input")
84+
85+
@d.remote(workload_calculator=boom)
86+
async def mul(a, b):
87+
return a
88+
89+
handler = _handler_for(d, "/remote/mul", boom)
90+
payload = handler.payload_cls().from_json_msg(
91+
_client_payload(d, a=[1, 2, 3], b=[4, 5])
92+
)
93+
assert payload.count_workload() == 100.0
94+
95+
96+
def test_workload_calculator_falls_back_on_negative_or_nan() -> None:
97+
d = Deployment(name="wl-bad-value")
98+
99+
@d.remote()
100+
async def mul(a, b):
101+
return a
102+
103+
for bad in (-1.0, float("nan"), float("inf")):
104+
calc = (lambda v: lambda a, b: v)(bad)
105+
handler = _handler_for(d, "/remote/mul", calc)
106+
payload = handler.payload_cls().from_json_msg(
107+
_client_payload(d, a=[1, 2, 3], b=[4, 5])
108+
)
109+
assert payload.count_workload() == 100.0
110+
111+
79112
def test_into_worker_wires_workload_calculator(monkeypatch) -> None:
80113
d = Deployment(name="wl-into-worker")
81114

vastai/serverless/server/worker.py

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from aiohttp import web, ClientResponse
1010
import logging
1111
import json
12+
import math
1213
import random
1314
import inspect
1415
import logging
@@ -30,6 +31,10 @@
3031
]
3132
WorkloadCalculator = Callable[[Dict[str, Any]], float]
3233

34+
log = logging.getLogger(__name__)
35+
36+
DEFAULT_WORKLOAD = 100.0
37+
3338

3439
@dataclass
3540
class LogActionConfig:
@@ -172,11 +177,26 @@ def from_dict(cls, input: Dict[str, Any]) -> "GenericApiPayload":
172177
return cls(input=input)
173178

174179
def count_workload(self) -> float:
175-
# Use custom workload calculator if provided
176-
if user_workload_calculator:
177-
return user_workload_calculator(self.input)
178-
# Default to 100 unless overridden
179-
return 100.0
180+
if not user_workload_calculator:
181+
return DEFAULT_WORKLOAD
182+
# Fall back rather than let a bad calculator 500 requests or
183+
# poison the autoscaler's load sums.
184+
try:
185+
workload = float(user_workload_calculator(self.input))
186+
except Exception:
187+
log.warning(
188+
f'workload_calculator for "{route_path}" raised; '
189+
f"falling back to {DEFAULT_WORKLOAD}",
190+
exc_info=True,
191+
)
192+
return DEFAULT_WORKLOAD
193+
if not math.isfinite(workload) or workload < 0:
194+
log.warning(
195+
f'workload_calculator for "{route_path}" returned '
196+
f"{workload!r}; falling back to {DEFAULT_WORKLOAD}"
197+
)
198+
return DEFAULT_WORKLOAD
199+
return workload
180200

181201
@classmethod
182202
def from_json_msg(cls, json_msg: Dict[str, Any]) -> "GenericApiPayload":

0 commit comments

Comments
 (0)