Skip to content

Commit 7729fec

Browse files
gmathy2104claude
andcommitted
fix: add global lock to prevent race conditions in camera reconfiguration
Fixed a critical race condition bug that occurred when multiple concurrent requests attempted to change camera configuration (resolution/framerate). Problem: - StreamingManager and CameraController had internal locks - No global lock protected the complete stop→reconfigure→start sequence - Concurrent requests could interleave, causing: * Double streaming stop/start * Conflicting camera configurations * Service crashes or deadlocks Solution: - Added global _reconfiguration_lock (RLock) in api.py - Protected /v1/camera/resolution and /v1/camera/framerate endpoints - Operations now execute sequentially, preventing conflicts Testing: - Reproduced bug with concurrent requests (Failed to set framerate) - Verified fix with multiple simultaneous requests (all successful) - Service remains stable and streaming active after stress testing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 23de47a commit 7729fec

File tree

1 file changed

+53
-44
lines changed

1 file changed

+53
-44
lines changed

camera_service/api.py

Lines changed: 53 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import logging
1111
from contextlib import asynccontextmanager
12+
from threading import RLock
1213
from typing import Annotated, AsyncGenerator
1314

1415
from fastapi import Depends, FastAPI, HTTPException, Security, status
@@ -37,6 +38,10 @@
3738
camera_controller: CameraController | None = None
3839
streaming_manager: StreamingManager | None = None
3940

41+
# Global lock for camera reconfiguration operations
42+
# Protects sequences that require stopping/reconfiguring/restarting streaming
43+
_reconfiguration_lock = RLock()
44+
4045
# API Key authentication
4146
api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
4247

@@ -1643,32 +1648,34 @@ def set_resolution(
16431648
"""
16441649
logger.info(f"Setting resolution: {req.width}x{req.height}")
16451650

1646-
try:
1647-
# Check if streaming is active
1648-
was_streaming = streaming.is_streaming()
1651+
# Use global lock to prevent concurrent reconfiguration operations
1652+
with _reconfiguration_lock:
1653+
try:
1654+
# Check if streaming is active
1655+
was_streaming = streaming.is_streaming()
16491656

1650-
# Stop streaming if active (must stop encoder first)
1651-
if was_streaming:
1652-
logger.info("Stopping streaming before resolution change")
1653-
streaming.stop()
1657+
# Stop streaming if active (must stop encoder first)
1658+
if was_streaming:
1659+
logger.info("Stopping streaming before resolution change")
1660+
streaming.stop()
16541661

1655-
# Change resolution (this will stop, reconfigure, and restart camera)
1656-
camera.set_resolution(req.width, req.height)
1662+
# Change resolution (this will stop, reconfigure, and restart camera)
1663+
camera.set_resolution(req.width, req.height)
16571664

1658-
# Restart streaming if requested and was previously streaming
1659-
if req.restart_streaming and was_streaming:
1660-
logger.info("Restarting streaming after resolution change")
1661-
streaming.start()
1665+
# Restart streaming if requested and was previously streaming
1666+
if req.restart_streaming and was_streaming:
1667+
logger.info("Restarting streaming after resolution change")
1668+
streaming.start()
16621669

1663-
return StatusResponse()
1664-
except (CameraNotAvailableError, InvalidParameterError):
1665-
raise
1666-
except Exception as e:
1667-
logger.error(f"Error setting resolution: {e}")
1668-
raise HTTPException(
1669-
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
1670-
detail="Failed to set resolution",
1671-
)
1670+
return StatusResponse()
1671+
except (CameraNotAvailableError, InvalidParameterError):
1672+
raise
1673+
except Exception as e:
1674+
logger.error(f"Error setting resolution: {e}")
1675+
raise HTTPException(
1676+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
1677+
detail="Failed to set resolution",
1678+
)
16721679

16731680

16741681
@app.post(
@@ -1708,29 +1715,31 @@ def set_framerate(
17081715
"""
17091716
logger.info(f"Setting framerate: {req.framerate}fps")
17101717

1711-
try:
1712-
# Check if streaming is active
1713-
was_streaming = streaming.is_streaming()
1718+
# Use global lock to prevent concurrent reconfiguration operations
1719+
with _reconfiguration_lock:
1720+
try:
1721+
# Check if streaming is active
1722+
was_streaming = streaming.is_streaming()
17141723

1715-
# Stop streaming if active (must stop encoder first)
1716-
if was_streaming:
1717-
logger.info("Stopping streaming before framerate change")
1718-
streaming.stop()
1724+
# Stop streaming if active (must stop encoder first)
1725+
if was_streaming:
1726+
logger.info("Stopping streaming before framerate change")
1727+
streaming.stop()
17191728

1720-
# Change framerate (this will stop, reconfigure, and restart camera)
1721-
result = camera.set_framerate(req.framerate)
1729+
# Change framerate (this will stop, reconfigure, and restart camera)
1730+
result = camera.set_framerate(req.framerate)
17221731

1723-
# Restart streaming if requested and was previously streaming
1724-
if req.restart_streaming and was_streaming:
1725-
logger.info("Restarting streaming after framerate change")
1726-
streaming.start()
1732+
# Restart streaming if requested and was previously streaming
1733+
if req.restart_streaming and was_streaming:
1734+
logger.info("Restarting streaming after framerate change")
1735+
streaming.start()
17271736

1728-
return FramerateResponse(**result)
1729-
except (CameraNotAvailableError, InvalidParameterError):
1730-
raise
1731-
except Exception as e:
1732-
logger.error(f"Error setting framerate: {e}")
1733-
raise HTTPException(
1734-
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
1735-
detail="Failed to set framerate",
1736-
)
1737+
return FramerateResponse(**result)
1738+
except (CameraNotAvailableError, InvalidParameterError):
1739+
raise
1740+
except Exception as e:
1741+
logger.error(f"Error setting framerate: {e}")
1742+
raise HTTPException(
1743+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
1744+
detail="Failed to set framerate",
1745+
)

0 commit comments

Comments
 (0)