From abd60d33feae41931f4a8071dd8fd05911a51f60 Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Wed, 23 Sep 2026 15:47:04 +0200 Subject: [PATCH] fix: evaluate automation schedules on Windows with PostgreSQL (#30424) Since 0.11.4, creating or editing an automation on Windows with PostgreSQL fails with a 400, and the scheduler logs NotImplementedError on every tick, so automations do not work at all on that setup. Schedules are now evaluated in a worker subprocess so a pathological rule can be killed after the 2s budget. On Windows with PostgreSQL, Open WebUI switches to the selector event loop that psycopg needs, and that loop cannot spawn subprocesses. When spawning fails there, the evaluation now reruns on a Proactor event loop in a worker thread. The subprocess, the 2s budget and the kill on timeout all stay the same, and the global loop policy psycopg depends on is untouched. Falling back to a plain thread was considered and rejected: a thread cannot be stopped, so a costly rule would keep burning CPU after the timeout. Verified with a loop that refuses subprocesses: base raises NotImplementedError, the fix returns the same results as base, still times out a pathological rule at 2s with the worker killed, and leaves no processes or loops behind under repeated and concurrent calls. Other platforms take the unchanged path. Fixes #30400 --- backend/open_webui/utils/recurrence.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/backend/open_webui/utils/recurrence.py b/backend/open_webui/utils/recurrence.py index d5c2b6b1f2..c4817eca07 100644 --- a/backend/open_webui/utils/recurrence.py +++ b/backend/open_webui/utils/recurrence.py @@ -1,11 +1,14 @@ """Recurrence calculations isolated from application/DB imports for worker processes.""" +import asyncio import logging from datetime import datetime, timedelta +from functools import partial from typing import Optional from zoneinfo import ZoneInfo -from anyio import fail_after, to_process +import anyio +from anyio import fail_after, to_process, to_thread from dateutil.rrule import HOURLY, MINUTELY, SECONDLY, rruleset, rrulestr from open_webui.constants import ERROR_MESSAGES @@ -101,6 +104,12 @@ async def _get_next_occurrences(s: str, now: datetime, n: int) -> list[datetime] return await to_process.run_sync(_next_occurrences, s, now, n, cancellable=True) except TimeoutError as e: raise RecurrenceEvaluationTimeout('Schedule took too long to evaluate; simplify its recurrence rule.') from e + except NotImplementedError: + # Windows' SelectorEventLoop (required by psycopg) cannot spawn subprocesses. + run_on_proactor_loop = partial( + anyio.run, _get_next_occurrences, s, now, n, backend_options={'loop_factory': asyncio.ProactorEventLoop} + ) + return await to_thread.run_sync(run_on_proactor_loop) async def validate_rrule(s: str, tz: str = None) -> None: