"""Трекер созданных инстансов — по юзеру и стенду: /tmp/instances-{client_id}-{stand}.json.""" import fcntl import json import os import time def _path(client_id, stand): return f"/tmp/instances-{client_id}-{stand}.json" def _acquire_lock(fd): """LOCK_EX с таймаутом 2 секунды (LOCK_NB + retry).""" deadline = time.time() + 2 while True: try: fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) return True except BlockingIOError: if time.time() >= deadline: return False time.sleep(0.05) def _locked_read(path): """Читает файл под эксклюзивной блокировкой, пустой dict если нет.""" try: fd = os.open(path, os.O_RDWR | os.O_CREAT, 0o644) except OSError: return {} try: if not _acquire_lock(fd): return {} try: data = os.read(fd, 65536) if data: return json.loads(data.decode("utf-8")) except (json.JSONDecodeError, UnicodeDecodeError): pass return {} finally: fcntl.flock(fd, fcntl.LOCK_UN) os.close(fd) def _locked_write(path, data): """Пишет файл под эксклюзивной блокировкой.""" try: fd = os.open(path, os.O_RDWR | os.O_CREAT, 0o644) except OSError: return try: if not _acquire_lock(fd): return os.lseek(fd, 0, 0) os.ftruncate(fd, 0) os.write(fd, json.dumps(data, indent=2).encode("utf-8")) finally: fcntl.flock(fd, fcntl.LOCK_UN) os.close(fd) def add(client_id, stand, instance_uid, svc_id, display_name): p = _path(client_id, stand) data = _locked_read(p) data[instance_uid] = { "svcId": svc_id, "displayName": display_name, "instanceUid": instance_uid, } _locked_write(p, data) def remove(client_id, stand, instance_uid): p = _path(client_id, stand) data = _locked_read(p) data.pop(instance_uid, None) _locked_write(p, data) def list_all(client_id, stand): return list(_locked_read(_path(client_id, stand)).values())