Skip to content

Commit 63ea25b

Browse files
committed
fix comments
1 parent 13aa170 commit 63ea25b

6 files changed

Lines changed: 77 additions & 9 deletions

File tree

api/test/api/test_launch_template_juicefs.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ def test_build_juicefs_pod_config_mount_command_includes_console_url(monkeypatch
6868

6969
assert env_vars["TFL_JUICEFS_CONSOLE_URL"] == "http://juicefs-console:8080"
7070
assert '--console-url "$TFL_JUICEFS_CONSOLE_URL"' in mount_cmd
71+
assert mount_cmd.count('--console-url "$TFL_JUICEFS_CONSOLE_URL"') == 2
7172

7273

7374
def test_build_juicefs_pod_config_custom_mount_point(monkeypatch):

api/test/api/test_remote_workspace_juicefs.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,18 @@ def test_validate_juicefs_config_raises_when_not_mounted(monkeypatch, tmp_path):
4646
remote_workspace._validate_juicefs_config()
4747

4848

49+
def test_validate_juicefs_config_raises_on_invalid_storage_backend(monkeypatch, tmp_path):
50+
monkeypatch.setenv("TFL_JUICEFS_METADATA_URL", "redis://localhost:6379/1")
51+
monkeypatch.setenv("TFL_JUICEFS_VOLUME_NAME", "myvol")
52+
monkeypatch.setenv("TFL_JUICEFS_STORAGE_BACKEND", "s3")
53+
monkeypatch.setenv("TFL_JUICEFS_MOUNT_POINT", str(tmp_path))
54+
monkeypatch.setattr(remote_workspace, "STORAGE_PROVIDER", "juicefs", raising=False)
55+
56+
with patch("transformerlab.shared.remote_workspace.os.path.ismount", return_value=True):
57+
with pytest.raises(SystemExit):
58+
remote_workspace._validate_juicefs_config()
59+
60+
4961
def test_validate_juicefs_config_passes_when_valid(monkeypatch, tmp_path):
5062
monkeypatch.setenv("TFL_JUICEFS_METADATA_URL", "redis://localhost:6379/1")
5163
monkeypatch.setenv("TFL_JUICEFS_VOLUME_NAME", "myvol")

api/transformerlab/services/compute_provider/launch_juicefs.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,19 +61,15 @@ def build_juicefs_pod_config(
6161
f" --subdir {shlex.quote(f'orgs/{team_id}')} --background"
6262
)
6363
if juicefs_token:
64+
console_flag = ' --console-url "$TFL_JUICEFS_CONSOLE_URL"' if juicefs_console_url else ""
6465
auth_cmd = (
6566
'if [ -n "$ACCESS_KEY" ] && [ -n "$SECRET_KEY" ]; then '
66-
f'juicefs auth {shlex.quote(volume_name)} --token "$TFL_JUICEFS_TOKEN" '
67+
f'juicefs auth {shlex.quote(volume_name)} --token "$TFL_JUICEFS_TOKEN"{console_flag} '
6768
'--access-key "$ACCESS_KEY" --secret-key "$SECRET_KEY"; '
6869
"else "
69-
f'juicefs auth {shlex.quote(volume_name)} --token "$TFL_JUICEFS_TOKEN"; '
70+
f'juicefs auth {shlex.quote(volume_name)} --token "$TFL_JUICEFS_TOKEN"{console_flag}; '
7071
"fi"
7172
)
72-
if juicefs_console_url:
73-
auth_cmd = auth_cmd.replace(
74-
'--token "$TFL_JUICEFS_TOKEN"',
75-
'--token "$TFL_JUICEFS_TOKEN" --console-url "$TFL_JUICEFS_CONSOLE_URL"',
76-
)
7773
mount_cmd = f"{auth_cmd} && {mount_cmd}"
7874

7975
return env_vars, mount_cmd, mount_point

api/transformerlab/shared/remote_workspace.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,13 @@ def _validate_juicefs_config() -> None:
187187
if not os.getenv(required):
188188
print(f"❌ ERROR: {required} is required when TFL_STORAGE_PROVIDER=juicefs", file=sys.stderr)
189189
sys.exit(1)
190+
backend = os.getenv("TFL_JUICEFS_STORAGE_BACKEND", "")
191+
if backend not in ("aws", "gcp", "azure"):
192+
print(
193+
f"❌ ERROR: Invalid TFL_JUICEFS_STORAGE_BACKEND. Expected one of: aws, gcp, azure; got {backend!r}",
194+
file=sys.stderr,
195+
)
196+
sys.exit(1)
190197
mount_point = os.getenv("TFL_JUICEFS_MOUNT_POINT", "/mnt/juicefs")
191198
if not os.path.ismount(mount_point):
192199
print(

lab-sdk/src/lab/experiment.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import asyncio
22
import contextlib
3+
import os
4+
import random
35
import time
46
import uuid
57
from datetime import datetime, timezone
@@ -224,12 +226,19 @@ async def create_job(self, type: str = "REMOTE"):
224226
# filesystems can surface stale metadata/races. Retry with a fresh UUID
225227
# if a just-created index file appears to already exist.
226228
new_job = None
227-
for _ in range(5):
229+
storage_provider = os.getenv("TFL_STORAGE_PROVIDER", "").lower()
230+
for attempt in range(5):
228231
new_job_id = str(uuid.uuid4())
229232
try:
230233
new_job = await Job.create(new_job_id, self.id)
231234
break
232235
except FileExistsError:
236+
logger.warning("Job.create raised FileExistsError; retrying with new UUID", exc_info=True)
237+
# JuiceFS can briefly surface stale metadata, causing false-positive
238+
# "already exists" signals right after creation attempts.
239+
if storage_provider == "juicefs" and attempt < 4:
240+
delay_seconds = (0.05 * (2**attempt)) + random.uniform(0.0, 0.05)
241+
await asyncio.sleep(delay_seconds)
233242
continue
234243
if new_job is None:
235244
raise RuntimeError(f"Unable to create a unique job ID for experiment '{self.id}' after retries")

lab-sdk/tests/test_experiment.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ async def test_create_job_uses_uuid_and_experiment_dir(tmp_path, monkeypatch):
199199

200200

201201
@pytest.mark.asyncio
202-
async def test_create_job_retries_after_file_exists_error(tmp_path, monkeypatch):
202+
async def test_create_job_retries_after_file_exists_error(tmp_path, monkeypatch, caplog):
203203
for mod in list(importlib.sys.modules.keys()):
204204
if mod.startswith("lab."):
205205
importlib.sys.modules.pop(mod)
@@ -225,10 +225,53 @@ async def flaky_create(job_id, experiment_id):
225225

226226
monkeypatch.setattr(Job, "create", flaky_create)
227227

228+
caplog.set_level("WARNING", logger="lab.experiment")
228229
exp = Experiment("alpha")
229230
job = await exp.create_job("TRAIN")
230231
assert str(job.id) == "unique-id"
231232
assert attempts["count"] == 2
233+
assert "Job.create raised FileExistsError; retrying with new UUID" in caplog.text
234+
235+
236+
@pytest.mark.asyncio
237+
async def test_create_job_retries_with_backoff_for_juicefs(tmp_path, monkeypatch):
238+
for mod in list(importlib.sys.modules.keys()):
239+
if mod.startswith("lab."):
240+
importlib.sys.modules.pop(mod)
241+
242+
ws = tmp_path / "ws"
243+
ws.mkdir()
244+
monkeypatch.setenv("TFL_WORKSPACE_DIR", str(ws))
245+
monkeypatch.setenv("TFL_STORAGE_PROVIDER", "juicefs")
246+
247+
from lab.experiment import Experiment
248+
from lab.job import Job
249+
250+
ids = iter(["collision-id", "unique-id"])
251+
monkeypatch.setattr("lab.experiment.uuid.uuid4", lambda: next(ids))
252+
253+
original_create = Job.create
254+
attempts = {"count": 0}
255+
sleep_calls = {"count": 0}
256+
257+
async def flaky_create(job_id, experiment_id):
258+
attempts["count"] += 1
259+
if attempts["count"] == 1:
260+
raise FileExistsError("simulated duplicate job id")
261+
return await original_create(job_id, experiment_id)
262+
263+
async def fake_sleep(_: float):
264+
sleep_calls["count"] += 1
265+
266+
monkeypatch.setattr(Job, "create", flaky_create)
267+
monkeypatch.setattr("lab.experiment.asyncio.sleep", fake_sleep)
268+
monkeypatch.setattr("lab.experiment.random.uniform", lambda _a, _b: 0.0)
269+
270+
exp = Experiment("alpha")
271+
job = await exp.create_job("TRAIN")
272+
assert str(job.id) == "unique-id"
273+
assert attempts["count"] == 2
274+
assert sleep_calls["count"] == 1
232275

233276

234277
@pytest.mark.asyncio

0 commit comments

Comments
 (0)