mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +01:00 
			
		
		
		
	graceful exit of subprocesses
This commit is contained in:
		
							parent
							
								
									4e0cf3681e
								
							
						
					
					
						commit
						d62aec7287
					
				| @ -5,6 +5,7 @@ import multiprocessing as mp | |||||||
| import os | import os | ||||||
| import queue | import queue | ||||||
| import threading | import threading | ||||||
|  | import signal | ||||||
| from abc import ABC, abstractmethod | from abc import ABC, abstractmethod | ||||||
| from multiprocessing.connection import Connection | from multiprocessing.connection import Connection | ||||||
| from typing import Dict | from typing import Dict | ||||||
| @ -109,6 +110,14 @@ def run_detector(name: str, detection_queue: mp.Queue, out_events: Dict[str, mp. | |||||||
|     threading.current_thread().name = f"detector:{name}" |     threading.current_thread().name = f"detector:{name}" | ||||||
|     logging.info(f"Starting detection process: {os.getpid()}") |     logging.info(f"Starting detection process: {os.getpid()}") | ||||||
|     listen() |     listen() | ||||||
|  | 
 | ||||||
|  |     stop_event = mp.Event() | ||||||
|  |     def receiveSignal(signalNumber, frame): | ||||||
|  |         stop_event.set() | ||||||
|  |      | ||||||
|  |     signal.signal(signal.SIGTERM, receiveSignal) | ||||||
|  |     signal.signal(signal.SIGINT, receiveSignal) | ||||||
|  | 
 | ||||||
|     frame_manager = SharedMemoryFrameManager() |     frame_manager = SharedMemoryFrameManager() | ||||||
|     object_detector = LocalObjectDetector(tf_device=tf_device) |     object_detector = LocalObjectDetector(tf_device=tf_device) | ||||||
| 
 | 
 | ||||||
| @ -122,7 +131,13 @@ def run_detector(name: str, detection_queue: mp.Queue, out_events: Dict[str, mp. | |||||||
|         } |         } | ||||||
|      |      | ||||||
|     while True: |     while True: | ||||||
|         connection_id = detection_queue.get() |         if stop_event.is_set(): | ||||||
|  |             break | ||||||
|  | 
 | ||||||
|  |         try: | ||||||
|  |             connection_id = detection_queue.get(timeout=5) | ||||||
|  |         except queue.Empty: | ||||||
|  |             continue | ||||||
|         input_frame = frame_manager.get(connection_id, (1,300,300,3)) |         input_frame = frame_manager.get(connection_id, (1,300,300,3)) | ||||||
| 
 | 
 | ||||||
|         if input_frame is None: |         if input_frame is None: | ||||||
|  | |||||||
| @ -1,6 +1,8 @@ | |||||||
| # adapted from https://medium.com/@jonathonbao/python3-logging-with-multiprocessing-f51f460b8778 | # adapted from https://medium.com/@jonathonbao/python3-logging-with-multiprocessing-f51f460b8778 | ||||||
| import logging | import logging | ||||||
| import threading | import threading | ||||||
|  | import signal | ||||||
|  | import multiprocessing as mp | ||||||
| from logging import handlers | from logging import handlers | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -19,9 +21,21 @@ def root_configurer(queue): | |||||||
|     root.setLevel(logging.INFO) |     root.setLevel(logging.INFO) | ||||||
| 
 | 
 | ||||||
| def log_process(queue): | def log_process(queue): | ||||||
|  |     stop_event = mp.Event() | ||||||
|  |     def receiveSignal(signalNumber, frame): | ||||||
|  |         stop_event.set() | ||||||
|  |      | ||||||
|  |     signal.signal(signal.SIGTERM, receiveSignal) | ||||||
|  |     signal.signal(signal.SIGINT, receiveSignal) | ||||||
|  | 
 | ||||||
|     threading.current_thread().name = f"logger" |     threading.current_thread().name = f"logger" | ||||||
|     listener_configurer() |     listener_configurer() | ||||||
|     while True: |     while True: | ||||||
|         record = queue.get() |         if stop_event.is_set() and queue.empty(): | ||||||
|  |             break | ||||||
|  |         try: | ||||||
|  |             record = queue.get(timeout=5) | ||||||
|  |         except queue.Empty: | ||||||
|  |             continue | ||||||
|         logger = logging.getLogger(record.name) |         logger = logging.getLogger(record.name) | ||||||
|         logger.handle(record) |         logger.handle(record) | ||||||
|  | |||||||
| @ -9,6 +9,7 @@ import multiprocessing as mp | |||||||
| import os | import os | ||||||
| import queue | import queue | ||||||
| import subprocess as sp | import subprocess as sp | ||||||
|  | import signal | ||||||
| import threading | import threading | ||||||
| import time | import time | ||||||
| from collections import defaultdict | from collections import defaultdict | ||||||
| @ -72,18 +73,21 @@ def create_tensor_input(frame, region): | |||||||
|     # Expand dimensions since the model expects images to have shape: [1, 300, 300, 3] |     # Expand dimensions since the model expects images to have shape: [1, 300, 300, 3] | ||||||
|     return np.expand_dims(cropped_frame, axis=0) |     return np.expand_dims(cropped_frame, axis=0) | ||||||
| 
 | 
 | ||||||
|  | def stop_ffmpeg(ffmpeg_process): | ||||||
|  |     logger.info("Terminating the existing ffmpeg process...") | ||||||
|  |     ffmpeg_process.terminate() | ||||||
|  |     try: | ||||||
|  |         logger.info("Waiting for ffmpeg to exit gracefully...") | ||||||
|  |         ffmpeg_process.communicate(timeout=30) | ||||||
|  |     except sp.TimeoutExpired: | ||||||
|  |         logger.info("FFmpeg didnt exit. Force killing...") | ||||||
|  |         ffmpeg_process.kill() | ||||||
|  |         ffmpeg_process.communicate() | ||||||
|  |     ffmpeg_process = None | ||||||
|  | 
 | ||||||
| def start_or_restart_ffmpeg(ffmpeg_cmd, frame_size=None, ffmpeg_process=None): | def start_or_restart_ffmpeg(ffmpeg_cmd, frame_size=None, ffmpeg_process=None): | ||||||
|     if not ffmpeg_process is None: |     if not ffmpeg_process is None: | ||||||
|         logger.info("Terminating the existing ffmpeg process...") |         stop_ffmpeg(ffmpeg_process) | ||||||
|         ffmpeg_process.terminate() |  | ||||||
|         try: |  | ||||||
|             logger.info("Waiting for ffmpeg to exit gracefully...") |  | ||||||
|             ffmpeg_process.communicate(timeout=30) |  | ||||||
|         except sp.TimeoutExpired: |  | ||||||
|             logger.info("FFmpeg didnt exit. Force killing...") |  | ||||||
|             ffmpeg_process.kill() |  | ||||||
|             ffmpeg_process.communicate() |  | ||||||
|         ffmpeg_process = None |  | ||||||
| 
 | 
 | ||||||
|     if frame_size is None: |     if frame_size is None: | ||||||
|         process = sp.Popen(ffmpeg_cmd, stdout = sp.DEVNULL, stdin = sp.DEVNULL, start_new_session=True) |         process = sp.Popen(ffmpeg_cmd, stdout = sp.DEVNULL, stdin = sp.DEVNULL, start_new_session=True) | ||||||
| @ -133,7 +137,7 @@ def capture_frames(ffmpeg_process, camera_name, frame_shape, frame_manager: Fram | |||||||
|         frame_queue.put(current_frame.value) |         frame_queue.put(current_frame.value) | ||||||
| 
 | 
 | ||||||
| class CameraWatchdog(threading.Thread): | class CameraWatchdog(threading.Thread): | ||||||
|     def __init__(self, camera_name, config, frame_queue, camera_fps, ffmpeg_pid): |     def __init__(self, camera_name, config, frame_queue, camera_fps, ffmpeg_pid, stop_event): | ||||||
|         threading.Thread.__init__(self) |         threading.Thread.__init__(self) | ||||||
|         self.name = f"watchdog:{camera_name}" |         self.name = f"watchdog:{camera_name}" | ||||||
|         self.camera_name = camera_name |         self.camera_name = camera_name | ||||||
| @ -146,6 +150,7 @@ class CameraWatchdog(threading.Thread): | |||||||
|         self.frame_queue = frame_queue |         self.frame_queue = frame_queue | ||||||
|         self.frame_shape = self.config.frame_shape_yuv |         self.frame_shape = self.config.frame_shape_yuv | ||||||
|         self.frame_size = self.frame_shape[0] * self.frame_shape[1] |         self.frame_size = self.frame_shape[0] * self.frame_shape[1] | ||||||
|  |         self.stop_event = stop_event | ||||||
| 
 | 
 | ||||||
|     def run(self): |     def run(self): | ||||||
|         self.start_ffmpeg_detect() |         self.start_ffmpeg_detect() | ||||||
| @ -160,6 +165,12 @@ class CameraWatchdog(threading.Thread): | |||||||
|          |          | ||||||
|         time.sleep(10) |         time.sleep(10) | ||||||
|         while True: |         while True: | ||||||
|  |             if self.stop_event.is_set(): | ||||||
|  |                 stop_ffmpeg(self.ffmpeg_detect_process) | ||||||
|  |                 for p in self.ffmpeg_other_processes: | ||||||
|  |                     stop_ffmpeg(p['process']) | ||||||
|  |                 break | ||||||
|  | 
 | ||||||
|             now = datetime.datetime.now().timestamp() |             now = datetime.datetime.now().timestamp() | ||||||
| 
 | 
 | ||||||
|             if not self.capture_thread.is_alive(): |             if not self.capture_thread.is_alive(): | ||||||
| @ -212,12 +223,26 @@ class CameraCapture(threading.Thread): | |||||||
|             self.fps, self.skipped_fps, self.current_frame) |             self.fps, self.skipped_fps, self.current_frame) | ||||||
| 
 | 
 | ||||||
| def capture_camera(name, config: CameraConfig, process_info): | def capture_camera(name, config: CameraConfig, process_info): | ||||||
|  |     stop_event = mp.Event() | ||||||
|  |     def receiveSignal(signalNumber, frame): | ||||||
|  |         stop_event.set() | ||||||
|  |      | ||||||
|  |     signal.signal(signal.SIGTERM, receiveSignal) | ||||||
|  |     signal.signal(signal.SIGINT, receiveSignal) | ||||||
|  | 
 | ||||||
|     frame_queue = process_info['frame_queue'] |     frame_queue = process_info['frame_queue'] | ||||||
|     camera_watchdog = CameraWatchdog(name, config, frame_queue, process_info['camera_fps'], process_info['ffmpeg_pid']) |     camera_watchdog = CameraWatchdog(name, config, frame_queue, process_info['camera_fps'], process_info['ffmpeg_pid'], stop_event) | ||||||
|     camera_watchdog.start() |     camera_watchdog.start() | ||||||
|     camera_watchdog.join() |     camera_watchdog.join() | ||||||
| 
 | 
 | ||||||
| def track_camera(name, config: CameraConfig, detection_queue, result_connection, detected_objects_queue, process_info): | def track_camera(name, config: CameraConfig, detection_queue, result_connection, detected_objects_queue, process_info): | ||||||
|  |     stop_event = mp.Event() | ||||||
|  |     def receiveSignal(signalNumber, frame): | ||||||
|  |         stop_event.set() | ||||||
|  |      | ||||||
|  |     signal.signal(signal.SIGTERM, receiveSignal) | ||||||
|  |     signal.signal(signal.SIGINT, receiveSignal) | ||||||
|  | 
 | ||||||
|     threading.current_thread().name = f"process:{name}" |     threading.current_thread().name = f"process:{name}" | ||||||
|     listen() |     listen() | ||||||
| 
 | 
 | ||||||
| @ -236,7 +261,7 @@ def track_camera(name, config: CameraConfig, detection_queue, result_connection, | |||||||
|     frame_manager = SharedMemoryFrameManager() |     frame_manager = SharedMemoryFrameManager() | ||||||
| 
 | 
 | ||||||
|     process_frames(name, frame_queue, frame_shape, frame_manager, motion_detector, object_detector, |     process_frames(name, frame_queue, frame_shape, frame_manager, motion_detector, object_detector, | ||||||
|         object_tracker, detected_objects_queue, process_info, objects_to_track, object_filters, mask) |         object_tracker, detected_objects_queue, process_info, objects_to_track, object_filters, mask, stop_event) | ||||||
| 
 | 
 | ||||||
|     logger.info(f"{name}: exiting subprocess") |     logger.info(f"{name}: exiting subprocess") | ||||||
| 
 | 
 | ||||||
| @ -273,7 +298,7 @@ def process_frames(camera_name: str, frame_queue: mp.Queue, frame_shape, | |||||||
|     frame_manager: FrameManager, motion_detector: MotionDetector,  |     frame_manager: FrameManager, motion_detector: MotionDetector,  | ||||||
|     object_detector: RemoteObjectDetector, object_tracker: ObjectTracker, |     object_detector: RemoteObjectDetector, object_tracker: ObjectTracker, | ||||||
|     detected_objects_queue: mp.Queue, process_info: Dict, |     detected_objects_queue: mp.Queue, process_info: Dict, | ||||||
|     objects_to_track: List[str], object_filters, mask, |     objects_to_track: List[str], object_filters, mask, stop_event, | ||||||
|     exit_on_empty: bool = False): |     exit_on_empty: bool = False): | ||||||
|      |      | ||||||
|     fps = process_info['process_fps'] |     fps = process_info['process_fps'] | ||||||
| @ -284,6 +309,9 @@ def process_frames(camera_name: str, frame_queue: mp.Queue, frame_shape, | |||||||
|     fps_tracker.start() |     fps_tracker.start() | ||||||
| 
 | 
 | ||||||
|     while True: |     while True: | ||||||
|  |         if stop_event.is_set(): | ||||||
|  |             break | ||||||
|  | 
 | ||||||
|         if exit_on_empty and frame_queue.empty(): |         if exit_on_empty and frame_queue.empty(): | ||||||
|             logger.info(f"Exiting track_objects...") |             logger.info(f"Exiting track_objects...") | ||||||
|             break |             break | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user