refactor: replace threading with multiprocessing
All checks were successful
Build Worker Base and Application Images / check-base-changes (push) Successful in 10s
Build Worker Base and Application Images / build-base (push) Has been skipped
Build Worker Base and Application Images / build-docker (push) Successful in 2m52s
Build Worker Base and Application Images / deploy-stack (push) Successful in 8s
All checks were successful
Build Worker Base and Application Images / check-base-changes (push) Successful in 10s
Build Worker Base and Application Images / build-base (push) Has been skipped
Build Worker Base and Application Images / build-docker (push) Successful in 2m52s
Build Worker Base and Application Images / deploy-stack (push) Successful in 8s
This commit is contained in:
parent
e87ed4c056
commit
bfab574058
6 changed files with 682 additions and 58 deletions
|
@ -1,14 +1,38 @@
|
|||
"""
|
||||
Stream coordination and lifecycle management.
|
||||
Optimized for 1280x720@6fps RTSP and 2560x1440 HTTP snapshots.
|
||||
Supports both threading and multiprocessing modes for scalability.
|
||||
"""
|
||||
import logging
|
||||
import threading
|
||||
import time
|
||||
import os
|
||||
from typing import Dict, Set, Optional, List, Any
|
||||
from dataclasses import dataclass
|
||||
from collections import defaultdict
|
||||
|
||||
# Check if multiprocessing is enabled (default enabled with proper initialization)
|
||||
USE_MULTIPROCESSING = os.environ.get('USE_MULTIPROCESSING', 'true').lower() == 'true'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
if USE_MULTIPROCESSING:
|
||||
try:
|
||||
from .process_manager import RTSPProcessManager, ProcessConfig
|
||||
logger.info("Multiprocessing support enabled")
|
||||
_mp_loaded = True
|
||||
except ImportError as e:
|
||||
logger.warning(f"Failed to load multiprocessing support: {e}")
|
||||
USE_MULTIPROCESSING = False
|
||||
_mp_loaded = False
|
||||
except Exception as e:
|
||||
logger.warning(f"Multiprocessing initialization failed: {e}")
|
||||
USE_MULTIPROCESSING = False
|
||||
_mp_loaded = False
|
||||
else:
|
||||
logger.info("Multiprocessing support disabled (using threading mode)")
|
||||
_mp_loaded = False
|
||||
|
||||
from .readers import RTSPReader, HTTPSnapshotReader
|
||||
from .buffers import shared_cache_buffer, StreamType
|
||||
from ..tracking.integration import TrackingPipelineIntegration
|
||||
|
@ -50,6 +74,42 @@ class StreamManager:
|
|||
self._camera_subscribers: Dict[str, Set[str]] = defaultdict(set) # camera_id -> set of subscription_ids
|
||||
self._lock = threading.RLock()
|
||||
|
||||
# Initialize multiprocessing manager if enabled (lazy initialization)
|
||||
self.process_manager = None
|
||||
self._frame_getter_thread = None
|
||||
self._multiprocessing_enabled = USE_MULTIPROCESSING and _mp_loaded
|
||||
|
||||
if self._multiprocessing_enabled:
|
||||
logger.info(f"Multiprocessing support enabled, will initialize on first use")
|
||||
else:
|
||||
logger.info(f"Multiprocessing support disabled, using threading mode")
|
||||
|
||||
def _initialize_multiprocessing(self) -> bool:
|
||||
"""Lazily initialize multiprocessing manager when first needed."""
|
||||
if self.process_manager is not None:
|
||||
return True
|
||||
|
||||
if not self._multiprocessing_enabled:
|
||||
return False
|
||||
|
||||
try:
|
||||
self.process_manager = RTSPProcessManager(max_processes=min(self.max_streams, 15))
|
||||
# Start monitoring synchronously to ensure it's ready
|
||||
self.process_manager.start_monitoring()
|
||||
# Start frame getter thread
|
||||
self._frame_getter_thread = threading.Thread(
|
||||
target=self._multiprocess_frame_getter,
|
||||
daemon=True
|
||||
)
|
||||
self._frame_getter_thread.start()
|
||||
logger.info(f"Initialized multiprocessing manager with max {self.process_manager.max_processes} processes")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to initialize multiprocessing manager: {e}")
|
||||
self.process_manager = None
|
||||
self._multiprocessing_enabled = False # Disable for future attempts
|
||||
return False
|
||||
|
||||
def add_subscription(self, subscription_id: str, stream_config: StreamConfig,
|
||||
crop_coords: Optional[tuple] = None,
|
||||
model_id: Optional[str] = None,
|
||||
|
@ -129,7 +189,24 @@ class StreamManager:
|
|||
"""Start a stream for the given camera."""
|
||||
try:
|
||||
if stream_config.rtsp_url:
|
||||
# RTSP stream
|
||||
# Try multiprocessing for RTSP if enabled
|
||||
if self._multiprocessing_enabled and self._initialize_multiprocessing():
|
||||
config = ProcessConfig(
|
||||
camera_id=camera_id,
|
||||
rtsp_url=stream_config.rtsp_url,
|
||||
expected_fps=6,
|
||||
buffer_size=3,
|
||||
max_retries=stream_config.max_retries
|
||||
)
|
||||
success = self.process_manager.add_camera(config)
|
||||
if success:
|
||||
self._streams[camera_id] = 'multiprocessing' # Mark as multiprocessing stream
|
||||
logger.info(f"Started RTSP multiprocessing stream for camera {camera_id}")
|
||||
return True
|
||||
else:
|
||||
logger.warning(f"Failed to start multiprocessing stream for {camera_id}, falling back to threading")
|
||||
|
||||
# Fall back to threading mode for RTSP
|
||||
reader = RTSPReader(
|
||||
camera_id=camera_id,
|
||||
rtsp_url=stream_config.rtsp_url,
|
||||
|
@ -138,10 +215,10 @@ class StreamManager:
|
|||
reader.set_frame_callback(self._frame_callback)
|
||||
reader.start()
|
||||
self._streams[camera_id] = reader
|
||||
logger.info(f"Started RTSP stream for camera {camera_id}")
|
||||
logger.info(f"Started RTSP threading stream for camera {camera_id}")
|
||||
|
||||
elif stream_config.snapshot_url:
|
||||
# HTTP snapshot stream
|
||||
# HTTP snapshot stream (always use threading)
|
||||
reader = HTTPSnapshotReader(
|
||||
camera_id=camera_id,
|
||||
snapshot_url=stream_config.snapshot_url,
|
||||
|
@ -167,10 +244,18 @@ class StreamManager:
|
|||
"""Stop a stream for the given camera."""
|
||||
if camera_id in self._streams:
|
||||
try:
|
||||
self._streams[camera_id].stop()
|
||||
stream_obj = self._streams[camera_id]
|
||||
if stream_obj == 'multiprocessing' and self.process_manager:
|
||||
# Remove from multiprocessing manager
|
||||
self.process_manager.remove_camera(camera_id)
|
||||
logger.info(f"Stopped multiprocessing stream for camera {camera_id}")
|
||||
else:
|
||||
# Stop threading stream
|
||||
stream_obj.stop()
|
||||
logger.info(f"Stopped threading stream for camera {camera_id}")
|
||||
|
||||
del self._streams[camera_id]
|
||||
shared_cache_buffer.clear_camera(camera_id)
|
||||
logger.info(f"Stopped stream for camera {camera_id}")
|
||||
except Exception as e:
|
||||
logger.error(f"Error stopping stream for camera {camera_id}: {e}")
|
||||
|
||||
|
@ -190,6 +275,38 @@ class StreamManager:
|
|||
except Exception as e:
|
||||
logger.error(f"Error in frame callback for camera {camera_id}: {e}")
|
||||
|
||||
def _multiprocess_frame_getter(self):
|
||||
"""Background thread to get frames from multiprocessing manager."""
|
||||
if not self.process_manager:
|
||||
return
|
||||
|
||||
logger.info("Started multiprocessing frame getter thread")
|
||||
|
||||
while self.process_manager:
|
||||
try:
|
||||
# Get frames from all multiprocessing cameras
|
||||
with self._lock:
|
||||
mp_cameras = [cid for cid, s in self._streams.items() if s == 'multiprocessing']
|
||||
|
||||
for camera_id in mp_cameras:
|
||||
try:
|
||||
result = self.process_manager.get_frame(camera_id)
|
||||
if result:
|
||||
frame, timestamp = result
|
||||
# Detect stream type and store in cache
|
||||
stream_type = self._detect_stream_type(frame)
|
||||
shared_cache_buffer.put_frame(camera_id, frame, stream_type)
|
||||
# Process tracking
|
||||
self._process_tracking_for_camera(camera_id, frame)
|
||||
except Exception as e:
|
||||
logger.debug(f"Error getting frame for {camera_id}: {e}")
|
||||
|
||||
time.sleep(0.05) # 20 FPS polling rate
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in multiprocess frame getter: {e}")
|
||||
time.sleep(1.0)
|
||||
|
||||
def _process_tracking_for_camera(self, camera_id: str, frame):
|
||||
"""Process tracking for all subscriptions of a camera."""
|
||||
try:
|
||||
|
@ -362,6 +479,12 @@ class StreamManager:
|
|||
for camera_id in list(self._streams.keys()):
|
||||
self._stop_stream(camera_id)
|
||||
|
||||
# Stop multiprocessing manager if exists
|
||||
if self.process_manager:
|
||||
self.process_manager.stop_all()
|
||||
self.process_manager = None
|
||||
logger.info("Stopped multiprocessing manager")
|
||||
|
||||
# Clear all tracking
|
||||
self._subscriptions.clear()
|
||||
self._camera_subscribers.clear()
|
||||
|
@ -434,9 +557,12 @@ class StreamManager:
|
|||
# Add stream type information
|
||||
stream_types = {}
|
||||
for camera_id in self._streams.keys():
|
||||
if isinstance(self._streams[camera_id], RTSPReader):
|
||||
stream_types[camera_id] = 'rtsp'
|
||||
elif isinstance(self._streams[camera_id], HTTPSnapshotReader):
|
||||
stream_obj = self._streams[camera_id]
|
||||
if stream_obj == 'multiprocessing':
|
||||
stream_types[camera_id] = 'rtsp_multiprocessing'
|
||||
elif isinstance(stream_obj, RTSPReader):
|
||||
stream_types[camera_id] = 'rtsp_threading'
|
||||
elif isinstance(stream_obj, HTTPSnapshotReader):
|
||||
stream_types[camera_id] = 'http'
|
||||
else:
|
||||
stream_types[camera_id] = 'unknown'
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue