v1.0.53: file-based tracker with fcntl.flock (multi-worker gunicorn fix)

This commit is contained in:
2026-07-27 10:08:40 +04:00
parent d79c78e530
commit f546f41f21
2 changed files with 59 additions and 15 deletions
+58 -14
View File
@@ -1,9 +1,11 @@
"""Трекер созданных инстансов — in-memory dict (один gunicorn worker)."""
import threading
"""Трекер созданных инстансов — файловый с fcntl.flock (multi-worker gunicorn)."""
import fcntl
import json
import os
_LOCK = threading.Lock()
_PATH = "/tmp/instances.json"
_data = {
_INITIAL = {
"408b7f96-6ed7-4985-be1b-f5bdcb6ab44d": {"svcId": 1, "displayName": "autotest-1", "instanceUid": "408b7f96-6ed7-4985-be1b-f5bdcb6ab44d"},
"884356cf-5212-4395-b56b-27c58a5fc1fa": {"svcId": 1, "displayName": "autotest-1-first", "instanceUid": "884356cf-5212-4395-b56b-27c58a5fc1fa"},
"6528854d-a4ea-428c-9fa4-68e85fa9b3ac": {"svcId": 1, "displayName": "curl-test-144834", "instanceUid": "6528854d-a4ea-428c-9fa4-68e85fa9b3ac"},
@@ -11,20 +13,62 @@ _data = {
}
def _locked_read():
"""Читает файл под эксклюзивной блокировкой, seed при отсутствии."""
try:
fd = os.open(_PATH, os.O_RDWR | os.O_CREAT, 0o644)
except OSError:
return dict(_INITIAL)
try:
fcntl.flock(fd, fcntl.LOCK_EX)
try:
data = os.read(fd, 65536)
if data:
return json.loads(data.decode("utf-8"))
except (json.JSONDecodeError, UnicodeDecodeError):
pass
# Файл пуст или битый — пишем _INITIAL
data = dict(_INITIAL)
os.lseek(fd, 0, 0)
os.ftruncate(fd, 0)
os.write(fd, json.dumps(data, indent=2).encode("utf-8"))
return data
finally:
fcntl.flock(fd, fcntl.LOCK_UN)
os.close(fd)
def _locked_write(data):
"""Пишет файл под эксклюзивной блокировкой."""
try:
fd = os.open(_PATH, os.O_RDWR | os.O_CREAT, 0o644)
except OSError:
return
try:
fcntl.flock(fd, fcntl.LOCK_EX)
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(instance_uid, svc_id, display_name):
with _LOCK:
_data[instance_uid] = {
"svcId": svc_id,
"displayName": display_name,
"instanceUid": instance_uid,
}
data = _locked_read()
data[instance_uid] = {
"svcId": svc_id,
"displayName": display_name,
"instanceUid": instance_uid,
}
_locked_write(data)
def remove(instance_uid):
with _LOCK:
_data.pop(instance_uid, None)
data = _locked_read()
data.pop(instance_uid, None)
_locked_write(data)
def list_all():
with _LOCK:
return list(_data.values())
return list(_locked_read().values())