Skip to content

Commit 5d935f8

Browse files
committed
update context to handle races
1 parent 073ab6c commit 5d935f8

1 file changed

Lines changed: 72 additions & 67 deletions

File tree

src/datasmith/docker/context.py

Lines changed: 72 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import contextlib
4+
import copy
45
import datetime
56
import io
67
import json
@@ -547,12 +548,14 @@ def _canonicalize_from_key(self, key: str | Task) -> Task:
547548
return self._canonicalize(t)
548549

549550
def get_default(self, tag: str = "pkg") -> tuple[Task, DockerContext]:
550-
if tag not in self.VALID_TAGS:
551-
raise ValueError(f"Unknown tag '{tag}'. Valid tags: {sorted(self.VALID_TAGS)}")
552-
# lookup under canonical default; return Task with requested tag
553-
user_task = Task(owner="default", repo="default", sha=None, tag=tag)
554-
canonical = self._canonicalize(user_task)
555-
return user_task, self.registry[canonical]
551+
with self._lock:
552+
if tag not in self.VALID_TAGS:
553+
raise ValueError(f"Unknown tag '{tag}'. Valid tags: {sorted(self.VALID_TAGS)}")
554+
# lookup under canonical default; return Task with requested tag
555+
user_task = Task(owner="default", repo="default", sha=None, tag=tag)
556+
canonical = self._canonicalize(user_task)
557+
ctx = copy.deepcopy(self.registry[canonical])
558+
return user_task, ctx
556559

557560
def get_lock(self) -> threading.Lock:
558561
return self._lock
@@ -622,23 +625,24 @@ def get(self, key: str | Task) -> DockerContext:
622625
2) owner/repo (canonical key, tag='pkg')
623626
3) default (canonical key, tag='pkg')
624627
"""
625-
# Keep the user's tag but look up under canonical keys
626-
user_task = self.parse_key(key) if isinstance(key, str) else key
627-
canonical = self._canonicalize(user_task)
628+
with self._lock:
629+
# Keep the user's tag but look up under canonical keys
630+
user_task = self.parse_key(key) if isinstance(key, str) else key
631+
canonical = self._canonicalize(user_task)
628632

629-
# exact match first (canonical)
630-
if canonical.sha is not None and canonical in self.registry:
631-
logger.debug(f"Found exact context for key '{user_task}' via '{canonical}'.")
632-
return self.registry[canonical]
633+
# exact match first (canonical)
634+
if canonical.sha is not None and canonical in self.registry:
635+
logger.debug(f"Found exact context for key '{user_task}' via '{canonical}'.")
636+
return self.registry[canonical]
633637

634-
# owner/repo base (canonical)
635-
base = Task(owner=canonical.owner, repo=canonical.repo, sha=None, tag="pkg")
636-
if base in self.registry:
637-
logger.debug(f"Found fallback context '{base}' for key '{user_task}'.")
638-
return self.registry[base]
638+
# owner/repo base (canonical)
639+
base = Task(owner=canonical.owner, repo=canonical.repo, sha=None, tag="pkg")
640+
if base in self.registry:
641+
logger.debug(f"Found fallback context '{base}' for key '{user_task}'.")
642+
return self.registry[base]
639643

640-
logger.info(f"No context found for key '{user_task}'. Using default context.")
641-
return self.registry[Task(owner="default", repo="default", sha=None, tag="pkg")]
644+
logger.info(f"No context found for key '{user_task}'. Using default context.")
645+
return self.registry[Task(owner="default", repo="default", sha=None, tag="pkg")]
642646

643647
def get_similar(self, key: str | Task) -> list[tuple[Task, DockerContext]]: # noqa: C901
644648
"""
@@ -649,56 +653,57 @@ def get_similar(self, key: str | Task) -> list[tuple[Task, DockerContext]]: # n
649653
sorted by |commit_date diff| if available, else by SHA
650654
3) base owner/repo — returned Tasks use the caller's tag
651655
"""
652-
user_task = self.parse_key(key) if isinstance(key, str) else key
653-
canonical = self._canonicalize(user_task)
656+
with self._lock:
657+
user_task = self.parse_key(key) if isinstance(key, str) else key
658+
canonical = self._canonicalize(user_task)
659+
660+
results: list[tuple[Task, DockerContext]] = []
661+
seen_canonical: set[Task] = set()
662+
663+
# 1) Exact match (if present)
664+
if canonical in self.registry:
665+
results.append((canonical.with_tag(user_task.tag), self.registry[canonical]))
666+
seen_canonical.add(canonical)
667+
668+
# 2) Other SHAs for same owner/repo (canonical keys in registry)
669+
candidates: list[tuple[Task, DockerContext]] = []
670+
for t, ctx in self.registry.items():
671+
if t in seen_canonical:
672+
continue
673+
if t.owner == canonical.owner and t.repo == canonical.repo and t.sha is not None:
674+
candidates.append((t, ctx))
675+
676+
has_valid_commit_date = (
677+
getattr(canonical, "sha", None) is not None and getattr(canonical, "commit_date", None) is not None
678+
)
679+
if has_valid_commit_date:
680+
681+
def _sort(item: tuple[Task, DockerContext]) -> tuple[float, str]:
682+
t, _ = item
683+
cand_cd = getattr(t, "commit_date", None)
684+
if cand_cd is None:
685+
return (float("inf"), str(t.sha))
686+
try:
687+
return (abs(canonical.commit_date - cand_cd), str(t.sha))
688+
except Exception:
689+
return (float("inf"), str(t.sha))
690+
691+
candidates.sort(key=_sort)
692+
else:
693+
candidates.sort(key=lambda item: str(item[0].sha))
654694

655-
results: list[tuple[Task, DockerContext]] = []
656-
seen_canonical: set[Task] = set()
695+
for t, ctx in candidates:
696+
if t not in seen_canonical:
697+
# Present with the user's tag for downstream execution behavior
698+
results.append((t.with_tag(user_task.tag), ctx))
699+
seen_canonical.add(t)
657700

658-
# 1) Exact match (if present)
659-
if canonical in self.registry:
660-
results.append((canonical.with_tag(user_task.tag), self.registry[canonical]))
661-
seen_canonical.add(canonical)
662-
663-
# 2) Other SHAs for same owner/repo (canonical keys in registry)
664-
candidates: list[tuple[Task, DockerContext]] = []
665-
for t, ctx in self.registry.items():
666-
if t in seen_canonical:
667-
continue
668-
if t.owner == canonical.owner and t.repo == canonical.repo and t.sha is not None:
669-
candidates.append((t, ctx))
670-
671-
has_valid_commit_date = (
672-
getattr(canonical, "sha", None) is not None and getattr(canonical, "commit_date", None) is not None
673-
)
674-
if has_valid_commit_date:
701+
# 3) Base owner/repo
702+
base = Task(owner=canonical.owner, repo=canonical.repo, sha=None, tag="pkg")
703+
if base in self.registry and base not in seen_canonical:
704+
results.append((base.with_tag(user_task.tag), self.registry[base]))
675705

676-
def _sort(item: tuple[Task, DockerContext]) -> tuple[float, str]:
677-
t, _ = item
678-
cand_cd = getattr(t, "commit_date", None)
679-
if cand_cd is None:
680-
return (float("inf"), str(t.sha))
681-
try:
682-
return (abs(canonical.commit_date - cand_cd), str(t.sha))
683-
except Exception:
684-
return (float("inf"), str(t.sha))
685-
686-
candidates.sort(key=_sort)
687-
else:
688-
candidates.sort(key=lambda item: str(item[0].sha))
689-
690-
for t, ctx in candidates:
691-
if t not in seen_canonical:
692-
# Present with the user's tag for downstream execution behavior
693-
results.append((t.with_tag(user_task.tag), ctx))
694-
seen_canonical.add(t)
695-
696-
# 3) Base owner/repo
697-
base = Task(owner=canonical.owner, repo=canonical.repo, sha=None, tag="pkg")
698-
if base in self.registry and base not in seen_canonical:
699-
results.append((base.with_tag(user_task.tag), self.registry[base]))
700-
701-
return results
706+
return results
702707

703708
def __getitem__(self, key: str) -> DockerContext:
704709
return self.get(key)

0 commit comments

Comments
 (0)