Skip to content

Commit 779808f

Browse files
Reject negative retry durations (#3885)
1 parent eaf68e9 commit 779808f

2 files changed

Lines changed: 29 additions & 0 deletions

File tree

  • src

src/dstack/_internal/server/services/runs/spec.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
SERVICE_HTTPS_DEFAULT,
55
ServiceConfiguration,
66
)
7+
from dstack._internal.core.models.profiles import ProfileRetry
78
from dstack._internal.core.models.repos.virtual import DEFAULT_VIRTUAL_REPO_ID, VirtualRunRepoData
89
from dstack._internal.core.models.routers import RouterType
910
from dstack._internal.core.models.runs import LEGACY_REPO_DIR, AnyRunConfiguration, RunSpec
@@ -74,6 +75,7 @@ def validate_run_spec_and_set_defaults(
7475
# the defaults depend on the server version, not the client version.
7576
if run_spec.run_name is not None:
7677
validate_dstack_resource_name(run_spec.run_name)
78+
_validate_retry_duration(run_spec)
7779
for mount_point in run_spec.configuration.volumes:
7880
if not is_valid_docker_volume_target(mount_point.path):
7981
raise ServerClientError(f"Invalid volume mount path: {mount_point.path}")
@@ -132,6 +134,12 @@ def validate_run_spec_and_set_defaults(
132134
run_spec.configuration.working_dir = LEGACY_REPO_DIR
133135

134136

137+
def _validate_retry_duration(run_spec: RunSpec) -> None:
138+
retry = run_spec.merged_profile.retry
139+
if isinstance(retry, ProfileRetry) and retry.duration is not None and retry.duration < 0:
140+
raise ServerClientError("retry.duration cannot be negative")
141+
142+
135143
def _check_dynamo_in_place_update_compatibility(
136144
current_run_spec: RunSpec, new_run_spec: RunSpec
137145
) -> None:

src/tests/_internal/server/services/runs/test_spec.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
import re
22
import uuid
3+
from types import SimpleNamespace
34

45
import pytest
56

67
from dstack._internal.core.errors import ServerClientError
78
from dstack._internal.core.models.configurations import ServiceConfiguration
89
from dstack._internal.core.models.files import FileArchiveMapping
10+
from dstack._internal.core.models.profiles import Profile, ProfileRetry
911
from dstack._internal.core.models.repos.local import LocalRunRepoData
1012
from dstack._internal.core.models.runs import RunSpec
1113
from dstack._internal.server.services.runs.spec import (
1214
_check_can_update_configuration,
1315
check_can_update_run_spec,
16+
validate_run_spec_and_set_defaults,
1417
)
1518
from dstack._internal.server.testing.common import get_run_spec
1619

@@ -77,6 +80,24 @@ def _run_spec_with_overrides(configuration: ServiceConfiguration, **overrides) -
7780
return RunSpec.parse_obj({**run_spec.dict(), **run_spec_overrides})
7881

7982

83+
class TestValidateRunSpecRetryDuration:
84+
def test_model_accepts_negative_retry_duration_for_backward_compatibility(self):
85+
retry = ProfileRetry(duration=-1)
86+
87+
assert retry.duration == -1
88+
89+
def test_rejects_negative_retry_duration_for_new_run_specs(self):
90+
run_spec = get_run_spec(
91+
repo_id="test-repo",
92+
profile=Profile(name="default", retry=ProfileRetry(duration=-1)),
93+
)
94+
95+
with pytest.raises(ServerClientError, match="retry.duration cannot be negative"):
96+
validate_run_spec_and_set_defaults(
97+
SimpleNamespace(ssh_public_key="ssh-rsa test"), run_spec
98+
)
99+
100+
80101
class TestCheckCanUpdateConfigurationRouterType:
81102
def test_sglang_to_dynamo_router_type_change_is_rejected(self):
82103
current = _run_spec(_service_configuration(router_type="sglang"))

0 commit comments

Comments
 (0)