fix(scheduler): 修復 Gunicorn 4 workers 重複發送排程通知
Some checks failed
CD Pipeline / deploy (push) Has been cancelled

根因:APScheduler 在 openclaw_bot_routes.py 透過 record_once 啟動,
但 record_once 只防止同一 process 內重複;Gunicorn --workers 4 有
4 個獨立 worker process,各自啟動一個 scheduler,導致早報/晚報/Excel
每次觸發都送出 4 份。

修復:start_scheduler() 改用 fcntl.LOCK_EX|LOCK_NB 搶佔 /tmp/openclaw_scheduler.lock,
只有搶到鎖的 worker 啟動排程,其餘 3 個 worker 靜默跳過。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ogt
2026-04-22 09:21:24 +08:00
parent b11789db77
commit 28acdc19ae

View File

@@ -3300,9 +3300,32 @@ def send_daily_excel():
sys_log.error(f"[AutoExcel] {e}")
_sched_lock_fh = None # 持有鎖的 file handle進程退出時自動釋放
def start_scheduler():
"""啟動排程Flask app 啟動後呼叫)"""
global _scheduler
"""啟動排程Flask app 啟動後呼叫)
使用 fcntl exclusive lock 確保 Gunicorn 多 worker 環境下只有一個 worker 運行排程。
"""
global _scheduler, _sched_lock_fh
import fcntl, atexit
lock_path = '/tmp/openclaw_scheduler.lock'
try:
_sched_lock_fh = open(lock_path, 'w')
fcntl.flock(_sched_lock_fh.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
except OSError:
sys_log.info("[OpenClawBot] Scheduler lock busy — another worker owns it, skipping")
return
@atexit.register
def _release_sched_lock():
try:
fcntl.flock(_sched_lock_fh.fileno(), fcntl.LOCK_UN)
_sched_lock_fh.close()
except Exception:
pass
try:
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger
@@ -3315,7 +3338,7 @@ def start_scheduler():
_scheduler.add_job(send_weekly_report, CronTrigger(day_of_week='mon', hour=9, minute=0))
_scheduler.add_job(check_anomalies, CronTrigger(hour='9,12,15,18', minute=0))
_scheduler.start()
sys_log.info("[OpenClawBot] Scheduler started ✓ (competitor/morning/excel/evening/weekly/anomaly)")
sys_log.info("[OpenClawBot] Scheduler started ✓ pid=%d (competitor/morning/excel/evening/weekly/anomaly)", os.getpid())
except ImportError:
sys_log.warning("[OpenClawBot] APScheduler 未安裝 — 排程功能停用")
except Exception as e: