mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-10-20 21:52:50 +03:00
Limit concurrent motion searches (#24717)
* limit concurrent motion search jobs and search range Any viewer could start unlimited motion search jobs over an unbounded time range, and finished jobs were never dropped from memory. Each user now gets one active job (409), at most 3 run at once (429), the range is capped at 48 hours, and finished jobs are evicted 10 minutes after they end. Jobs are no longer filed in the shared job manager, which kept every finished job forever. * show translated errors for motion search limits * release the motion search slot if the runner fails to start * keep wedged or shared-identity motion searches from locking out users A stalled VOD read blocked the runner forever, so a wedged job held its user and global slot until restart, and on installs without per-user identity (internal port, or auth disabled without a proxy user header) everyone shared one per-user slot. VOD ffmpeg and ffprobe inputs now use a 30 s rw_timeout, a cancel also drops queued parallel runs, a cancelled job stops counting after 60 s, and the per-user limit only applies when the request carries a real username. A failed runner start also stops its requestor. * treat timed-out VOD segments as motion search errors With rw_timeout, the HLS demuxer skipped a timed-out segment and exited cleanly, so a run reported success with a hole in it, and when the keyframe probe and the decode lost different segments the equal frame counts paired frames with the wrong timestamps. The keyframe path now requires the probe to cover the whole run without a gap over 5 s, a decode whose stderr shows a skipped segment fails the run, and a cadence decode more than 5 s short of the run's duration fails it too, so these runs land in segments_with_errors. The keyframe probe polls should_stop and is killed on cancel, probe_video_dimensions gets the read timeout, and a user's own cancelled job no longer blocks their next search while it still counts toward the global cap. * drop motion search stall detection and range cap * remove motion search range check from the UI
This commit is contained in:
@@ -9,7 +9,10 @@ from pydantic import BaseModel, Field
|
||||
|
||||
from frigate.api.auth import require_camera_access
|
||||
from frigate.api.defs.tags import Tags
|
||||
from frigate.config import FrigateConfig
|
||||
from frigate.jobs.motion_search import (
|
||||
MotionSearchCapacityError,
|
||||
MotionSearchUserBusyError,
|
||||
cancel_motion_search_job,
|
||||
get_motion_search_job,
|
||||
start_motion_search_job,
|
||||
@@ -95,6 +98,20 @@ class MotionSearchStatusResponse(BaseModel):
|
||||
progress: float | None = None
|
||||
|
||||
|
||||
def _per_user_identity(request: Request, config: FrigateConfig) -> str | None:
|
||||
"""Return the requesting user, or None when callers share one identity.
|
||||
|
||||
Internal-port requests are all "anonymous", and with auth disabled and no
|
||||
proxy user header every browser is "viewer".
|
||||
"""
|
||||
username = request.headers.get("remote-user")
|
||||
if not username or username == "anonymous":
|
||||
return None
|
||||
if config.auth.enabled or config.proxy.header_map.user:
|
||||
return username
|
||||
return None
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{camera_name}/search/motion",
|
||||
response_model=MotionSearchStartResponse,
|
||||
@@ -138,18 +155,35 @@ async def start_motion_search(
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
# Start the job using the jobs module
|
||||
job_id = start_motion_search_job(
|
||||
config=config,
|
||||
camera_name=camera_name,
|
||||
start_time=body.start_time,
|
||||
end_time=body.end_time,
|
||||
polygon_points=body.polygon_points,
|
||||
threshold=body.threshold,
|
||||
min_area=body.min_area,
|
||||
parallel=body.parallel,
|
||||
max_results=body.max_results,
|
||||
)
|
||||
try:
|
||||
job_id = start_motion_search_job(
|
||||
config=config,
|
||||
camera_name=camera_name,
|
||||
start_time=body.start_time,
|
||||
end_time=body.end_time,
|
||||
polygon_points=body.polygon_points,
|
||||
threshold=body.threshold,
|
||||
min_area=body.min_area,
|
||||
parallel=body.parallel,
|
||||
max_results=body.max_results,
|
||||
username=_per_user_identity(request, config),
|
||||
)
|
||||
except MotionSearchUserBusyError:
|
||||
return JSONResponse(
|
||||
content={
|
||||
"success": False,
|
||||
"message": "A motion search is already running for this user",
|
||||
},
|
||||
status_code=409,
|
||||
)
|
||||
except MotionSearchCapacityError:
|
||||
return JSONResponse(
|
||||
content={
|
||||
"success": False,
|
||||
"message": "Too many motion searches are running",
|
||||
},
|
||||
status_code=429,
|
||||
)
|
||||
|
||||
return JSONResponse(
|
||||
content={
|
||||
|
||||
Reference in New Issue
Block a user