f1ab1958cec7cf4f14056692907e7518aa67273b hiram Thu Apr 30 10:25:36 2026 -0700 use flock to ensure only one instance of this script is ever running at one time refs #31811 diff --git src/hg/utils/otto/userRequests/ottoRequestPush.py src/hg/utils/otto/userRequests/ottoRequestPush.py index 699ccf08279..0842decfecd 100755 --- src/hg/utils/otto/userRequests/ottoRequestPush.py +++ src/hg/utils/otto/userRequests/ottoRequestPush.py @@ -1,35 +1,51 @@ #!/usr/bin/env python3 """ ottoRequestPush.py - group fromDb/toDb identifiers from pending push requests (status=5) by clade. Output: dict[clade] -> sorted list of assembly identifiers, where each identifier is "<gcAccession>_<asmName>" for GenArk accessions, or the plain UCSC db name for native dbs. """ +import fcntl import os import re import subprocess import sys from collections import defaultdict scriptDir = os.path.dirname(os.path.abspath(__file__)) cladeTsv = os.path.join(scriptDir, "dbDb.name.clade.tsv") +lockPath = os.path.join(scriptDir, "ottoRequestPush.lock") gcPattern = re.compile(r"^GC[AF]_") + +def acquireSingletonLock(): + """Ensure only one instance of this script runs at a time. Holds an + exclusive flock on lockPath for the lifetime of the process; the + kernel releases it on exit (including crash / kill -9), so no stale + lock cleanup is needed. Returns the open file handle, which the + caller must keep alive.""" + fh = open(lockPath, "w") + try: + fcntl.flock(fh, fcntl.LOCK_EX | fcntl.LOCK_NB) + except BlockingIOError: + sys.exit(0) + return fh + def hgsql(query, db="hgcentraltest"): """Run hgsql -N -B and return rows as list of tuples (tab-split).""" out = subprocess.run( ["hgsql", "-N", "-B", "-e", query, db], check=True, capture_output=True, text=True, ).stdout return [tuple(line.split("\t")) for line in out.splitlines() if line] def loadDbDbClades(): """Read dbDb.name.clade.tsv -> {dbName: clade}.""" result = {} with open(cladeTsv) as fh: for line in fh: if line.startswith("#") or not line.strip(): @@ -143,30 +159,31 @@ written. Returns True on success, False if any step fails (the chain stops at the first failure).""" for cmd in makeChainCommands: print("# [%s] %s" % (cladeDir, cmd), file=sys.stderr) result = subprocess.run( cmd, shell=True, executable="/bin/bash", cwd=cladeDir, ) if result.returncode != 0: print("# ERROR: exit %d from: %s -- stopping chain" % (result.returncode, cmd), file=sys.stderr) return False return True def main(): + lockFh = acquireSingletonLock() # noqa: F841 -- keep ref alive requests = pendingRequests() if not requests: return dbs = set() for _, fromDb, toDb in requests: dbs.update((fromDb, toDb)) accessions = {db for db in dbs if gcPattern.match(db)} dbDbClades = loadDbDbClades() genarkInfo = lookupGenark(accessions) grouped = groupByClade(dbs, dbDbClades, genarkInfo) def cladeOf(db): if gcPattern.match(db): info = genarkInfo.get(db) return info[1] if info else None