Files
ANSCORE/modules/ANSCV/ANSMatRegistry.h

117 lines
3.9 KiB
C++

#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 <opencv2/core/mat.hpp>
#include <mutex>
#include <unordered_set>
#include "ANSGpuFrameOps.h"
namespace anscv {
namespace detail {
inline std::mutex& registry_mutex() {
static std::mutex m;
return m;
}
inline std::unordered_set<cv::Mat*>& registry() {
static std::unordered_set<cv::Mat*> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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();
}