# co-gahusb/app/locks.py from redis.exceptions import WatchError LOCK_PREFIX = "co:lock:" async def acquire_lock(r, resource, role, ttl_sec=300): key = LOCK_PREFIX + resource ok = await r.set(key, role, nx=True, ex=ttl_sec) if ok: return {"acquired": True} held_by = await r.get(key) ttl = await r.ttl(key) return {"acquired": False, "held_by": held_by, "ttl_remaining": max(ttl, 0)} async def release_lock(r, resource, role): key = LOCK_PREFIX + resource async with r.pipeline() as pipe: while True: try: await pipe.watch(key) owner = await pipe.get(key) if owner != role: await pipe.unwatch() return {"released": False, "held_by": owner} pipe.multi() pipe.delete(key) await pipe.execute() return {"released": True} except WatchError: continue async def heartbeat_lock(r, resource, role, ttl_sec=300): key = LOCK_PREFIX + resource async with r.pipeline() as pipe: while True: try: await pipe.watch(key) owner = await pipe.get(key) if owner != role: await pipe.unwatch() return {"renewed": False, "held_by": owner} pipe.multi() pipe.expire(key, ttl_sec) await pipe.execute() return {"renewed": True} except WatchError: continue async def list_locks(r): keys = await r.keys(LOCK_PREFIX + "*") out = [] for key in keys: held_by = await r.get(key) if held_by is None: continue ttl = await r.ttl(key) out.append({ "resource": key[len(LOCK_PREFIX):], "held_by": held_by, "ttl_remaining": max(ttl, 0), }) return {"locks": out}