ensure frigate can exit gracefully

This commit is contained in:
Blake Blackshear 2020-08-02 08:46:36 -05:00
parent e5048f98b6
commit adcfe699c2
6 changed files with 68 additions and 17 deletions

View File

@ -1,4 +1,5 @@
import os import os
import signal
import sys import sys
import traceback import traceback
import signal import signal
@ -71,13 +72,14 @@ def start_plasma_store():
return plasma_process return plasma_process
class CameraWatchdog(threading.Thread): class CameraWatchdog(threading.Thread):
def __init__(self, camera_processes, config, tflite_process, tracked_objects_queue, plasma_process): def __init__(self, camera_processes, config, tflite_process, tracked_objects_queue, plasma_process, stop_event):
threading.Thread.__init__(self) threading.Thread.__init__(self)
self.camera_processes = camera_processes self.camera_processes = camera_processes
self.config = config self.config = config
self.tflite_process = tflite_process self.tflite_process = tflite_process
self.tracked_objects_queue = tracked_objects_queue self.tracked_objects_queue = tracked_objects_queue
self.plasma_process = plasma_process self.plasma_process = plasma_process
self.stop_event = stop_event
def run(self): def run(self):
time.sleep(10) time.sleep(10)
@ -85,6 +87,10 @@ class CameraWatchdog(threading.Thread):
# wait a bit before checking # wait a bit before checking
time.sleep(10) time.sleep(10)
if self.stop_event.is_set():
print(f"Exiting watchdog...")
break
now = datetime.datetime.now().timestamp() now = datetime.datetime.now().timestamp()
# check the plasma process # check the plasma process
@ -125,7 +131,7 @@ class CameraWatchdog(threading.Thread):
frame_size = frame_shape[0] * frame_shape[1] * frame_shape[2] frame_size = frame_shape[0] * frame_shape[1] * frame_shape[2]
ffmpeg_process = start_or_restart_ffmpeg(camera_process['ffmpeg_cmd'], frame_size) ffmpeg_process = start_or_restart_ffmpeg(camera_process['ffmpeg_cmd'], frame_size)
camera_capture = CameraCapture(name, ffmpeg_process, frame_shape, camera_process['frame_queue'], camera_capture = CameraCapture(name, ffmpeg_process, frame_shape, camera_process['frame_queue'],
camera_process['take_frame'], camera_process['camera_fps'], camera_process['detection_frame']) camera_process['take_frame'], camera_process['camera_fps'], camera_process['detection_frame'], self.stop_event)
camera_capture.start() camera_capture.start()
camera_process['ffmpeg_process'] = ffmpeg_process camera_process['ffmpeg_process'] = ffmpeg_process
camera_process['capture_thread'] = camera_capture camera_process['capture_thread'] = camera_capture
@ -142,6 +148,7 @@ class CameraWatchdog(threading.Thread):
ffmpeg_process.communicate() ffmpeg_process.communicate()
def main(): def main():
stop_event = threading.Event()
# connect to mqtt and setup last will # connect to mqtt and setup last will
def on_connect(client, userdata, flags, rc): def on_connect(client, userdata, flags, rc):
print("On connect called") print("On connect called")
@ -176,7 +183,7 @@ def main():
} }
# Queue for cameras to push tracked objects to # Queue for cameras to push tracked objects to
tracked_objects_queue = mp.SimpleQueue() tracked_objects_queue = mp.Queue()
# Queue for clip processing # Queue for clip processing
event_queue = mp.Queue() event_queue = mp.Queue()
@ -232,10 +239,10 @@ def main():
detection_frame = mp.Value('d', 0.0) detection_frame = mp.Value('d', 0.0)
ffmpeg_process = start_or_restart_ffmpeg(ffmpeg_cmd, frame_size) ffmpeg_process = start_or_restart_ffmpeg(ffmpeg_cmd, frame_size)
frame_queue = mp.SimpleQueue() frame_queue = mp.Queue()
camera_fps = EventsPerSecond() camera_fps = EventsPerSecond()
camera_fps.start() camera_fps.start()
camera_capture = CameraCapture(name, ffmpeg_process, frame_shape, frame_queue, take_frame, camera_fps, detection_frame) camera_capture = CameraCapture(name, ffmpeg_process, frame_shape, frame_queue, take_frame, camera_fps, detection_frame, stop_event)
camera_capture.start() camera_capture.start()
camera_processes[name] = { camera_processes[name] = {
@ -263,15 +270,31 @@ def main():
camera_process['process'].start() camera_process['process'].start()
print(f"Camera_process started for {name}: {camera_process['process'].pid}") print(f"Camera_process started for {name}: {camera_process['process'].pid}")
event_processor = EventProcessor(CONFIG['cameras'], camera_processes, '/cache', '/clips', event_queue) event_processor = EventProcessor(CONFIG['cameras'], camera_processes, '/cache', '/clips', event_queue, stop_event)
event_processor.start() event_processor.start()
object_processor = TrackedObjectProcessor(CONFIG['cameras'], CONFIG.get('zones', {}), client, MQTT_TOPIC_PREFIX, tracked_objects_queue, event_queue) object_processor = TrackedObjectProcessor(CONFIG['cameras'], CONFIG.get('zones', {}), client, MQTT_TOPIC_PREFIX, tracked_objects_queue, event_queue,stop_event)
object_processor.start() object_processor.start()
camera_watchdog = CameraWatchdog(camera_processes, CONFIG['cameras'], tflite_process, tracked_objects_queue, plasma_process) camera_watchdog = CameraWatchdog(camera_processes, CONFIG['cameras'], tflite_process, tracked_objects_queue, plasma_process, stop_event)
camera_watchdog.start() camera_watchdog.start()
def receiveSignal(signalNumber, frame):
print('Received:', signalNumber)
stop_event.set()
event_processor.join()
object_processor.join()
camera_watchdog.join()
for name, camera_process in camera_processes.items():
camera_process['capture_thread'].join()
rc = camera_watchdog.plasma_process.poll()
if rc == None:
camera_watchdog.plasma_process.terminate()
sys.exit()
signal.signal(signal.SIGTERM, receiveSignal)
signal.signal(signal.SIGINT, receiveSignal)
# create a flask app that encodes frames a mjpeg on demand # create a flask app that encodes frames a mjpeg on demand
app = Flask(__name__) app = Flask(__name__)
log = logging.getLogger('werkzeug') log = logging.getLogger('werkzeug')

View File

@ -87,7 +87,7 @@ def run_detector(detection_queue, avg_speed, start):
class EdgeTPUProcess(): class EdgeTPUProcess():
def __init__(self): def __init__(self):
self.detection_queue = mp.SimpleQueue() self.detection_queue = mp.Queue()
self.avg_inference_speed = mp.Value('d', 0.01) self.avg_inference_speed = mp.Value('d', 0.01)
self.detection_start = mp.Value('d', 0.0) self.detection_start = mp.Value('d', 0.0)
self.detect_process = None self.detect_process = None

View File

@ -9,7 +9,7 @@ import subprocess as sp
import queue import queue
class EventProcessor(threading.Thread): class EventProcessor(threading.Thread):
def __init__(self, config, camera_processes, cache_dir, clip_dir, event_queue): def __init__(self, config, camera_processes, cache_dir, clip_dir, event_queue, stop_event):
threading.Thread.__init__(self) threading.Thread.__init__(self)
self.config = config self.config = config
self.camera_processes = camera_processes self.camera_processes = camera_processes
@ -18,6 +18,7 @@ class EventProcessor(threading.Thread):
self.cached_clips = {} self.cached_clips = {}
self.event_queue = event_queue self.event_queue = event_queue
self.events_in_process = {} self.events_in_process = {}
self.stop_event = stop_event
def refresh_cache(self): def refresh_cache(self):
cached_files = os.listdir(self.cache_dir) cached_files = os.listdir(self.cache_dir)
@ -133,6 +134,10 @@ class EventProcessor(threading.Thread):
def run(self): def run(self):
while True: while True:
if self.stop_event.is_set():
print(f"Exiting event processor...")
break
try: try:
event_type, camera, event_data = self.event_queue.get(timeout=10) event_type, camera, event_data = self.event_queue.get(timeout=10)
except queue.Empty: except queue.Empty:

View File

@ -5,6 +5,7 @@ import time
import copy import copy
import cv2 import cv2
import threading import threading
import queue
import numpy as np import numpy as np
from collections import Counter, defaultdict from collections import Counter, defaultdict
import itertools import itertools
@ -51,7 +52,7 @@ def zone_filtered(obj, object_config):
return False return False
class TrackedObjectProcessor(threading.Thread): class TrackedObjectProcessor(threading.Thread):
def __init__(self, camera_config, zone_config, client, topic_prefix, tracked_objects_queue, event_queue): def __init__(self, camera_config, zone_config, client, topic_prefix, tracked_objects_queue, event_queue, stop_event):
threading.Thread.__init__(self) threading.Thread.__init__(self)
self.camera_config = camera_config self.camera_config = camera_config
self.zone_config = zone_config self.zone_config = zone_config
@ -59,6 +60,7 @@ class TrackedObjectProcessor(threading.Thread):
self.topic_prefix = topic_prefix self.topic_prefix = topic_prefix
self.tracked_objects_queue = tracked_objects_queue self.tracked_objects_queue = tracked_objects_queue
self.event_queue = event_queue self.event_queue = event_queue
self.stop_event = stop_event
self.camera_data = defaultdict(lambda: { self.camera_data = defaultdict(lambda: {
'best_objects': {}, 'best_objects': {},
'object_status': defaultdict(lambda: defaultdict(lambda: 'OFF')), 'object_status': defaultdict(lambda: defaultdict(lambda: 'OFF')),
@ -89,7 +91,7 @@ class TrackedObjectProcessor(threading.Thread):
for i, zone in enumerate(self.zone_data.values()): for i, zone in enumerate(self.zone_data.values()):
zone['color'] = tuple(int(round(255 * c)) for c in colors(i)[:3]) zone['color'] = tuple(int(round(255 * c)) for c in colors(i)[:3])
self.plasma_client = PlasmaManager() self.plasma_client = PlasmaManager(self.stop_event)
def get_best(self, camera, label): def get_best(self, camera, label):
if label in self.camera_data[camera]['best_objects']: if label in self.camera_data[camera]['best_objects']:
@ -102,7 +104,14 @@ class TrackedObjectProcessor(threading.Thread):
def run(self): def run(self):
while True: while True:
camera, frame_time, current_tracked_objects = self.tracked_objects_queue.get() if self.stop_event.is_set():
print(f"Exiting event processor...")
break
try:
camera, frame_time, current_tracked_objects = self.tracked_objects_queue.get(True, 10)
except queue.Empty:
continue
camera_config = self.camera_config[camera] camera_config = self.camera_config[camera]
best_objects = self.camera_data[camera]['best_objects'] best_objects = self.camera_data[camera]['best_objects']
@ -215,7 +224,7 @@ class TrackedObjectProcessor(threading.Thread):
### ###
# Report over MQTT # Report over MQTT
### ###
# get the zones that are relevant for this camera # get the zones that are relevant for this camera
relevant_zones = [zone for zone, config in self.zone_config.items() if camera in config] relevant_zones = [zone for zone, config in self.zone_config.items() if camera in config]
# for each zone # for each zone

View File

@ -140,11 +140,14 @@ def listen():
signal.signal(signal.SIGUSR1, print_stack) signal.signal(signal.SIGUSR1, print_stack)
class PlasmaManager: class PlasmaManager:
def __init__(self): def __init__(self, stop_event=None):
self.stop_event = stop_event
self.connect() self.connect()
def connect(self): def connect(self):
while True: while True:
if self.stop_event != None and self.stop_event.is_set():
return
try: try:
self.plasma_client = plasma.connect("/tmp/plasma") self.plasma_client = plasma.connect("/tmp/plasma")
return return
@ -155,6 +158,8 @@ class PlasmaManager:
def get(self, name, timeout_ms=0): def get(self, name, timeout_ms=0):
object_id = plasma.ObjectID(hashlib.sha1(str.encode(name)).digest()) object_id = plasma.ObjectID(hashlib.sha1(str.encode(name)).digest())
while True: while True:
if self.stop_event != None and self.stop_event.is_set():
return
try: try:
return self.plasma_client.get(object_id, timeout_ms=timeout_ms) return self.plasma_client.get(object_id, timeout_ms=timeout_ms)
except: except:
@ -164,6 +169,8 @@ class PlasmaManager:
def put(self, name, obj): def put(self, name, obj):
object_id = plasma.ObjectID(hashlib.sha1(str.encode(name)).digest()) object_id = plasma.ObjectID(hashlib.sha1(str.encode(name)).digest())
while True: while True:
if self.stop_event != None and self.stop_event.is_set():
return
try: try:
self.plasma_client.put(obj, object_id) self.plasma_client.put(obj, object_id)
return return
@ -175,6 +182,8 @@ class PlasmaManager:
def delete(self, name): def delete(self, name):
object_id = plasma.ObjectID(hashlib.sha1(str.encode(name)).digest()) object_id = plasma.ObjectID(hashlib.sha1(str.encode(name)).digest())
while True: while True:
if self.stop_event != None and self.stop_event.is_set():
return
try: try:
self.plasma_client.delete([object_id]) self.plasma_client.delete([object_id])
return return

View File

@ -116,7 +116,7 @@ def start_or_restart_ffmpeg(ffmpeg_cmd, frame_size, ffmpeg_process=None):
return process return process
class CameraCapture(threading.Thread): class CameraCapture(threading.Thread):
def __init__(self, name, ffmpeg_process, frame_shape, frame_queue, take_frame, fps, detection_frame): def __init__(self, name, ffmpeg_process, frame_shape, frame_queue, take_frame, fps, detection_frame, stop_event):
threading.Thread.__init__(self) threading.Thread.__init__(self)
self.name = name self.name = name
self.frame_shape = frame_shape self.frame_shape = frame_shape
@ -125,16 +125,21 @@ class CameraCapture(threading.Thread):
self.take_frame = take_frame self.take_frame = take_frame
self.fps = fps self.fps = fps
self.skipped_fps = EventsPerSecond() self.skipped_fps = EventsPerSecond()
self.plasma_client = PlasmaManager() self.plasma_client = PlasmaManager(stop_event)
self.ffmpeg_process = ffmpeg_process self.ffmpeg_process = ffmpeg_process
self.current_frame = 0 self.current_frame = 0
self.last_frame = 0 self.last_frame = 0
self.detection_frame = detection_frame self.detection_frame = detection_frame
self.stop_event = stop_event
def run(self): def run(self):
frame_num = 0 frame_num = 0
self.skipped_fps.start() self.skipped_fps.start()
while True: while True:
if self.stop_event.is_set():
print(f"{self.name}: stop event set. exiting capture thread...")
break
if self.ffmpeg_process.poll() != None: if self.ffmpeg_process.poll() != None:
print(f"{self.name}: ffmpeg process is not running. exiting capture thread...") print(f"{self.name}: ffmpeg process is not running. exiting capture thread...")
break break