From a4f077b128e96de679a777565a7cf0ce630ff72f Mon Sep 17 00:00:00 2001
From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
Date: Thu, 4 Jun 2026 13:48:58 -0500
Subject: [PATCH] Miscellaneous fixes (#23394)
* serialize OpenVINO inference per process to prevent concurrent-inference segfault
* clean up
* add max scaling meta to login page
* add more detect section field messages
* fix icon layout in settings field messages
* tweak edit icon color
---
frigate/detectors/detection_runners.py | 29 +++---
web/login.html | 2 +-
web/public/locales/en/views/settings.json | 6 +-
.../config-form/ConfigFieldMessage.tsx | 8 +-
.../config-form/ConfigMessageBanner.tsx | 6 +-
.../config-form/section-configs/detect.ts | 91 +++++++++++++++++--
.../components/filter/CameraGroupSelector.tsx | 4 +-
7 files changed, 114 insertions(+), 32 deletions(-)
diff --git a/frigate/detectors/detection_runners.py b/frigate/detectors/detection_runners.py
index c89cd8b44f..ee465b3d51 100644
--- a/frigate/detectors/detection_runners.py
+++ b/frigate/detectors/detection_runners.py
@@ -15,6 +15,9 @@ from frigate.util.rknn_converter import auto_convert_model, is_rknn_compatible
logger = logging.getLogger(__name__)
+# Process-wide lock serializing all OpenVINO compile/inference calls
+_OPENVINO_LOCK = threading.Lock()
+
def is_arm64_platform() -> bool:
"""Check if we're running on an ARM platform."""
@@ -326,19 +329,17 @@ class OpenVINOModelRunner(BaseModelRunner):
except Exception as e:
logger.debug(f"NPU_TURBO not supported by driver: {e}")
- # Compile model
- self.compiled_model = self.ov_core.compile_model(
- model=model_path, device_name=device
- )
+ # Compile model under the shared lock
+ with _OPENVINO_LOCK:
+ self.compiled_model = self.ov_core.compile_model(
+ model=model_path, device_name=device
+ )
+
+ # Create reusable inference request
+ self.infer_request = self.compiled_model.create_infer_request()
- # Create reusable inference request
- self.infer_request = self.compiled_model.create_infer_request()
self.input_tensor: ov.Tensor | None = None
- # Thread lock to prevent concurrent inference (needed for JinaV2 which shares
- # one runner between text and vision embeddings called from different threads)
- self._inference_lock = threading.Lock()
-
if not self.complex_model:
try:
input_shape = self.compiled_model.inputs[0].get_shape()
@@ -382,9 +383,11 @@ class OpenVINOModelRunner(BaseModelRunner):
Returns:
List of output tensors
"""
- # Lock prevents concurrent access to infer_request
- # Needed for JinaV2: genai thread (text) + embeddings thread (vision)
- with self._inference_lock:
+ # Shared lock serializes inference across every OpenVINO runner in this
+ # process — both the shared-runner JinaV2 case (genai text thread +
+ # embeddings vision thread) and distinct runners running on separate
+ # threads (e.g. the ArcFace face-model build vs the LPR detector).
+ with _OPENVINO_LOCK:
from frigate.embeddings.types import EnrichmentModelTypeEnum
if self.model_type in [EnrichmentModelTypeEnum.arcface.value]:
diff --git a/web/login.html b/web/login.html
index fc0fb551e3..f83243d023 100644
--- a/web/login.html
+++ b/web/login.html
@@ -3,7 +3,7 @@
-
+
Frigate
;
+ return ;
case "warning":
- return ;
+ return ;
case "error":
- return ;
+ return ;
default:
- return ;
+ return ;
}
}
diff --git a/web/src/components/config-form/ConfigMessageBanner.tsx b/web/src/components/config-form/ConfigMessageBanner.tsx
index 919d337c15..8e701947a0 100644
--- a/web/src/components/config-form/ConfigMessageBanner.tsx
+++ b/web/src/components/config-form/ConfigMessageBanner.tsx
@@ -18,11 +18,11 @@ const severityVariantMap: Record<
function SeverityIcon({ severity }: { severity: MessageSeverity }) {
switch (severity) {
case "info":
- return ;
+ return ;
case "warning":
- return ;
+ return ;
case "error":
- return ;
+ return ;
}
}
diff --git a/web/src/components/config-form/section-configs/detect.ts b/web/src/components/config-form/section-configs/detect.ts
index 74d170edc6..9176c49a74 100644
--- a/web/src/components/config-form/section-configs/detect.ts
+++ b/web/src/components/config-form/section-configs/detect.ts
@@ -11,8 +11,12 @@ const detect: SectionConfigOverrides = {
condition: (ctx) =>
ctx.level === "camera" && ctx.formData?.enabled === false,
},
+ ],
+ fieldMessages: [
{
key: "detect-resolution-not-multiple-of-four",
+ field: "width",
+ position: "before",
messageKey: "configMessages.detect.resolutionShouldBeMultipleOfFour",
severity: "warning",
condition: (ctx) => {
@@ -23,8 +27,59 @@ const detect: SectionConfigOverrides = {
return isEvenButNotFour(width) || isEvenButNotFour(height);
},
},
+ {
+ key: "detect-global-resolution-multiple-cameras",
+ field: "width",
+ position: "before",
+ messageKey: "configMessages.detect.globalResolutionMultipleCameras",
+ severity: "info",
+ condition: (ctx) => {
+ if (ctx.level !== "global") return false;
+ const width = ctx.formData?.width as number | null | undefined;
+ const height = ctx.formData?.height as number | null | undefined;
+ if (typeof width !== "number" && typeof height !== "number") {
+ return false;
+ }
+ const cameraCount = Object.keys(ctx.fullConfig?.cameras ?? {}).length;
+ return cameraCount > 1;
+ },
+ },
+ {
+ key: "detect-resolution-high",
+ field: "width",
+ position: "before",
+ messageKey: "configMessages.detect.resolutionHigh",
+ severity: "warning",
+ condition: (ctx) => {
+ const width = ctx.formData?.width as number | null | undefined;
+ const height = ctx.formData?.height as number | null | undefined;
+ if (typeof width !== "number" || typeof height !== "number") {
+ return false;
+ }
+ return Math.min(width, height) > 1080;
+ },
+ },
+ {
+ key: "detect-square-resolution",
+ field: "width",
+ position: "before",
+ messageKey: "configMessages.detect.squareResolution",
+ severity: "warning",
+ condition: (ctx) => {
+ const width = ctx.formData?.width as number | null | undefined;
+ const height = ctx.formData?.height as number | null | undefined;
+ return (
+ typeof width === "number" &&
+ typeof height === "number" &&
+ width > 0 &&
+ width === height
+ );
+ },
+ },
{
key: "detect-aspect-ratio-mismatch",
+ field: "width",
+ position: "before",
messageKey: "configMessages.detect.aspectRatioMismatch",
severity: "warning",
condition: (ctx) => {
@@ -55,8 +110,6 @@ const detect: SectionConfigOverrides = {
return Math.abs(newRatio - savedRatio) > 0.01;
},
},
- ],
- fieldMessages: [
{
key: "fps-greater-than-five",
field: "fps",
@@ -71,6 +124,31 @@ const detect: SectionConfigOverrides = {
return detectFps != null && streamFps != null && detectFps > 5;
},
},
+ {
+ key: "max-frames-set",
+ field: "stationary.max_frames",
+ messageKey: "configMessages.detect.maxFramesSet",
+ severity: "warning",
+ position: "after",
+ condition: (ctx) => {
+ const stationary = ctx.formData?.stationary as
+ | {
+ max_frames?: {
+ default?: number | null;
+ objects?: Record;
+ } | null;
+ }
+ | null
+ | undefined;
+ const maxFrames = stationary?.max_frames;
+ if (!maxFrames) return false;
+ return (
+ typeof maxFrames.default === "number" ||
+ (maxFrames.objects != null &&
+ Object.keys(maxFrames.objects).length > 0)
+ );
+ },
+ },
],
fieldOrder: [
"enabled",
@@ -81,9 +159,9 @@ const detect: SectionConfigOverrides = {
"max_disappeared",
"annotation_offset",
"stationary",
- "interval",
- "threshold",
- "max_frames",
+ "stationary.interval",
+ "stationary.threshold",
+ "stationary.max_frames",
],
restartRequired: [],
fieldGroups: {
@@ -129,9 +207,6 @@ const detect: SectionConfigOverrides = {
"max_disappeared",
"annotation_offset",
"stationary",
- "interval",
- "threshold",
- "max_frames",
],
advancedFields: [],
},
diff --git a/web/src/components/filter/CameraGroupSelector.tsx b/web/src/components/filter/CameraGroupSelector.tsx
index 8f32846fc1..151eb6d044 100644
--- a/web/src/components/filter/CameraGroupSelector.tsx
+++ b/web/src/components/filter/CameraGroupSelector.tsx
@@ -283,7 +283,7 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
afterSelect?.();
}}
>
-
+
,
);
}
@@ -376,7 +376,7 @@ export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
onMouseEnter={() => showTooltip("edit")}
onMouseLeave={() => showTooltip(undefined)}
>
-
+