mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-07-20 21:57:14 +08:00
fix(scheduler): make scheduler init idempotent to prevent duplicate task runs
This commit is contained in:
@@ -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}")
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user