From 397be6236df3351b9336a6e600a830e05a6fffd1 Mon Sep 17 00:00:00 2001 From: Petr Polezhaev Date: Tue, 29 Sep 2026 11:55:36 +0300 Subject: [PATCH] =?UTF-8?q?ha(H1):=20=D0=BD=D0=B8=D1=82=D1=8B=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=B2=D1=82=D0=BE=D1=80=D0=BD=D0=BE=D0=B3=D0=BE=20=D1=80?= =?UTF-8?q?=D0=B5=D0=B2=D1=8C=D1=8E=20=E2=80=94=20=D0=B3=D0=B0=D1=80=D0=B0?= =?UTF-8?q?=D0=BD=D1=82=D0=B8=D1=8F=20destroy=20=D0=BF=D1=80=D0=B8=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=B2=D1=82=D0=BE=D1=80=D0=BD=D0=BE=D0=BC=20close,?= =?UTF-8?q?=20rate-limit=20warn?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - close() из другого потока после close из reader-колбэка теперь джойнит reader (гарантия, что destroy завершён к возврату); - предупреждения о переполнении очередей не чаще 1/30 с; - events_dropped сохраняет последнее значение после close; - find_library выбирает .so по версии (а не по длине имени). --- pyfglair/_cffi.py | 9 +++++++-- pyfglair/session.py | 34 ++++++++++++++++++++++------------ 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/pyfglair/_cffi.py b/pyfglair/_cffi.py index 3a13c6e..26a6298 100644 --- a/pyfglair/_cffi.py +++ b/pyfglair/_cffi.py @@ -206,12 +206,17 @@ def find_library() -> str: return env pkg = Path(__file__).resolve().parent pattern = re.compile(r"^libfgl-aircon\.(so(\.[0-9.]+)?|dylib|\d+\.dylib)$") + + def version_key(path: Path) -> tuple[int, ...]: + match = re.search(r"\.so\.(\d+(?:\.\d+)*)$", path.name) + return tuple(int(part) for part in match.group(1).split(".")) if match else () + candidates = sorted( (p for p in pkg.iterdir() if p.is_file() and pattern.match(p.name)), - key=lambda p: (p.name.count("."), len(p.name)), + key=version_key, ) if candidates: - return str(candidates[0]) + return str(candidates[-1]) system = ctypes.util.find_library("fgl-aircon") if system: return system diff --git a/pyfglair/session.py b/pyfglair/session.py index a0c4526..2abec03 100644 --- a/pyfglair/session.py +++ b/pyfglair/session.py @@ -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