Skip to content

chore(version): v1.5.6.6 – fix memory leaks and chart legend consistency - #96

Merged
cj-vana merged 7 commits into
betafrom
new-pr
Sep 22, 2025
Merged

cj-vana merged 7 commits into
betafrom
new-pr

Conversation

@cj-vana

@cj-vana cj-vana commented Sep 22, 2025 •

Copy link
Copy Markdown
Collaborator

User description

Fix critical memory leaks in capture agent (FOR REAL THIS TIME!) and chart legend display issues

  • Fixed memory leaks in buffer pool, DSP cache, FFT plans, and WebSocket cleanup
  • Resolved QueueFull errors and improved connection handling stability
  • Updated websockets v12+ compatibility for better connection resilience
  • Fixed legend white fill consistency for magnitude/phase charts
  • Improved capture agent stability and reduced frame drops
  • Bumped capture agent version to 0.1.14

PR Type

Bug fix, Enhancement


Description

  • Fixed critical memory leaks in capture agent DSP operations

  • Improved WebSocket connection handling and stability

  • Enhanced chart legend visual consistency

  • Upgraded capture agent to version 0.1.14


Diagram Walkthrough

flowchart LR
  A["Memory Leaks"] --> B["DSP Cache Fixes"]
  A --> C["Buffer Pool Management"]
  A --> D["FFT Plan Cleanup"]
  E["WebSocket Issues"] --> F["Connection Handling"]
  E --> G["Error Recovery"]
  H["Chart Legends"] --> I["White Fill Consistency"]
  B --> J["Stable Capture Agent v0.1.14"]
  C --> J
  D --> J
  F --> J
  G --> J
Loading

File Walkthrough

Relevant files
Bug fix
dsp.py
DSP memory management overhaul                                                     

agents/capture-agent-py/capture_agent/dsp.py

  • Replaced lru_cache with custom MemoryAwareLRUCache for better memory
    management
  • Enhanced work array caching with memory size tracking and eviction
  • Implemented proper FFT plan lifecycle management with cleanup
  • Added memory monitoring and periodic cleanup routines
+205/-23
server.py
WebSocket and buffer management fixes                                       

agents/capture-agent-py/capture_agent/server.py

  • Fixed WebSocket client management using weakref.WeakSet
  • Enhanced buffer pool management with proper cleanup
  • Improved error handling and connection state checking
  • Added dropped frame tracking and logging
+113/-39
TransferFunctionVisualizer.tsx
Chart legend white fill consistency                                           

packages/analyzer-lite/src/TransferFunctionVisualizer.tsx

  • Added white borderColor and backgroundColor to magnitude and phase
    chart datasets
  • Ensures consistent legend appearance with white fill
+4/-0     
Miscellaneous
index.html
Version bump in HTML metadata                                                       

apps/web/index.html

  • Updated software version from 1.5.6.5 to 1.5.6.6
+1/-1     
Documentation
CHANGELOG.md
Version 1.5.6.6 changelog entry                                                   

CHANGELOG.md

  • Added comprehensive changelog entry for version 1.5.6.6
  • Documented memory leak fixes and WebSocket improvements
  • Listed chart legend consistency improvements
+21/-0   
Configuration changes
pyproject.toml
Capture agent version update                                                         

agents/capture-agent-py/pyproject.toml

  • Bumped capture agent version from 0.1.13 to 0.1.14
+1/-1     
package.json
Web app version synchronization                                                   

apps/web/package.json

  • Updated version from 1.5.2.3 to 1.5.6.6
+1/-1     
package.json
Root package version update                                                           

package.json

  • Updated root package version from 1.5.6.5 to 1.5.6.6
+1/-1     

