eigent/server/app/domains/trigger/service/trigger_schedule_task.py
Tong Chen 712f20a8fa
Some checks are pending
Pre-commit / pre-commit (push) Waiting to run
CodeQL Advanced / Analyze (actions) (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
CodeQL Advanced / Analyze (python) (push) Waiting to run
Test / Run Python Tests (push) Waiting to run
Feat: Server refactor v1 (#1509)
2026-03-24 18:05:52 +08:00

151 lines
6 KiB
Python

# ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
"""
Celery tasks for trigger scheduling: poll due triggers and check execution timeouts.
"""
import logging
from datetime import datetime, timezone
from celery import shared_task
from sqlmodel import select, or_
from app.core.database import session_make
from app.core.environment import env
from app.core.trigger_utils import MAX_DISPATCH_PER_TICK
from app.core.redis_utils import get_redis_manager
from app.model.trigger.trigger_execution import TriggerExecution
from app.model.trigger.trigger import Trigger
from app.shared.types.trigger_types import ExecutionStatus
from app.domains.trigger.service.trigger_schedule_service import TriggerScheduleService
from app.domains.trigger.service.trigger_service import TriggerService
EXECUTION_PENDING_TIMEOUT_SECONDS = int(env("EXECUTION_PENDING_TIMEOUT_SECONDS", "60"))
EXECUTION_RUNNING_TIMEOUT_SECONDS = int(env("EXECUTION_RUNNING_TIMEOUT_SECONDS", "600"))
logger = logging.getLogger("server_trigger_schedule_task")
@shared_task(queue="poll_trigger_schedules")
def poll_trigger_schedules() -> None:
"""Poll and execute scheduled triggers."""
logger.info("Starting poll_trigger_schedules task")
session = session_make()
try:
schedule_service = TriggerScheduleService(session)
schedule_service.poll_and_execute_due_triggers(
max_dispatch_per_tick=MAX_DISPATCH_PER_TICK
)
finally:
session.close()
@shared_task(queue="check_execution_timeouts")
def check_execution_timeouts() -> None:
"""Check for timed-out pending and running executions."""
logger.info("Starting check_execution_timeouts task", extra={
"pending_timeout": EXECUTION_PENDING_TIMEOUT_SECONDS,
"running_timeout": EXECUTION_RUNNING_TIMEOUT_SECONDS
})
session = session_make()
redis_manager = get_redis_manager()
trigger_service = TriggerService(session)
try:
now = datetime.now(timezone.utc)
executions = session.exec(
select(TriggerExecution).where(
or_(
TriggerExecution.status == ExecutionStatus.pending,
TriggerExecution.status == ExecutionStatus.running
)
)
).all()
timed_out_pending_count = 0
timed_out_running_count = 0
for execution in executions:
is_pending = execution.status == ExecutionStatus.pending
is_running = execution.status == ExecutionStatus.running
if is_pending:
reference_time = execution.created_at
timeout_seconds = EXECUTION_PENDING_TIMEOUT_SECONDS
else:
reference_time = execution.started_at or execution.created_at
timeout_seconds = EXECUTION_RUNNING_TIMEOUT_SECONDS
if reference_time.tzinfo is None:
reference_time = reference_time.replace(tzinfo=timezone.utc)
time_elapsed = (now - reference_time).total_seconds()
if time_elapsed > timeout_seconds:
if is_pending:
new_status = ExecutionStatus.missed
error_message = f"Execution acknowledgment timeout ({timeout_seconds} seconds)"
timed_out_pending_count += 1
else:
new_status = ExecutionStatus.failed
error_message = f"Execution running timeout ({timeout_seconds} seconds) - no completion received"
timed_out_running_count += 1
trigger_service.update_execution_status(
execution=execution,
status=new_status,
error_message=error_message
)
try:
trigger = session.get(Trigger, execution.trigger_id)
if trigger and trigger.user_id:
user_session_ids = redis_manager.get_user_sessions(trigger.user_id)
for session_id in user_session_ids:
redis_manager.remove_pending_execution(session_id, execution.execution_id)
except Exception as e:
logger.warning("Failed to remove execution from Redis", extra={
"execution_id": execution.execution_id,
"trigger_id": execution.trigger_id,
"error": str(e)
})
logger.info("Execution timed out", extra={
"execution_id": execution.execution_id,
"trigger_id": execution.trigger_id,
"original_status": "pending" if is_pending else "running",
"new_status": new_status.value,
"time_elapsed": time_elapsed
})
total_timed_out = timed_out_pending_count + timed_out_running_count
if total_timed_out > 0:
logger.info("Marked executions as timed out", extra={
"timed_out_pending_count": timed_out_pending_count,
"timed_out_running_count": timed_out_running_count,
"total_timed_out": total_timed_out
})
except Exception as e:
logger.error("Error checking execution timeouts", extra={
"error": str(e),
"error_type": type(e).__name__
}, exc_info=True)
session.rollback()
finally:
session.close()