feat: 完善模型平台相关功能

This commit is contained in:
Winnie
2026-07-31 19:10:37 +08:00
parent 86988bdaef
commit 49ee2c0a4a
24 changed files with 529 additions and 125 deletions
+5 -2
View File
@@ -1,6 +1,9 @@
FROM python:3.12-slim-bookworm
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 PYTHONPATH=/app
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PYTHONPATH=/app \
PATH="/app/.venv/bin:${PATH}"
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
@@ -10,7 +13,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
g++ \
python3-dev \
build-essential \
&& rm -rf /var/lib/apt/lists/* \
&& rm -rf /var/lib/apt/lists/*
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
COPY pyproject.toml uv.lock ./
+24 -2
View File
@@ -33,6 +33,20 @@ from schedule.context import naive_utc
LOGGER = logging.getLogger(__name__)
# APScheduler's persistent SQLAlchemy job store pickles each job. A bound
# ``CronScheduler`` method captures this instance (including SQLAlchemy engine
# state) and therefore cannot be pickled. Keep the persisted callable at
# module scope and resolve the process-local callback when the job fires.
_ACTIVE_TRIGGER: Callable[[str], Awaitable[None]] | None = None
async def dispatch_persisted_cron(schedule_id: str) -> None:
"""Dispatch one persisted cron tick through the active service."""
callback = _ACTIVE_TRIGGER
if callback is None:
raise RuntimeError("cron trigger callback is not initialized")
await callback(schedule_id)
class CronScheduler:
"""Manages cron triggers in APScheduler, backed by a MySQL jobstore."""
@@ -44,12 +58,15 @@ class CronScheduler:
database_url: str,
on_trigger: Callable[[str], Awaitable[None]],
) -> None:
global _ACTIVE_TRIGGER
self.session_factory = session_factory
self.scheduler = AsyncIOScheduler(
jobstores={"default": build_sqlalchemy_jobstore(database_url)},
timezone=UTC,
)
self._on_trigger = on_trigger
_ACTIVE_TRIGGER = on_trigger
self._sync_task: asyncio.Task[None] | None = None
def start(self) -> None:
@@ -62,6 +79,8 @@ class CronScheduler:
async def close(self) -> None:
"""Cancel the sync loop and shut APScheduler down."""
global _ACTIVE_TRIGGER
if self._sync_task is not None:
self._sync_task.cancel()
try:
@@ -71,11 +90,14 @@ class CronScheduler:
self._sync_task = None
if self.scheduler.running:
self.scheduler.shutdown(wait=False)
if _ACTIVE_TRIGGER is self._on_trigger:
_ACTIVE_TRIGGER = None
async def trigger(self, schedule_id: str) -> None:
"""APScheduler cron tick callback.
Wired via ``add_job(self.trigger, args=[schedule_id], ...)``. The
The persisted job calls :func:`dispatch_persisted_cron`, which then
resolves this process-local callback. The
standard on_trigger is ``SchedulerService.trigger_schedule`` which
posts back to Backend; Backend then writes the Outbox row that the
orchestrator picks up.
@@ -125,7 +147,7 @@ class CronScheduler:
self.scheduler.reschedule_job(job_id, trigger=trigger)
else:
self.scheduler.add_job(
self.trigger,
dispatch_persisted_cron,
trigger=trigger,
args=[item.schedule_id],
id=job_id,
+2 -2
View File
@@ -372,14 +372,14 @@ class NodeExecutor:
return log_id, result_id, upload_error
@staticmethod
def _start_inbox(
async def _start_inbox(
session,
*,
consumer_name: str,
event_id: str,
message_id: str,
) -> tuple[ConsumerInbox, bool]:
item = session.get(
item = await session.get(
ConsumerInbox,
(consumer_name, event_id),
with_for_update=True,