#pragma once // ANSMatRegistry.h — Thread-safe cv::Mat* pointer registry. // Prevents double-free crashes when LabVIEW calls ReleaseImage on a pointer // that a stream source (RTSP, VideoPlayer, etc.) has already freed and replaced. // // Every cv::Mat* allocated through these functions is tracked in a global set. // Deletion only proceeds if the pointer is still in the set, guaranteeing each // pointer is freed exactly once regardless of which thread calls delete first. // // Usage: // Allocate: cv::Mat* p = anscv_mat_new(std::move(img)); // Free: anscv_mat_delete(&p); // safe, no-op if already freed // Swap: anscv_mat_replace(&p, std::move(newImg)); // atomic old-delete + new-alloc #include #include #include #include "ANSGpuFrameOps.h" namespace anscv { namespace detail { inline std::mutex& registry_mutex() { static std::mutex m; return m; } inline std::unordered_set& registry() { static std::unordered_set s; return s; } } // namespace detail } // namespace anscv // Allocate a new cv::Mat on the heap and register it. inline cv::Mat* anscv_mat_new(cv::Mat&& src) { cv::Mat* p = new cv::Mat(std::move(src)); { std::lock_guard lk(anscv::detail::registry_mutex()); anscv::detail::registry().insert(p); } return p; } inline cv::Mat* anscv_mat_new(const cv::Mat& src) { cv::Mat* p = new cv::Mat(src); { std::lock_guard lk(anscv::detail::registry_mutex()); anscv::detail::registry().insert(p); } return p; } // Delete a cv::Mat* only if it is still in the registry. // Returns true if deleted, false if pointer was null or already freed. // Thread-safe: the pointer is removed from the registry AND nulled AND deleted // all inside the lock, so no other thread can race on the same pointer. inline bool anscv_mat_delete(cv::Mat** pp) { if (!pp || !(*pp)) return false; cv::Mat* toDelete = nullptr; { std::lock_guard lk(anscv::detail::registry_mutex()); // Re-check under lock — another thread may have nulled *pp if (!*pp) return false; auto& reg = anscv::detail::registry(); auto it = reg.find(*pp); if (it == reg.end()) { // Not in registry — already freed by another thread. *pp = nullptr; return false; } toDelete = *pp; reg.erase(it); *pp = nullptr; // Null the caller's pointer while still under lock } // Release GPU frame data (refcount--, frees NV12+GPU cache when refcount=0) gpu_frame_remove(toDelete); // Safe to delete outside lock: pointer is removed from registry and // *pp is nullptr, so no other thread can reach toDelete. delete toDelete; return true; } // Atomic replace: delete old image (if still registered), allocate new, assign to *pp. // The old pointer is only deleted if it is in the registry (prevents double-free // when LabVIEW already freed it via anscv_mat_delete). inline void anscv_mat_replace(cv::Mat** pp, cv::Mat&& newImg) { cv::Mat* newPtr = new cv::Mat(std::move(newImg)); cv::Mat* toDelete = nullptr; { std::lock_guard lk(anscv::detail::registry_mutex()); auto& reg = anscv::detail::registry(); if (pp && *pp) { auto it = reg.find(*pp); if (it != reg.end()) { toDelete = *pp; reg.erase(it); } // If not in registry, another thread already freed it — skip delete } *pp = newPtr; reg.insert(newPtr); } if (toDelete) { // Release GPU frame ref for old Mat (safe with refcounting — // only frees NV12/GPU cache when refcount reaches 0) gpu_frame_remove(toDelete); delete toDelete; } // Periodically run TTL eviction (piggybacked on camera thread activity) gpu_frame_evict_stale(); }