front/xhs-agent-desktop/engine/scripts/run_lock.py
zzh 5a26ae0048 feat: 小红书模块迁移、登录重构与 xhs-agent-desktop 桌面 Agent
- 新增小红书前端模块(启动服务、内容分析、爆款创作、发布记录、技能卡片、拟人设置、AI 助手)

- 新增 xhs-agent-desktop 独立桌面 Agent,对接 OMS 任务调度

- 登录页重构,整合微信登录与 OMS JWT 认证

- 订单、渠道、用户中心等页面与 API 增强

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-30 16:13:13 +08:00

90 lines
2.7 KiB
Python
Raw Permalink 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.

"""单实例锁,防止多个进程同时操作浏览器。"""
from __future__ import annotations
import contextlib
import logging
import os
import time
logger = logging.getLogger(__name__)
_DEFAULT_LOCK_FILE = os.path.join(os.path.expanduser("~"), ".xhs", "run.lock")
class RunLock:
"""文件锁,确保同一时间只有一个进程在操作。"""
def __init__(self, lock_file: str = _DEFAULT_LOCK_FILE) -> None:
self.lock_file = lock_file
self._fd: int | None = None
def acquire(self, timeout: float = 30.0) -> bool:
"""获取锁。
Args:
timeout: 超时时间(秒)。
Returns:
True 获取成功,False 超时。
"""
os.makedirs(os.path.dirname(self.lock_file), exist_ok=True)
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
try:
self._fd = os.open(
self.lock_file,
os.O_CREAT | os.O_EXCL | os.O_WRONLY,
)
# 写入 PID
os.write(self._fd, str(os.getpid()).encode())
logger.debug("获取锁成功: %s", self.lock_file)
return True
except FileExistsError:
# 检查持有者是否还活着
if self._is_stale():
self._force_release()
continue
time.sleep(1)
logger.warning("获取锁超时: %s", self.lock_file)
return False
def release(self) -> None:
"""释放锁。"""
if self._fd is not None:
with contextlib.suppress(OSError):
os.close(self._fd)
self._fd = None
with contextlib.suppress(FileNotFoundError):
os.remove(self.lock_file)
logger.debug("释放锁: %s", self.lock_file)
def _is_stale(self) -> bool:
"""检查锁文件是否已过时(持有进程已退出)。"""
try:
with open(self.lock_file) as f:
pid = int(f.read().strip())
# 检查进程是否存在
os.kill(pid, 0)
return False
except (ValueError, OSError):
return True
def _force_release(self) -> None:
"""强制释放过时的锁。"""
with contextlib.suppress(FileNotFoundError):
os.remove(self.lock_file)
logger.info("强制释放过时锁: %s", self.lock_file)
def __enter__(self) -> RunLock:
if not self.acquire():
raise TimeoutError(f"无法获取锁: {self.lock_file}")
return self
def __exit__(self, *args: object) -> None:
self.release()