83 lines
2.2 KiB
Python
83 lines
2.2 KiB
Python
"""Трекер созданных инстансов — по юзеру и стенду: /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())
|