36 lines
977 B
Python
36 lines
977 B
Python
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
import os
|
|||
|
|
import fcntl
|
|||
|
|
from typing import Optional
|
|||
|
|
|
|||
|
|
_LOCK_FD: Optional[int] = None # keep fd open for process lifetime
|
|||
|
|
|
|||
|
|
|
|||
|
|
def should_start_scheduler() -> bool:
|
|||
|
|
"""Return True in exactly one process (the scheduler owner).
|
|||
|
|
|
|||
|
|
Under gunicorn --workers N, code is imported in N processes. If APScheduler
|
|||
|
|
starts at import time, you get N schedulers => N× API calls.
|
|||
|
|
|
|||
|
|
We prevent that by taking a non-blocking exclusive lock on a lockfile.
|
|||
|
|
"""
|
|||
|
|
global _LOCK_FD
|
|||
|
|
if _LOCK_FD is not None:
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
lock_path = os.environ.get("LAIR_SCHED_LOCK", "/tmp/lair-scheduler.lock")
|
|||
|
|
fd = os.open(lock_path, os.O_CREAT | os.O_RDWR, 0o644)
|
|||
|
|
try:
|
|||
|
|
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|||
|
|
_LOCK_FD = fd
|
|||
|
|
return True
|
|||
|
|
except BlockingIOError:
|
|||
|
|
os.close(fd)
|
|||
|
|
return False
|
|||
|
|
except Exception:
|
|||
|
|
try:
|
|||
|
|
os.close(fd)
|
|||
|
|
finally:
|
|||
|
|
return False
|