MCPcopy Create free account
hub / github.com/OpenDCAI/Paper2Any / AsyncInterProcessSemaphore

Class AsyncInterProcessSemaphore

fastapi_app/interprocess_lock.py:23–70  ·  view source on GitHub ↗

A small file-lock based semaphore that works across uvicorn workers.

Source from the content-addressed store, hash-verified

21
22
23class AsyncInterProcessSemaphore:
24 """A small file-lock based semaphore that works across uvicorn workers."""
25
26 def __init__(self, name: str, limit: int = 1, poll_interval: float = 0.2) -> None:
27 safe_name = re.sub(r"[^A-Za-z0-9._-]+", "_", (name or "").strip()).strip("._")
28 self._name = safe_name or "lock"
29 self._limit = max(1, int(limit))
30 self._poll_interval = max(0.05, float(poll_interval))
31
32 def _slot_path(self, index: int) -> Path:
33 return LOCK_ROOT / f"{self._name}.{index}.lock"
34
35 def _try_acquire_once(self) -> _HeldLockSlot | None:
36 LOCK_ROOT.mkdir(parents=True, exist_ok=True)
37 for index in range(self._limit):
38 fd = os.open(self._slot_path(index), os.O_CREAT | os.O_RDWR, 0o666)
39 try:
40 fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
41 except BlockingIOError:
42 os.close(fd)
43 continue
44
45 os.ftruncate(fd, 0)
46 os.write(fd, f"pid={os.getpid()} slot={index}\n".encode("utf-8"))
47 return _HeldLockSlot(fd=fd)
48 return None
49
50 async def acquire(self) -> _HeldLockSlot:
51 while True:
52 held = self._try_acquire_once()
53 if held is not None:
54 return held
55 await asyncio.sleep(self._poll_interval)
56
57 @staticmethod
58 def release(held: _HeldLockSlot) -> None:
59 try:
60 fcntl.flock(held.fd, fcntl.LOCK_UN)
61 finally:
62 os.close(held.fd)
63
64 @asynccontextmanager
65 async def hold(self) -> AsyncIterator[None]:
66 held = await self.acquire()
67 try:
68 yield
69 finally:
70 self.release(held)

Calls

no outgoing calls

Tested by

no test coverage detected