Fix critical memory leaks in capture agent (FOR REAL THIS TIME!) and chart legend display issues

  - Fixed memory leaks in buffer pool, DSP cache, FFT plans, and WebSocket cleanup
  - Resolved QueueFull errors and improved connection handling stability
  - Updated websockets v12+ compatibility for better connection resilience
  - Fixed legend white fill consistency for magnitude/phase charts
  - Improved capture agent stability and reduced frame drops
  - Bumped capture agent version to 0.1.14
@netlify

netlify Bot commented Sep 22, 2025 •

Copy link
Copy Markdown

✅ Deploy Preview for sounddocsbeta ready!

Name Link
🔨 Latest commit 8c1373b
🔍 Latest deploy log https://app.netlify.com/projects/sounddocsbeta/deploys/68d15ecc4633950008cbb222
😎 Deploy Preview https://deploy-preview-96--sounddocsbeta.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@qodo-code-review

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Possible Issue

The custom MemoryAwareLRUCache attaches cache_clear via wrapper.self, which relies on self on a plain function wrapper and may be brittle. Also, cache keys are built from args/kwargs tuples directly; using numpy arrays as args would make keys unhashable or very large. Confirm all decorated functions receive only hashable, small primitives.

# Fix 5: Memory-aware cache decorator implementation
class MemoryAwareLRUCache:
    """Memory-aware LRU cache that tracks memory usage and evicts based on memory limits.

    This custom cache decorator tracks the memory footprint of cached items and
    ensures the total cache size doesn't exceed the specified memory limit.
    """

    def __init__(self, max_memory_mb=10, max_items=32):
        self.max_memory = max_memory_mb * 1024 * 1024  # Convert to bytes
        self.max_items = max_items
        self.cache = OrderedDict()
        self.memory_usage = {}
        self.total_memory = 0

    def __call__(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            # Create cache key from function arguments
            key = (args, tuple(sorted(kwargs.items())))

            # Check if in cache
            if key in self.cache:
                # Move to end (most recently used)
                self.cache.move_to_end(key)
                return self.cache[key]

            # Compute result
            result = func(*args, **kwargs)

            # Calculate memory size for numpy arrays
            if isinstance(result, np.ndarray):
                result_memory = result.nbytes
            else:
                # Estimate memory for non-array objects
                result_memory = 1024  # Conservative estimate

            # Check if we need to evict items
            while (self.total_memory + result_memory > self.max_memory or
                   len(self.cache) >= self.max_items) and self.cache:
                # Evict least recently used
                evict_key = next(iter(self.cache))
                evicted = self.cache.pop(evict_key)
                evicted_memory = self.memory_usage.pop(evict_key, 0)
                self.total_memory -= evicted_memory

            # Add to cache
            self.cache[key] = result
            self.memory_usage[key] = result_memory
            self.total_memory += result_memory

            return result

        def cache_clear():
            """Clear the cache and reset memory tracking."""
            wrapper.__self__.cache.clear()
            wrapper.__self__.memory_usage.clear()
            wrapper.__self__.total_memory = 0

        # Attach instance to wrapper for access to cache_clear
        wrapper.__self__ = self
        wrapper.cache_clear = cache_clear
        return wrapper

# Create memory-aware cache instances
_hann_cache_instance = MemoryAwareLRUCache(max_memory_mb=5, max_items=32)
_taper_cache_instance = MemoryAwareLRUCache(max_memory_mb=5, max_items=16)

@_hann_cache_instance
def _hann_cached(M: int) -> np.ndarray:
Memory Calc

FFT plan memory estimation mixes dtype sizes and always uses complex128 for out arrays; if dtype isn’t float64 this may over/under-estimate, affecting eviction behavior. Validate sizes for forward/inverse and chosen dtype, and that cleanup triggers as intended.

if plan_key not in _fft_plans:
    # Check memory before creating new plan
    new_plan_memory = n * np.dtype(dtype).itemsize
    if direction == 'forward':
        new_plan_memory += (n // 2 + 1) * np.dtype(np.complex128).itemsize
    else:
        new_plan_memory += (n // 2 + 1) * np.dtype(np.complex128).itemsize + n * np.dtype(dtype).itemsize

    # Ensure we have space
    current_memory = sum(p[1].nbytes + p[2].nbytes for p in _fft_plans.values())
    if current_memory + new_plan_memory > MAX_FFT_PLAN_MEMORY:
        cleanup_fft_plans(force=True)
WS State Check

Using ws.state with websockets v12: confirm protocol.State is the correct enum and that accessing ws.request.headers for Origin is valid across server versions. Incompatibility could cause runtime errors.

# Check if WebSocket is still open before sending
if ws.state == protocol.State.OPEN:
    try:
        frame = FrameMessage(
            type="frame",
            tf=tf_data,
            spl=spl_data,
            delay_ms=applied,              # show applied, not local variable
            latency_ms=float(stream.latency)*1000.0 if hasattr(stream, "latency") else 0.0,
            ts=int(time.time() * 1000),
            sampleRate=fs,
            delay_mode=status["mode"],
            applied_delay_ms=applied,
        )
        await ws.send(frame.model_dump_json())
        last_send = now
    except websockets.exceptions.ConnectionClosed:
        print("WebSocket connection closed during frame send")
        break  # Exit the capture loop
else:
    print("WebSocket connection is not open, stopping capture")
    break

@qodo-code-review

qodo-code-review Bot commented Sep 22, 2025 •

Copy link
Copy Markdown
Contributor

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Prevent buffer leaks with thread-safe queuing

To prevent a buffer leak from a race condition, move the try...except logic for
queuing a buffer into a dedicated function and schedule that function with
call_soon_threadsafe.

agents/capture-agent-py/capture_agent/server.py [154-155]

 # Try to queue the buffer
-loop.call_soon_threadsafe(aq.put_nowait, buf)
+def queue_buffer(buffer):
+    try:
+        aq.put_nowait(buffer)
+    except asyncio.QueueFull:
+        nonlocal dropped_frames
+        dropped_frames += 1
+        if len(pool) < max_pool_size:
+            pool.append(buffer)
 
+loop.call_soon_threadsafe(queue_buffer, buf)
+
  • Apply / Chat
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a subtle but critical race condition that can cause a buffer leak, which is a primary issue this PR aims to resolve. The proposed fix is robust and correctly handles the thread-safety issue.

High
Fix premature loop exit in eviction logic

In get_work_array, remove the break statement from the memory eviction while
loop to ensure it can run multiple eviction passes and correctly enforce the
MAX_WORK_ARRAY_MEMORY limit.

agents/capture-agent-py/capture_agent/dsp.py [136-139]

-# If still not enough space, clear more aggressively
-if current_memory + array_nbytes > MAX_WORK_ARRAY_MEMORY:
-    # Keep only the smallest essential arrays
-    break
+# If still not enough space, the while loop will continue
+# to evict more arrays in the next iteration.
+# The 'break' here was a bug causing the loop to exit prematurely.
+pass
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a logic error in the memory eviction loop that would cause it to exit prematurely, undermining the memory limit enforcement. Fixing this is crucial for the stability and memory management goals of the PR.

Medium
  • Update

…rFunctionVisualizer

  - Replace 4 'any' types with proper TypeScript types (ScriptableLineSegmentContext, number | string)
  - Add missing dependencies (coherenceAlpha, coherenceThreshold) to React useMemo hooks
  - Maintains all existing functionality while improving type safety
  - Resolves 4 @typescript-eslint/no-explicit-any errors and 2 react-hooks/exhaustive-deps warnings
…sorry, it's my repo lol)

- Remove ruff linting and formatting checks from the GitHub Actions PR workflow.
- The Python checks job now only runs optional mypy type checking.
@cj-vana
cj-vana merged commit e20c520 into beta Sep 22, 2025
9 checks passed
@cj-vana cj-vana mentioned this pull request Oct 10, 2025
5 of 7 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant