Files
microsoft--agent-lightning/agentlightning/store/threading.py
T
wehub-resource-sync 85742ab165
Deploy Documentation / deploy (push) Has been cancelled
CPU Test / Test (Utilities, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (LLM proxy, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Others, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Store, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Utilities, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Weave, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (AgentOps, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (LLM proxy, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Others, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Weave, latest, Python 3.13) (push) Has been cancelled
Dashboard / Chromatic (push) Has been cancelled
CPU Test / Lint - fast (push) Has been cancelled
CPU Test / Lint - next (push) Has been cancelled
CPU Test / Lint - slow (push) Has been cancelled
CPU Test / Lint - JavaScript (push) Has been cancelled
CPU Test / Build documentation (push) Has been cancelled
CPU Test / Test (AgentOps, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (LLM proxy, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (Others, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (Store, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (Weave, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (AgentOps, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Store, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Utilities, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Weave, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (AgentOps, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (LLM proxy, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (Others, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (Store, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (Utilities, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (JavaScript) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:44:17 +08:00

340 lines
12 KiB
Python

# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import threading
from typing import Any, Dict, List, Literal, Optional, Sequence, Tuple
from opentelemetry.sdk.trace import ReadableSpan
from agentlightning.types import (
Attempt,
AttemptedRollout,
AttemptStatus,
EnqueueRolloutRequest,
NamedResources,
ResourcesUpdate,
Rollout,
RolloutConfig,
RolloutStatus,
Span,
TaskInput,
Worker,
WorkerStatus,
)
from .base import UNSET, LightningStore, LightningStoreCapabilities, LightningStoreStatistics, Unset
class LightningStoreThreaded(LightningStore):
"""Facade that delegates all store operations to a underlying store instance.
The operations are guaranteed to be thread-safe.
Make sure the threaded stores are instantiated before initializing the threads.
"""
def __init__(self, store: LightningStore) -> None:
super().__init__() # watchdog relies on the underlying store
self.store = store
self._lock = threading.Lock()
@property
def capabilities(self) -> LightningStoreCapabilities:
"""Return the capabilities of the store."""
capabilities = self.store.capabilities
return {
**capabilities,
"async_safe": True,
"thread_safe": True,
}
async def statistics(self) -> LightningStoreStatistics:
"""Return the statistics of the store."""
with self._lock:
return await self.store.statistics()
async def start_rollout(
self,
input: TaskInput,
mode: Literal["train", "val", "test"] | None = None,
resources_id: str | None = None,
config: RolloutConfig | None = None,
metadata: Dict[str, Any] | None = None,
worker_id: Optional[str] = None,
) -> AttemptedRollout:
with self._lock:
return await self.store.start_rollout(
input,
mode,
resources_id,
config,
metadata,
worker_id,
)
async def enqueue_rollout(
self,
input: TaskInput,
mode: Literal["train", "val", "test"] | None = None,
resources_id: str | None = None,
config: RolloutConfig | None = None,
metadata: Dict[str, Any] | None = None,
) -> Rollout:
with self._lock:
return await self.store.enqueue_rollout(input, mode, resources_id, config, metadata)
async def enqueue_many_rollouts(self, rollouts: Sequence[EnqueueRolloutRequest]) -> Sequence[Rollout]:
with self._lock:
return await self.store.enqueue_many_rollouts(rollouts)
async def dequeue_rollout(self, worker_id: Optional[str] = None) -> Optional[AttemptedRollout]:
with self._lock:
return await self.store.dequeue_rollout(worker_id=worker_id)
async def dequeue_many_rollouts(
self,
*,
limit: int = 1,
worker_id: Optional[str] = None,
) -> Sequence[AttemptedRollout]:
with self._lock:
return await self.store.dequeue_many_rollouts(limit=limit, worker_id=worker_id)
async def start_attempt(self, rollout_id: str, worker_id: Optional[str] = None) -> AttemptedRollout:
with self._lock:
return await self.store.start_attempt(rollout_id, worker_id)
async def query_rollouts(
self,
*,
status_in: Optional[Sequence[RolloutStatus]] = None,
rollout_id_in: Optional[Sequence[str]] = None,
rollout_id_contains: Optional[str] = None,
filter_logic: Literal["and", "or"] = "and",
sort_by: Optional[str] = None,
sort_order: Literal["asc", "desc"] = "asc",
limit: int = -1,
offset: int = 0,
status: Optional[Sequence[RolloutStatus]] = None,
rollout_ids: Optional[Sequence[str]] = None,
) -> Sequence[Rollout]:
with self._lock:
return await self.store.query_rollouts(
status_in=status_in,
rollout_id_in=rollout_id_in,
rollout_id_contains=rollout_id_contains,
filter_logic=filter_logic,
sort_by=sort_by,
sort_order=sort_order,
limit=limit,
offset=offset,
status=status,
rollout_ids=rollout_ids,
)
async def query_attempts(
self,
rollout_id: str,
*,
sort_by: Optional[str] = "sequence_id",
sort_order: Literal["asc", "desc"] = "asc",
limit: int = -1,
offset: int = 0,
) -> Sequence[Attempt]:
with self._lock:
return await self.store.query_attempts(
rollout_id,
sort_by=sort_by,
sort_order=sort_order,
limit=limit,
offset=offset,
)
async def get_rollout_by_id(self, rollout_id: str) -> Optional[Rollout]:
with self._lock:
return await self.store.get_rollout_by_id(rollout_id)
async def get_latest_attempt(self, rollout_id: str) -> Optional[Attempt]:
with self._lock:
return await self.store.get_latest_attempt(rollout_id)
async def query_resources(
self,
*,
resources_id: Optional[str] = None,
resources_id_contains: Optional[str] = None,
sort_by: Optional[str] = None,
sort_order: Literal["asc", "desc"] = "asc",
limit: int = -1,
offset: int = 0,
) -> Sequence[ResourcesUpdate]:
with self._lock:
return await self.store.query_resources(
resources_id=resources_id,
resources_id_contains=resources_id_contains,
sort_by=sort_by,
sort_order=sort_order,
limit=limit,
offset=offset,
)
async def add_resources(self, resources: NamedResources) -> ResourcesUpdate:
with self._lock:
return await self.store.add_resources(resources)
async def update_resources(self, resources_id: str, resources: NamedResources) -> ResourcesUpdate:
with self._lock:
return await self.store.update_resources(resources_id, resources)
async def get_resources_by_id(self, resources_id: str) -> Optional[ResourcesUpdate]:
with self._lock:
return await self.store.get_resources_by_id(resources_id)
async def get_latest_resources(self) -> Optional[ResourcesUpdate]:
with self._lock:
return await self.store.get_latest_resources()
async def add_many_spans(self, spans: Sequence[Span]) -> Sequence[Span]:
with self._lock:
return await self.store.add_many_spans(spans)
async def add_span(self, span: Span) -> Optional[Span]:
with self._lock:
return await self.store.add_span(span)
async def add_otel_span(
self,
rollout_id: str,
attempt_id: str,
readable_span: ReadableSpan,
sequence_id: int | None = None,
) -> Optional[Span]:
with self._lock:
return await self.store.add_otel_span(rollout_id, attempt_id, readable_span, sequence_id)
async def wait_for_rollouts(self, *, rollout_ids: List[str], timeout: Optional[float] = None) -> List[Rollout]:
# This method does not change the state of the store, and it's not thread-safe.
return await self.store.wait_for_rollouts(rollout_ids=rollout_ids, timeout=timeout)
async def get_next_span_sequence_id(self, rollout_id: str, attempt_id: str) -> int:
with self._lock:
return await self.store.get_next_span_sequence_id(rollout_id, attempt_id)
async def get_many_span_sequence_ids(self, rollout_attempt_ids: Sequence[Tuple[str, str]]) -> Sequence[int]:
with self._lock:
return await self.store.get_many_span_sequence_ids(rollout_attempt_ids)
async def query_spans(
self,
rollout_id: str,
attempt_id: str | Literal["latest"] | None = None,
*,
trace_id: Optional[str] = None,
trace_id_contains: Optional[str] = None,
span_id: Optional[str] = None,
span_id_contains: Optional[str] = None,
parent_id: Optional[str] = None,
parent_id_contains: Optional[str] = None,
name: Optional[str] = None,
name_contains: Optional[str] = None,
filter_logic: Literal["and", "or"] = "and",
limit: int = -1,
offset: int = 0,
sort_by: Optional[str] = "sequence_id",
sort_order: Literal["asc", "desc"] = "asc",
) -> Sequence[Span]:
with self._lock:
return await self.store.query_spans(
rollout_id,
attempt_id,
trace_id=trace_id,
trace_id_contains=trace_id_contains,
span_id=span_id,
span_id_contains=span_id_contains,
parent_id=parent_id,
parent_id_contains=parent_id_contains,
name=name,
name_contains=name_contains,
filter_logic=filter_logic,
limit=limit,
offset=offset,
sort_by=sort_by,
sort_order=sort_order,
)
async def update_rollout(
self,
rollout_id: str,
input: TaskInput | Unset = UNSET,
mode: Optional[Literal["train", "val", "test"]] | Unset = UNSET,
resources_id: Optional[str] | Unset = UNSET,
status: RolloutStatus | Unset = UNSET,
config: RolloutConfig | Unset = UNSET,
metadata: Optional[Dict[str, Any]] | Unset = UNSET,
) -> Rollout:
with self._lock:
return await self.store.update_rollout(
rollout_id=rollout_id,
input=input,
mode=mode,
resources_id=resources_id,
status=status,
config=config,
metadata=metadata,
)
async def update_attempt(
self,
rollout_id: str,
attempt_id: str | Literal["latest"],
status: AttemptStatus | Unset = UNSET,
worker_id: str | Unset = UNSET,
last_heartbeat_time: float | Unset = UNSET,
metadata: Optional[Dict[str, Any]] | Unset = UNSET,
) -> Attempt:
with self._lock:
return await self.store.update_attempt(
rollout_id=rollout_id,
attempt_id=attempt_id,
status=status,
worker_id=worker_id,
last_heartbeat_time=last_heartbeat_time,
metadata=metadata,
)
async def query_workers(
self,
*,
status_in: Optional[Sequence[WorkerStatus]] = None,
worker_id_contains: Optional[str] = None,
filter_logic: Literal["and", "or"] = "and",
sort_by: Optional[str] = None,
sort_order: Literal["asc", "desc"] = "asc",
limit: int = -1,
offset: int = 0,
) -> Sequence[Worker]:
with self._lock:
return await self.store.query_workers(
status_in=status_in,
worker_id_contains=worker_id_contains,
sort_by=sort_by,
sort_order=sort_order,
limit=limit,
offset=offset,
)
async def get_worker_by_id(self, worker_id: str) -> Optional[Worker]:
with self._lock:
return await self.store.get_worker_by_id(worker_id)
async def update_worker(
self,
worker_id: str,
heartbeat_stats: Dict[str, Any] | Unset = UNSET,
) -> Worker:
with self._lock:
return await self.store.update_worker(
worker_id=worker_id,
heartbeat_stats=heartbeat_stats,
)