fix(scheduler): make scheduler init idempotent to prevent duplicate task runs

This commit is contained in:
zhayujie
2026-05-18 18:36:48 +08:00
parent 2720bba5b7
commit a85c5f9d4e
2 changed files with 87 additions and 58 deletions

View File

@@ -3,6 +3,7 @@ Integration module for scheduler with AgentBridge
""" """
import os import os
import threading
from typing import Optional from typing import Optional
from config import conf from config import conf
from common.log import logger from common.log import logger
@@ -13,20 +14,36 @@ from bridge.reply import Reply, ReplyType
# Global scheduler service instance # Global scheduler service instance
_scheduler_service = None _scheduler_service = None
_task_store = None _task_store = None
# Module-level lock to guard idempotent initialization across threads
_init_lock = threading.Lock()
def init_scheduler(agent_bridge) -> bool: def init_scheduler(agent_bridge) -> bool:
""" """
Initialize scheduler service Initialize scheduler service (idempotent).
Safe to call multiple times and from multiple threads: only the first
successful call creates the singleton ``SchedulerService`` + background
scanning thread. Subsequent calls return immediately.
Args: Args:
agent_bridge: AgentBridge instance agent_bridge: AgentBridge instance
Returns: Returns:
True if initialized successfully True if scheduler is initialized (newly created or already running)
""" """
global _scheduler_service, _task_store global _scheduler_service, _task_store
# Fast path: already initialized and running
if _scheduler_service is not None and getattr(_scheduler_service, "running", False):
return True
with _init_lock:
# Re-check under the lock to avoid races where multiple threads
# passed the fast-path check before any of them acquired the lock.
if _scheduler_service is not None and getattr(_scheduler_service, "running", False):
return True
try: try:
from agent.tools.scheduler.task_store import TaskStore from agent.tools.scheduler.task_store import TaskStore
from agent.tools.scheduler.scheduler_service import SchedulerService from agent.tools.scheduler.scheduler_service import SchedulerService
@@ -35,7 +52,8 @@ def init_scheduler(agent_bridge) -> bool:
workspace_root = expand_path(conf().get("agent_workspace", "~/cow")) workspace_root = expand_path(conf().get("agent_workspace", "~/cow"))
store_path = os.path.join(workspace_root, "scheduler", "tasks.json") store_path = os.path.join(workspace_root, "scheduler", "tasks.json")
# Create task store # Create task store (reuse if already created)
if _task_store is None:
_task_store = TaskStore(store_path) _task_store = TaskStore(store_path)
logger.debug(f"[Scheduler] Task store initialized: {store_path}") logger.debug(f"[Scheduler] Task store initialized: {store_path}")

View File

@@ -5,6 +5,7 @@ Agent Initializer - Handles agent initialization logic
import os import os
import asyncio import asyncio
import datetime import datetime
import threading
import time import time
from typing import Optional, List from typing import Optional, List
@@ -13,6 +14,9 @@ from agent.tools import ToolManager
from common.log import logger from common.log import logger
from common.utils import expand_path from common.utils import expand_path
# Module-level lock to serialize scheduler init across concurrent sessions
_scheduler_init_lock = threading.Lock()
class AgentInitializer: class AgentInitializer:
""" """
@@ -406,7 +410,14 @@ class AgentInitializer:
return tools return tools
def _initialize_scheduler(self, tools: List, session_id: Optional[str] = None): def _initialize_scheduler(self, tools: List, session_id: Optional[str] = None):
"""Initialize scheduler service if needed""" """Initialize scheduler service if needed.
Serialize the check-and-set under a module-level lock so concurrent
first-time session inits cannot each create a new SchedulerService
(which would leak background scanning threads).
"""
if not self.agent_bridge.scheduler_initialized:
with _scheduler_init_lock:
if not self.agent_bridge.scheduler_initialized: if not self.agent_bridge.scheduler_initialized:
try: try:
from agent.tools.scheduler.integration import init_scheduler from agent.tools.scheduler.integration import init_scheduler