Fix deadlock in spatial_scene — lock re-entrancy
observe() held self._lock, called _check_anomaly, which called get_usual_direction, which tried to acquire self._lock again → deadlock. Split into _usual_direction_unlocked (no lock) for internal use. This caused /scene and all other API endpoints to hang after the first sound classification with spatial data. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -168,8 +168,8 @@ class SpatialScene:
|
|||||||
if now - self._last_anomaly.get(category, 0) < 30.0:
|
if now - self._last_anomaly.get(category, 0) < 30.0:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Find the usual direction for this category
|
# Find the usual direction for this category (already holding lock)
|
||||||
usual_angle = self.get_usual_direction(category)
|
usual_angle = self._usual_direction_unlocked(category)
|
||||||
if usual_angle is None:
|
if usual_angle is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -192,34 +192,36 @@ class SpatialScene:
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _usual_direction_unlocked(self, category: str) -> Optional[float]:
|
||||||
|
"""Get the most common direction for a category. Caller must hold self._lock."""
|
||||||
|
bins = self.scene_map.get(category)
|
||||||
|
if not bins:
|
||||||
|
return None
|
||||||
|
|
||||||
|
total_weight = sum(bins.values())
|
||||||
|
if total_weight == 0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
sin_sum = 0.0
|
||||||
|
cos_sum = 0.0
|
||||||
|
for bin_idx, count in bins.items():
|
||||||
|
angle_rad = math.radians(_bin_center(bin_idx))
|
||||||
|
sin_sum += count * math.sin(angle_rad)
|
||||||
|
cos_sum += count * math.cos(angle_rad)
|
||||||
|
|
||||||
|
return math.degrees(math.atan2(sin_sum, cos_sum)) % 360
|
||||||
|
|
||||||
def get_usual_direction(self, category: str) -> Optional[float]:
|
def get_usual_direction(self, category: str) -> Optional[float]:
|
||||||
"""Get the most common direction for a sound category (weighted average)."""
|
"""Get the most common direction for a sound category (thread-safe)."""
|
||||||
with self._lock:
|
with self._lock:
|
||||||
bins = self.scene_map.get(category)
|
return self._usual_direction_unlocked(category)
|
||||||
if not bins:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Weighted circular mean
|
|
||||||
total_weight = sum(bins.values())
|
|
||||||
if total_weight == 0:
|
|
||||||
return None
|
|
||||||
|
|
||||||
sin_sum = 0.0
|
|
||||||
cos_sum = 0.0
|
|
||||||
for bin_idx, count in bins.items():
|
|
||||||
angle_rad = math.radians(_bin_center(bin_idx))
|
|
||||||
sin_sum += count * math.sin(angle_rad)
|
|
||||||
cos_sum += count * math.cos(angle_rad)
|
|
||||||
|
|
||||||
mean_angle = math.degrees(math.atan2(sin_sum, cos_sum)) % 360
|
|
||||||
return mean_angle
|
|
||||||
|
|
||||||
def get_scene_summary(self) -> dict:
|
def get_scene_summary(self) -> dict:
|
||||||
"""Get a summary of the learned spatial scene."""
|
"""Get a summary of the learned spatial scene."""
|
||||||
with self._lock:
|
with self._lock:
|
||||||
summary = {}
|
summary = {}
|
||||||
for category in sorted(self.scene_map.keys()):
|
for category in sorted(self.scene_map.keys()):
|
||||||
usual = self.get_usual_direction(category)
|
usual = self._usual_direction_unlocked(category)
|
||||||
total = self.category_totals[category]
|
total = self.category_totals[category]
|
||||||
if usual is not None:
|
if usual is not None:
|
||||||
summary[category] = {
|
summary[category] = {
|
||||||
|
|||||||
Reference in New Issue
Block a user