ha(H1): ниты повторного ревью — гарантия destroy при повторном close, rate-limit warn

- close() из другого потока после close из reader-колбэка теперь джойнит
  reader (гарантия, что destroy завершён к возврату);
- предупреждения о переполнении очередей не чаще 1/30 с;
- events_dropped сохраняет последнее значение после close;
- find_library выбирает .so по версии (а не по длине имени).
This commit is contained in:
2026-09-29 11:55:36 +03:00
parent b84bd8d555
commit 397be6236d
2 changed files with 29 additions and 14 deletions
+22 -12
View File
@@ -13,6 +13,7 @@ from __future__ import annotations
import asyncio
import logging
import threading
import time
from dataclasses import dataclass
from typing import Callable, Optional
@@ -68,12 +69,16 @@ class _LogPump(threading.Thread):
def run(self) -> None:
out = ffi.new("fgl_log_event_t[]", 8)
dropped = 0
warned_at = 0.0
while True:
total = int(lib.fgl_log_events_dropped())
if total > dropped:
_LOGGER.warning(
"переполнение очереди логов: потеряно %d", total - dropped
)
if time.monotonic() - warned_at > 30.0:
_LOGGER.warning(
"переполнение очереди логов: потеряно %d",
total - dropped,
)
warned_at = time.monotonic()
dropped = total
count = lib.fgl_log_wait_events(out, 8, 500)
handler = _GLOBAL_LOG
@@ -129,6 +134,7 @@ class _SessionReader(threading.Thread):
out = ffi.new("fgl_event_t[]", 16)
finalize = ffi.NULL
dropped = 0
warned_at = 0.0
try:
while True:
with self._session._lock:
@@ -143,11 +149,14 @@ class _SessionReader(threading.Thread):
if raw == ffi.NULL:
return
total = int(lib.fgl_session_events_dropped(raw))
self._session._dropped = total
if total > dropped:
_LOGGER.warning(
"переполнение очереди событий: потеряно %d",
total - dropped,
)
if time.monotonic() - warned_at > 30.0:
_LOGGER.warning(
"переполнение очереди событий: потеряно %d",
total - dropped,
)
warned_at = time.monotonic()
dropped = total
count = lib.fgl_session_wait_events(raw, out, 16, 250)
for i in range(count):
@@ -181,6 +190,7 @@ class Session:
self._loop = loop if loop is not None else _running_loop()
self._lock = threading.Lock()
self._closed = False
self._dropped = 0
self._cfg_strings: list = []
@@ -275,8 +285,9 @@ class Session:
"""Сколько событий потеряно из-за переполнения очереди (диагностика)."""
with self._lock:
if self._closed or self._session == ffi.NULL:
return 0
return int(lib.fgl_session_events_dropped(self._session))
return self._dropped
self._dropped = int(lib.fgl_session_events_dropped(self._session))
return self._dropped
@property
def config(self) -> Config:
@@ -296,11 +307,10 @@ class Session:
"""Останавливает сессию, джойнит reader и освобождает ядро.
Из колбэка в reader-потоке (Session без loop) возвращается сразу:
освобождение завершит сам reader. Повторный вызов — no-op.
освобождение завершит сам reader. Повторный вызов из другого потока
дожидается фактического освобождения (join идемпотентен).
"""
with self._lock:
if self._closed:
return
self._closed = True
if self._reader is threading.current_thread():
return