mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-10-14 02:32:47 +03:00
CI / AMD64 Build (push) Canceled after 0s
CI / ARM Build (push) Canceled after 0s
CI / Jetson Jetpack 6 (push) Canceled after 0s
CI / AMD64 Extra Build (push) Canceled after 0s
CI / ARM Extra Build (push) Canceled after 0s
CI / Synaptics Build (push) Canceled after 0s
CI / Assemble and push default build (push) Canceled after 0s
* lock zmq sockets shared across threads Several zmq sockets were used from more than one thread at a time, which zmq does not allow. In the embeddings process, GenAI review and object description threads shared one requestor, so overlapping calls silently dropped the second write and could eventually leave a thread waiting forever on a reply that had already arrived, after which nothing was saved until restart. The API's embeddings requestor had the same problem, concurrent config publishes could mix the topic of one update with the payload of another, and the webpush config subscribers were drained from every publishing thread, which could abort the process. Each socket now has a lock. The embeddings requestor lock does not wait, so an overlapping call still returns empty right away instead of stalling the API behind a slow reply. * add test
90 lines
2.8 KiB
Python
90 lines
2.8 KiB
Python
"""Facilitates communication between processes."""
|
|
|
|
import logging
|
|
import multiprocessing as mp
|
|
import threading
|
|
from collections.abc import Callable
|
|
from multiprocessing.synchronize import Event as MpEvent
|
|
from typing import Any
|
|
|
|
import zmq
|
|
|
|
from frigate.comms.base_communicator import Communicator
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
SOCKET_REP_REQ = "ipc:///tmp/cache/comms"
|
|
|
|
|
|
class InterProcessCommunicator(Communicator):
|
|
def __init__(self) -> None:
|
|
self.context = zmq.Context()
|
|
self.socket = self.context.socket(zmq.REP)
|
|
self.socket.bind(SOCKET_REP_REQ)
|
|
self.stop_event: MpEvent = mp.Event()
|
|
|
|
def publish(self, topic: str, payload: Any, retain: bool = False) -> None:
|
|
"""There is no communication back to the processes."""
|
|
pass
|
|
|
|
def subscribe(self, receiver: Callable) -> None:
|
|
self._dispatcher = receiver
|
|
self.reader_thread = threading.Thread(target=self.read)
|
|
self.reader_thread.start()
|
|
|
|
def read(self) -> None:
|
|
while not self.stop_event.is_set():
|
|
while True: # load all messages that are queued
|
|
has_message, _, _ = zmq.select([self.socket], [], [], 1)
|
|
|
|
if not has_message:
|
|
break
|
|
|
|
try:
|
|
raw = self.socket.recv_json(flags=zmq.NOBLOCK)
|
|
|
|
if isinstance(raw, list):
|
|
(topic, value) = raw
|
|
response = self._dispatcher(topic, value)
|
|
else:
|
|
logging.warning(
|
|
f"Received unexpected data type in ZMQ recv_json: {type(raw)}"
|
|
)
|
|
response = None
|
|
|
|
if response is not None:
|
|
self.socket.send_json(response)
|
|
else:
|
|
self.socket.send_json([])
|
|
except zmq.ZMQError:
|
|
break
|
|
|
|
def stop(self) -> None:
|
|
self.stop_event.set()
|
|
self.reader_thread.join()
|
|
self.socket.close(linger=0)
|
|
self.context.destroy(linger=0)
|
|
|
|
|
|
class InterProcessRequestor:
|
|
"""Simplifies sending data to InterProcessCommunicator and getting a reply."""
|
|
|
|
def __init__(self) -> None:
|
|
self.context = zmq.Context()
|
|
self.socket = self.context.socket(zmq.REQ)
|
|
self.socket.connect(SOCKET_REP_REQ)
|
|
self.lock = threading.Lock()
|
|
|
|
def send_data(self, topic: str, data: Any) -> Any:
|
|
"""Sends data and then waits for reply."""
|
|
with self.lock:
|
|
try:
|
|
self.socket.send_json((topic, data))
|
|
return self.socket.recv_json()
|
|
except zmq.ZMQError:
|
|
return ""
|
|
|
|
def stop(self) -> None:
|
|
self.socket.close(linger=0)
|
|
self.context.destroy(linger=0)
|