feat: 完善模型平台相关功能
This commit is contained in:
+5
-2
@@ -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 ./
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user