Files
edict/scripts/refresh_watcher.py
cft0808 74d8130391 feat: Week 0-4 optimizations - event bus, state machine, dispatch, outbox relay
- EventBus: Redis Streams pub/sub for decoupled service communication
- State machine: strict lifecycle transitions with audit logging
- Dispatch worker: parallel execution, retry with backoff, resource locking
- Orchestrator: DAG-based task decomposition and dependency resolution
- Outbox relay: transactional outbox pattern for reliable event delivery
- Auth: dashboard authentication module
- Agent groups: sansheng/liubu agent configuration
- CI/CD: Docker publish workflow, systemd service, start script
- Frontend: dashboard build assets
- Tests: state machine consistency tests
2026-04-04 12:16:32 +08:00

100 lines
3.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Refresh Watcher — 常驻进程监控信号文件debounce 后执行 refresh_live_data.py。
替代 kanban_update.py 中每次操作都 fork 子进程的方式。
多 Agent 并发时200 次 touch → 合并为 1 次 refresh。
运行方式:
python3 scripts/refresh_watcher.py
部署方式:
- systemd: 参见 edict.service
- docker-compose: 参见 edict/docker-compose.yml
- 手动前台: python3 scripts/refresh_watcher.py
"""
import logging
import os
import pathlib
import signal
import subprocess
import sys
import time
_BASE = pathlib.Path(os.environ.get('EDICT_HOME', '')).resolve() if os.environ.get('EDICT_HOME') else pathlib.Path(__file__).resolve().parent.parent
SIGNAL_FILE = _BASE / 'data' / '.refresh_pending'
PID_FILE = _BASE / 'data' / '.refresh_watcher_pid'
REFRESH_SCRIPT = _BASE / 'scripts' / 'refresh_live_data.py'
DEBOUNCE_SEC = 2 # 信号文件稳定 2 秒后才执行
POLL_INTERVAL = 0.5 # 检查间隔
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [refresh_watcher] %(message)s',
datefmt='%H:%M:%S',
)
log = logging.getLogger('refresh_watcher')
_running = True
def _shutdown(signum, frame):
global _running
_running = False
log.info(f'收到信号 {signum},准备退出')
def main():
# 写 PID 文件,让 kanban_update.py 知道 watcher 在运行
PID_FILE.parent.mkdir(parents=True, exist_ok=True)
PID_FILE.write_text(str(os.getpid()))
log.info(f'Refresh watcher started (pid={os.getpid()}, debounce={DEBOUNCE_SEC}s)')
signal.signal(signal.SIGTERM, _shutdown)
signal.signal(signal.SIGINT, _shutdown)
last_seen_mtime = 0.0
refresh_count = 0
try:
while _running:
try:
if SIGNAL_FILE.exists():
mtime = SIGNAL_FILE.stat().st_mtime
now = time.time()
# 信号文件存在且已稳定 DEBOUNCE_SEC 秒
if mtime > last_seen_mtime and (now - mtime) >= DEBOUNCE_SEC:
last_seen_mtime = mtime
# 删除信号文件(在执行前删,避免执行期间的新 touch 被吞)
try:
SIGNAL_FILE.unlink()
except FileNotFoundError:
pass
refresh_count += 1
log.info(f'🔄 执行 refresh #{refresh_count}')
try:
subprocess.run(
[sys.executable, str(REFRESH_SCRIPT)],
capture_output=True,
timeout=30,
)
except subprocess.TimeoutExpired:
log.warning('refresh_live_data.py 超时 (30s)')
except Exception as e:
log.error(f'refresh 执行失败: {e}')
except Exception as e:
log.error(f'Watcher loop error: {e}')
time.sleep(POLL_INTERVAL)
finally:
# 清理 PID 文件
try:
PID_FILE.unlink()
except Exception:
pass
log.info(f'Refresh watcher stopped (total refreshes: {refresh_count})')
if __name__ == '__main__':
main()