49 lines
1.6 KiB
Python
49 lines
1.6 KiB
Python
"""同账套任务串行锁;独立持有 MySQL 连接,跨分页 commit 仍保持有效。"""
|
|
|
|
import hashlib
|
|
from contextlib import contextmanager
|
|
|
|
from sqlalchemy import text
|
|
from sqlalchemy.orm import Session
|
|
|
|
|
|
class TPlusBusyError(RuntimeError):
|
|
"""已有任务持有执行锁。"""
|
|
|
|
|
|
def lock_name(session: Session) -> str:
|
|
"""生成不含地址、账号或密码的固定长度锁名。"""
|
|
database = session.get_bind().url.database or "default"
|
|
return "ys:tplus:read:" + hashlib.sha256(database.encode()).hexdigest()[:24]
|
|
|
|
|
|
@contextmanager
|
|
def sync_lock(session: Session):
|
|
"""只在取得锁后执行;释放失败时使连接失效,防止带锁回池。"""
|
|
engine = session.get_bind()
|
|
if engine.dialect.name != "mysql":
|
|
raise RuntimeError("T+ 执行协调需要 MySQL 命名锁")
|
|
connection = engine.connect()
|
|
acquired = False
|
|
try:
|
|
acquired = (
|
|
connection.execute(
|
|
text("SELECT GET_LOCK(:name, 0)"), {"name": lock_name(session)}
|
|
).scalar_one()
|
|
== 1
|
|
)
|
|
if not acquired:
|
|
raise TPlusBusyError("已有 T+ 任务正在执行,请稍后重试")
|
|
yield
|
|
finally:
|
|
if acquired:
|
|
try:
|
|
released = connection.execute(
|
|
text("SELECT RELEASE_LOCK(:name)"), {"name": lock_name(session)}
|
|
).scalar_one()
|
|
if released != 1:
|
|
connection.invalidate()
|
|
except Exception:
|
|
connection.invalidate()
|
|
connection.close()
|