From 51fe507dfda9a5355f7856f966b7234e3c4c0d51 Mon Sep 17 00:00:00 2001 From: Tuan Nghia Nguyen Date: Sun, 19 Apr 2026 14:47:29 +1000 Subject: [PATCH] Fix unregister issue --- modules/ANSFR/dllmain.cpp | 29 ++++--- modules/ANSLLM/dllmain.cpp | 29 ++++--- modules/ANSLPR/dllmain.cpp | 38 +++++---- modules/ANSMOT/dllmain.cpp | 46 ++++++----- modules/ANSOCR/dllmain.cpp | 29 ++++--- modules/ANSODEngine/dllmain.cpp | 36 +++++---- .../ANSTrainingEngine/ANSTrainingEngine.cpp | 29 ++++--- modules/ANSUtilities/dllmain.cpp | 77 ++++++++++++------- 8 files changed, 191 insertions(+), 122 deletions(-) diff --git a/modules/ANSFR/dllmain.cpp b/modules/ANSFR/dllmain.cpp index cc2c4ec..99f4c0f 100644 --- a/modules/ANSFR/dllmain.cpp +++ b/modules/ANSFR/dllmain.cpp @@ -28,8 +28,12 @@ std::atomic g_forceNoPool{false}; // Handle registry with refcount — prevents use-after-free when // ReleaseANSRFHandle is called while inference is still running. -static std::unordered_map& FRHandleRegistry() { - static std::unordered_map s; +// destructionStarted: set by the first Unregister caller; blocks new Acquires +// and makes subsequent Unregister calls return false without deleting. +// Prevents double-free when Release is raced on the same handle. +struct FREntry { int refcount; bool destructionStarted; }; +static std::unordered_map& FRHandleRegistry() { + static std::unordered_map s; return s; } static std::mutex& FRHandleRegistryMutex() { @@ -43,14 +47,15 @@ static std::condition_variable& FRHandleRegistryCV() { static void RegisterFRHandle(ANSCENTER::ANSFacialRecognition* h) { std::lock_guard lk(FRHandleRegistryMutex()); - FRHandleRegistry()[h] = 1; + FRHandleRegistry()[h] = { 1, false }; } static ANSCENTER::ANSFacialRecognition* AcquireFRHandle(ANSCENTER::ANSFacialRecognition* h) { std::lock_guard lk(FRHandleRegistryMutex()); auto it = FRHandleRegistry().find(h); if (it == FRHandleRegistry().end()) return nullptr; - it->second++; + if (it->second.destructionStarted) return nullptr; + it->second.refcount++; return h; } @@ -58,23 +63,25 @@ static bool ReleaseFRHandleRef(ANSCENTER::ANSFacialRecognition* h) { std::lock_guard lk(FRHandleRegistryMutex()); auto it = FRHandleRegistry().find(h); if (it == FRHandleRegistry().end()) return false; - it->second--; - if (it->second <= 0) { - FRHandleRegistry().erase(it); + it->second.refcount--; + if (it->second.refcount <= 0) { FRHandleRegistryCV().notify_all(); - return true; } - return false; + return false; // Only Unregister deletes. } static bool UnregisterFRHandle(ANSCENTER::ANSFacialRecognition* h) { std::unique_lock lk(FRHandleRegistryMutex()); auto it = FRHandleRegistry().find(h); if (it == FRHandleRegistry().end()) return false; - it->second--; + if (it->second.destructionStarted) { + return false; // Another thread already owns the delete. + } + it->second.destructionStarted = true; + it->second.refcount--; bool ok = FRHandleRegistryCV().wait_for(lk, std::chrono::seconds(30), [&]() { auto it2 = FRHandleRegistry().find(h); - return it2 == FRHandleRegistry().end() || it2->second <= 0; + return it2 == FRHandleRegistry().end() || it2->second.refcount <= 0; }); if (!ok) { OutputDebugStringA("WARNING: UnregisterFRHandle timed out waiting for in-flight inference\n"); diff --git a/modules/ANSLLM/dllmain.cpp b/modules/ANSLLM/dllmain.cpp index ff391b7..fa2ef45 100644 --- a/modules/ANSLLM/dllmain.cpp +++ b/modules/ANSLLM/dllmain.cpp @@ -9,8 +9,12 @@ // Handle registry with refcount — prevents use-after-free when // ReleaseANSLLMHandle is called while an operation is still running. -static std::unordered_map& LLMHandleRegistry() { - static std::unordered_map s; +// destructionStarted: set by the first Unregister caller; blocks new Acquires +// and makes subsequent Unregister calls return false without deleting. +// Prevents double-free when Release is raced on the same handle. +struct LLMEntry { int refcount; bool destructionStarted; }; +static std::unordered_map& LLMHandleRegistry() { + static std::unordered_map s; return s; } static std::mutex& LLMHandleRegistryMutex() { @@ -24,14 +28,15 @@ static std::condition_variable& LLMHandleRegistryCV() { static void RegisterLLMHandle(ANSCENTER::ANSLLM* h) { std::lock_guard lk(LLMHandleRegistryMutex()); - LLMHandleRegistry()[h] = 1; + LLMHandleRegistry()[h] = { 1, false }; } static ANSCENTER::ANSLLM* AcquireLLMHandle(ANSCENTER::ANSLLM* h) { std::lock_guard lk(LLMHandleRegistryMutex()); auto it = LLMHandleRegistry().find(h); if (it == LLMHandleRegistry().end()) return nullptr; - it->second++; + if (it->second.destructionStarted) return nullptr; + it->second.refcount++; return h; } @@ -39,23 +44,25 @@ static bool ReleaseLLMHandleRef(ANSCENTER::ANSLLM* h) { std::lock_guard lk(LLMHandleRegistryMutex()); auto it = LLMHandleRegistry().find(h); if (it == LLMHandleRegistry().end()) return false; - it->second--; - if (it->second <= 0) { - LLMHandleRegistry().erase(it); + it->second.refcount--; + if (it->second.refcount <= 0) { LLMHandleRegistryCV().notify_all(); - return true; } - return false; + return false; // Only Unregister deletes. } static bool UnregisterLLMHandle(ANSCENTER::ANSLLM* h) { std::unique_lock lk(LLMHandleRegistryMutex()); auto it = LLMHandleRegistry().find(h); if (it == LLMHandleRegistry().end()) return false; - it->second--; + if (it->second.destructionStarted) { + return false; // Another thread already owns the delete. + } + it->second.destructionStarted = true; + it->second.refcount--; bool ok = LLMHandleRegistryCV().wait_for(lk, std::chrono::seconds(30), [&]() { auto it2 = LLMHandleRegistry().find(h); - return it2 == LLMHandleRegistry().end() || it2->second <= 0; + return it2 == LLMHandleRegistry().end() || it2->second.refcount <= 0; }); if (!ok) { OutputDebugStringA("WARNING: UnregisterLLMHandle timed out waiting for in-flight operations\n"); diff --git a/modules/ANSLPR/dllmain.cpp b/modules/ANSLPR/dllmain.cpp index 01c4b88..cd1fd0d 100644 --- a/modules/ANSLPR/dllmain.cpp +++ b/modules/ANSLPR/dllmain.cpp @@ -28,8 +28,12 @@ static void WriteWindowsEventLog(const char* message, WORD eventType = EVENTLOG_ // ReleaseANSALPRHandle is called while inference is still running. // refcount: starts at 1 on Register. AcquireALPRHandle increments, // ReleaseALPRHandleRef decrements. Object is destroyed when refcount hits 0. -static std::unordered_map& ALPRHandleRegistry() { - static std::unordered_map s; +// destructionStarted: set by the first Unregister caller; blocks new Acquires +// and makes subsequent Unregister calls return false without deleting. +// Prevents double-free when Release is raced on the same handle. +struct ALPREntry { int refcount; bool destructionStarted; }; +static std::unordered_map& ALPRHandleRegistry() { + static std::unordered_map s; return s; } static std::mutex& ALPRHandleRegistryMutex() { @@ -43,46 +47,48 @@ static std::condition_variable& ALPRHandleRegistryCV() { static void RegisterALPRHandle(ANSCENTER::ANSALPR* h) { std::lock_guard lk(ALPRHandleRegistryMutex()); - ALPRHandleRegistry()[h] = 1; // refcount = 1 + ALPRHandleRegistry()[h] = { 1, false }; } // Acquire a handle for use (increment refcount). Returns the handle -// if valid, nullptr if already released. +// if valid, nullptr if already released or being destroyed. static ANSCENTER::ANSALPR* AcquireALPRHandle(ANSCENTER::ANSALPR* h) { std::lock_guard lk(ALPRHandleRegistryMutex()); auto it = ALPRHandleRegistry().find(h); if (it == ALPRHandleRegistry().end()) return nullptr; - it->second++; + if (it->second.destructionStarted) return nullptr; + it->second.refcount++; return h; } -// Release a use of the handle (decrement refcount). -// Returns true if this was the last reference (caller should destroy). +// Release a use of the handle (decrement refcount). Only signals the CV; +// the object is always deleted by Unregister, never by a stray ref-drop. static bool ReleaseALPRHandleRef(ANSCENTER::ANSALPR* h) { std::lock_guard lk(ALPRHandleRegistryMutex()); auto it = ALPRHandleRegistry().find(h); if (it == ALPRHandleRegistry().end()) return false; - it->second--; - if (it->second <= 0) { - ALPRHandleRegistry().erase(it); + it->second.refcount--; + if (it->second.refcount <= 0) { ALPRHandleRegistryCV().notify_all(); - return true; // Caller should destroy } return false; } // Unregister and wait for all in-flight uses to finish. -// Decrements the creation refcount and blocks until refcount hits 0. -// Returns true if caller should destroy the object. +// First caller takes ownership of destruction; subsequent calls return false. static bool UnregisterALPRHandle(ANSCENTER::ANSALPR* h) { std::unique_lock lk(ALPRHandleRegistryMutex()); auto it = ALPRHandleRegistry().find(h); if (it == ALPRHandleRegistry().end()) return false; - it->second--; // Remove creation ref - // Wait for in-flight inferences to finish (30s timeout as safety net) + if (it->second.destructionStarted) { + // Another thread already owns the delete — back off. + return false; + } + it->second.destructionStarted = true; + it->second.refcount--; // Remove creation ref bool ok = ALPRHandleRegistryCV().wait_for(lk, std::chrono::seconds(30), [&]() { auto it2 = ALPRHandleRegistry().find(h); - return it2 == ALPRHandleRegistry().end() || it2->second <= 0; + return it2 == ALPRHandleRegistry().end() || it2->second.refcount <= 0; }); if (!ok) { OutputDebugStringA("WARNING: UnregisterALPRHandle timed out waiting for in-flight inference\n"); diff --git a/modules/ANSMOT/dllmain.cpp b/modules/ANSMOT/dllmain.cpp index 200952a..b10382a 100644 --- a/modules/ANSMOT/dllmain.cpp +++ b/modules/ANSMOT/dllmain.cpp @@ -13,8 +13,12 @@ // Handle registry with refcount — prevents use-after-free when // ReleaseANSMOTHandle is called while an operation is still running. -static std::unordered_map& MOTHandleRegistry() { - static std::unordered_map s; +// destructionStarted: set by the first Unregister caller; blocks new Acquires +// and makes subsequent Unregister calls return false without deleting. +// Prevents double-free when Release is raced on the same handle. +struct MOTEntry { int refcount; bool destructionStarted; }; +static std::unordered_map& MOTHandleRegistry() { + static std::unordered_map s; return s; } static std::mutex& MOTHandleRegistryMutex() { @@ -28,14 +32,15 @@ static std::condition_variable& MOTHandleRegistryCV() { static void RegisterMOTHandle(ANSCENTER::ANSMOT* h) { std::lock_guard lk(MOTHandleRegistryMutex()); - MOTHandleRegistry()[h] = 1; + MOTHandleRegistry()[h] = { 1, false }; } static ANSCENTER::ANSMOT* AcquireMOTHandle(ANSCENTER::ANSMOT* h) { std::lock_guard lk(MOTHandleRegistryMutex()); auto it = MOTHandleRegistry().find(h); if (it == MOTHandleRegistry().end()) return nullptr; - it->second++; + if (it->second.destructionStarted) return nullptr; + it->second.refcount++; return h; } @@ -43,23 +48,25 @@ static bool ReleaseMOTHandleRef(ANSCENTER::ANSMOT* h) { std::lock_guard lk(MOTHandleRegistryMutex()); auto it = MOTHandleRegistry().find(h); if (it == MOTHandleRegistry().end()) return false; - it->second--; - if (it->second <= 0) { - MOTHandleRegistry().erase(it); + it->second.refcount--; + if (it->second.refcount <= 0) { MOTHandleRegistryCV().notify_all(); - return true; } - return false; + return false; // Only Unregister deletes. } static bool UnregisterMOTHandle(ANSCENTER::ANSMOT* h) { std::unique_lock lk(MOTHandleRegistryMutex()); auto it = MOTHandleRegistry().find(h); if (it == MOTHandleRegistry().end()) return false; - it->second--; + if (it->second.destructionStarted) { + return false; // Another thread already owns the delete. + } + it->second.destructionStarted = true; + it->second.refcount--; bool ok = MOTHandleRegistryCV().wait_for(lk, std::chrono::seconds(30), [&]() { auto it2 = MOTHandleRegistry().find(h); - return it2 == MOTHandleRegistry().end() || it2->second <= 0; + return it2 == MOTHandleRegistry().end() || it2->second.refcount <= 0; }); if (!ok) { OutputDebugStringA("WARNING: UnregisterMOTHandle timed out waiting for in-flight operations\n"); @@ -106,14 +113,15 @@ BOOL APIENTRY DllMain( HMODULE hModule, extern "C" __declspec(dllexport) int __cdecl CreateANSMOTHandle(ANSCENTER::ANSMOT **Handle, int motType) { if (Handle == nullptr) return 0; try { - // Release existing handle if called twice (prevents leak from LabVIEW) - if (*Handle) { - if (UnregisterMOTHandle(*Handle)) { - (*Handle)->Destroy(); - delete *Handle; - } - *Handle = nullptr; - } + // Pure constructor: ignore *Handle(in). LabVIEW's CLF Node marshalling + // reuses the same temp buffer per call site, so *Handle(in) often holds + // leftover bytes from the previous Create's output even when the actual + // LabVIEW wire is a different, freshly-allocated instance. Inspecting + // *Handle(in) and destroying what we "see" tears down legitimate + // parallel instances. (Same reasoning as CreateANSAWSHandle.) + // Trade-off: a true double-Create on the same wire leaks the prior + // handle -- caller's bug; the alternative is far worse. + *Handle = nullptr; switch (motType) { case 0: //BYTETRACKNCNN =0 diff --git a/modules/ANSOCR/dllmain.cpp b/modules/ANSOCR/dllmain.cpp index 6726a59..942d4d7 100644 --- a/modules/ANSOCR/dllmain.cpp +++ b/modules/ANSOCR/dllmain.cpp @@ -17,8 +17,12 @@ // Handle registry with refcount — prevents use-after-free when // ReleaseANSOCRHandle is called while inference is still running. -static std::unordered_map& OCRHandleRegistry() { - static std::unordered_map s; +// destructionStarted: set by the first Unregister caller; blocks new Acquires +// and makes subsequent Unregister calls return false without deleting. +// Prevents double-free when Release is raced on the same handle. +struct OCREntry { int refcount; bool destructionStarted; }; +static std::unordered_map& OCRHandleRegistry() { + static std::unordered_map s; return s; } static std::mutex& OCRHandleRegistryMutex() { @@ -32,14 +36,15 @@ static std::condition_variable& OCRHandleRegistryCV() { static void RegisterOCRHandle(ANSCENTER::ANSOCRBase* h) { std::lock_guard lk(OCRHandleRegistryMutex()); - OCRHandleRegistry()[h] = 1; // refcount = 1 + OCRHandleRegistry()[h] = { 1, false }; } static ANSCENTER::ANSOCRBase* AcquireOCRHandle(ANSCENTER::ANSOCRBase* h) { std::lock_guard lk(OCRHandleRegistryMutex()); auto it = OCRHandleRegistry().find(h); if (it == OCRHandleRegistry().end()) return nullptr; - it->second++; + if (it->second.destructionStarted) return nullptr; + it->second.refcount++; return h; } @@ -47,23 +52,25 @@ static bool ReleaseOCRHandleRef(ANSCENTER::ANSOCRBase* h) { std::lock_guard lk(OCRHandleRegistryMutex()); auto it = OCRHandleRegistry().find(h); if (it == OCRHandleRegistry().end()) return false; - it->second--; - if (it->second <= 0) { - OCRHandleRegistry().erase(it); + it->second.refcount--; + if (it->second.refcount <= 0) { OCRHandleRegistryCV().notify_all(); - return true; } - return false; + return false; // Only Unregister deletes. } static bool UnregisterOCRHandle(ANSCENTER::ANSOCRBase* h) { std::unique_lock lk(OCRHandleRegistryMutex()); auto it = OCRHandleRegistry().find(h); if (it == OCRHandleRegistry().end()) return false; - it->second--; + if (it->second.destructionStarted) { + return false; // Another thread already owns the delete. + } + it->second.destructionStarted = true; + it->second.refcount--; bool ok = OCRHandleRegistryCV().wait_for(lk, std::chrono::seconds(5), [&]() { auto it2 = OCRHandleRegistry().find(h); - return it2 == OCRHandleRegistry().end() || it2->second <= 0; + return it2 == OCRHandleRegistry().end() || it2->second.refcount <= 0; }); if (!ok) { OutputDebugStringA("WARNING: UnregisterOCRHandle timed out waiting for in-flight inference\n"); diff --git a/modules/ANSODEngine/dllmain.cpp b/modules/ANSODEngine/dllmain.cpp index 651a145..01c5048 100644 --- a/modules/ANSODEngine/dllmain.cpp +++ b/modules/ANSODEngine/dllmain.cpp @@ -193,8 +193,12 @@ static bool CheckGPUVRAM(int gpuIndex, size_t minFreeBytes = 512ULL * 1024 * 102 return true; } -static std::unordered_map& ODHandleRegistry() { - static std::unordered_map s; +// destructionStarted: set by the first Unregister caller; blocks new Acquires +// and makes subsequent Unregister calls return false without deleting. +// Prevents double-free when Release is raced on the same handle. +struct ODEntry { int refcount; bool destructionStarted; }; +static std::unordered_map& ODHandleRegistry() { + static std::unordered_map s; return s; } static std::mutex& ODHandleRegistryMutex() { @@ -208,46 +212,48 @@ static std::condition_variable& ODHandleRegistryCV() { static void RegisterODHandle(ANSCENTER::ANSODBase* h) { std::lock_guard lk(ODHandleRegistryMutex()); - ODHandleRegistry()[h] = 1; // refcount = 1 + ODHandleRegistry()[h] = { 1, false }; } // Acquire a handle for use (increment refcount). Returns the handle -// if valid, nullptr if already released. +// if valid, nullptr if already released or being destroyed. static ANSCENTER::ANSODBase* AcquireODHandle(ANSCENTER::ANSODBase* h) { std::lock_guard lk(ODHandleRegistryMutex()); auto it = ODHandleRegistry().find(h); if (it == ODHandleRegistry().end()) return nullptr; - it->second++; + if (it->second.destructionStarted) return nullptr; + it->second.refcount++; return h; } -// Release a use of the handle (decrement refcount). -// Returns true if this was the last reference (caller should destroy). +// Release a use of the handle (decrement refcount). Only signals the CV; +// the object is always deleted by Unregister, never by a stray ref-drop. static bool ReleaseODHandleRef(ANSCENTER::ANSODBase* h) { std::lock_guard lk(ODHandleRegistryMutex()); auto it = ODHandleRegistry().find(h); if (it == ODHandleRegistry().end()) return false; - it->second--; - if (it->second <= 0) { - ODHandleRegistry().erase(it); + it->second.refcount--; + if (it->second.refcount <= 0) { ODHandleRegistryCV().notify_all(); - return true; // Caller should destroy } return false; } // Unregister and wait for all in-flight uses to finish. -// Decrements the creation refcount and blocks until refcount hits 0. -// Returns true if caller should destroy the object. +// First caller takes ownership of destruction; subsequent calls return false. static bool UnregisterODHandle(ANSCENTER::ANSODBase* h) { std::unique_lock lk(ODHandleRegistryMutex()); auto it = ODHandleRegistry().find(h); if (it == ODHandleRegistry().end()) return false; - it->second--; // Remove creation ref + if (it->second.destructionStarted) { + return false; // Another thread already owns the delete. + } + it->second.destructionStarted = true; + it->second.refcount--; // Remove creation ref // Wait for in-flight inferences to finish (30s timeout as safety net) bool ok = ODHandleRegistryCV().wait_for(lk, std::chrono::seconds(30), [&]() { auto it2 = ODHandleRegistry().find(h); - return it2 == ODHandleRegistry().end() || it2->second <= 0; + return it2 == ODHandleRegistry().end() || it2->second.refcount <= 0; }); if (!ok) { OutputDebugStringA("WARNING: UnregisterODHandle timed out waiting for in-flight inference\n"); diff --git a/modules/ANSTrainingEngine/ANSTrainingEngine.cpp b/modules/ANSTrainingEngine/ANSTrainingEngine.cpp index 54b9121..c871368 100644 --- a/modules/ANSTrainingEngine/ANSTrainingEngine.cpp +++ b/modules/ANSTrainingEngine/ANSTrainingEngine.cpp @@ -20,8 +20,12 @@ // Handle registry with refcount — prevents use-after-free when // ReleaseANSTREHandle is called while an operation is still running. -static std::unordered_map& TREHandleRegistry() { - static std::unordered_map s; +// destructionStarted: set by the first Unregister caller; blocks new Acquires +// and makes subsequent Unregister calls return false without deleting. +// Prevents double-free when Release is raced on the same handle. +struct TREEntry { int refcount; bool destructionStarted; }; +static std::unordered_map& TREHandleRegistry() { + static std::unordered_map s; return s; } static std::mutex& TREHandleRegistryMutex() { @@ -35,14 +39,15 @@ static std::condition_variable& TREHandleRegistryCV() { static void RegisterTREHandle(ANSCENTER::ANSTRE* h) { std::lock_guard lk(TREHandleRegistryMutex()); - TREHandleRegistry()[h] = 1; + TREHandleRegistry()[h] = { 1, false }; } static ANSCENTER::ANSTRE* AcquireTREHandle(ANSCENTER::ANSTRE* h) { std::lock_guard lk(TREHandleRegistryMutex()); auto it = TREHandleRegistry().find(h); if (it == TREHandleRegistry().end()) return nullptr; - it->second++; + if (it->second.destructionStarted) return nullptr; + it->second.refcount++; return h; } @@ -50,23 +55,25 @@ static bool ReleaseTREHandleRef(ANSCENTER::ANSTRE* h) { std::lock_guard lk(TREHandleRegistryMutex()); auto it = TREHandleRegistry().find(h); if (it == TREHandleRegistry().end()) return false; - it->second--; - if (it->second <= 0) { - TREHandleRegistry().erase(it); + it->second.refcount--; + if (it->second.refcount <= 0) { TREHandleRegistryCV().notify_all(); - return true; } - return false; + return false; // Only Unregister deletes. } static bool UnregisterTREHandle(ANSCENTER::ANSTRE* h) { std::unique_lock lk(TREHandleRegistryMutex()); auto it = TREHandleRegistry().find(h); if (it == TREHandleRegistry().end()) return false; - it->second--; + if (it->second.destructionStarted) { + return false; // Another thread already owns the delete. + } + it->second.destructionStarted = true; + it->second.refcount--; bool ok = TREHandleRegistryCV().wait_for(lk, std::chrono::seconds(30), [&]() { auto it2 = TREHandleRegistry().find(h); - return it2 == TREHandleRegistry().end() || it2->second <= 0; + return it2 == TREHandleRegistry().end() || it2->second.refcount <= 0; }); if (!ok) { OutputDebugStringA("WARNING: UnregisterTREHandle timed out waiting for in-flight operations\n"); diff --git a/modules/ANSUtilities/dllmain.cpp b/modules/ANSUtilities/dllmain.cpp index 7789b99..49300eb 100644 --- a/modules/ANSUtilities/dllmain.cpp +++ b/modules/ANSUtilities/dllmain.cpp @@ -20,8 +20,12 @@ static void AWSDbg(const char* fmt, ...) { } // ── ANSUtilities handle registry ── -static std::unordered_map& UtilHandleRegistry() { - static std::unordered_map s; +// destructionStarted: set by the first Unregister caller; blocks new Acquires +// and causes subsequent Unregister calls to return false without deleting. +// Prevents double-free when two threads race Release on the same handle. +struct UtilEntry { int refcount; bool destructionStarted; }; +static std::unordered_map& UtilHandleRegistry() { + static std::unordered_map s; return s; } static std::mutex& UtilHandleRegistryMutex() { @@ -35,14 +39,15 @@ static std::condition_variable& UtilHandleRegistryCV() { static void RegisterUtilHandle(ANSCENTER::ANSUtilities* h) { std::lock_guard lk(UtilHandleRegistryMutex()); - UtilHandleRegistry()[h] = 1; + UtilHandleRegistry()[h] = { 1, false }; } static ANSCENTER::ANSUtilities* AcquireUtilHandle(ANSCENTER::ANSUtilities* h) { std::lock_guard lk(UtilHandleRegistryMutex()); auto it = UtilHandleRegistry().find(h); if (it == UtilHandleRegistry().end()) return nullptr; - it->second++; + if (it->second.destructionStarted) return nullptr; + it->second.refcount++; return h; } @@ -50,23 +55,26 @@ static bool ReleaseUtilHandleRef(ANSCENTER::ANSUtilities* h) { std::lock_guard lk(UtilHandleRegistryMutex()); auto it = UtilHandleRegistry().find(h); if (it == UtilHandleRegistry().end()) return false; - it->second--; - if (it->second <= 0) { - UtilHandleRegistry().erase(it); + it->second.refcount--; + if (it->second.refcount <= 0) { UtilHandleRegistryCV().notify_all(); - return true; } - return false; + return false; // Only Unregister deletes. Ref drop just signals the CV. } static bool UnregisterUtilHandle(ANSCENTER::ANSUtilities* h) { std::unique_lock lk(UtilHandleRegistryMutex()); auto it = UtilHandleRegistry().find(h); if (it == UtilHandleRegistry().end()) return false; - it->second--; + if (it->second.destructionStarted) { + // Another thread is already tearing this handle down; let it own the delete. + return false; + } + it->second.destructionStarted = true; + it->second.refcount--; bool ok = UtilHandleRegistryCV().wait_for(lk, std::chrono::seconds(30), [&]() { auto it2 = UtilHandleRegistry().find(h); - return it2 == UtilHandleRegistry().end() || it2->second <= 0; + return it2 == UtilHandleRegistry().end() || it2->second.refcount <= 0; }); if (!ok) { OutputDebugStringA("WARNING: UnregisterUtilHandle timed out waiting for in-flight operations\n"); @@ -87,8 +95,13 @@ public: }; // ── ANSAWSS3 handle registry ── -static std::unordered_map& AWSHandleRegistry() { - static std::unordered_map s; +// destructionStarted: set by the first Unregister caller; blocks new Acquires +// and causes subsequent Unregister calls to return false without deleting. +// Prevents double-free when two threads race Release on the same handle +// (e.g. LabVIEW double-Release on the same wire, or Release during heap reuse). +struct AWSEntry { int refcount; bool destructionStarted; }; +static std::unordered_map& AWSHandleRegistry() { + static std::unordered_map s; return s; } static std::mutex& AWSHandleRegistryMutex() { @@ -102,14 +115,15 @@ static std::condition_variable& AWSHandleRegistryCV() { static void RegisterAWSHandle(ANSCENTER::ANSAWSS3* h) { std::lock_guard lk(AWSHandleRegistryMutex()); - AWSHandleRegistry()[h] = 1; + AWSHandleRegistry()[h] = { 1, false }; AWSDbg("[ANSAWS] Register: handle=%p (uint=%llu) registrySize=%zu\n", (void*)h, (unsigned long long)(uintptr_t)h, AWSHandleRegistry().size()); } static bool IsAWSHandleLive(ANSCENTER::ANSAWSS3* h) { std::lock_guard lk(AWSHandleRegistryMutex()); - return AWSHandleRegistry().find(h) != AWSHandleRegistry().end(); + auto it = AWSHandleRegistry().find(h); + return it != AWSHandleRegistry().end() && !it->second.destructionStarted; } static ANSCENTER::ANSAWSS3* AcquireAWSHandle(ANSCENTER::ANSAWSS3* h) { @@ -118,16 +132,20 @@ static ANSCENTER::ANSAWSS3* AcquireAWSHandle(ANSCENTER::ANSAWSS3* h) { if (it == AWSHandleRegistry().end()) { AWSDbg("[ANSAWS] Acquire FAIL: handle=%p (uint=%llu) NOT in registry. registrySize=%zu\n", (void*)h, (unsigned long long)(uintptr_t)h, AWSHandleRegistry().size()); - // dump current registry contents so you can compare what IS there size_t i = 0; for (auto& kv : AWSHandleRegistry()) { - AWSDbg("[ANSAWS] registry[%zu] = %p (uint=%llu) refcount=%d\n", - i++, (void*)kv.first, (unsigned long long)(uintptr_t)kv.first, kv.second); + AWSDbg("[ANSAWS] registry[%zu] = %p (uint=%llu) refcount=%d destructionStarted=%d\n", + i++, (void*)kv.first, (unsigned long long)(uintptr_t)kv.first, + kv.second.refcount, kv.second.destructionStarted ? 1 : 0); } return nullptr; } - it->second++; - AWSDbg("[ANSAWS] Acquire OK: handle=%p refcount=%d\n", (void*)h, it->second); + if (it->second.destructionStarted) { + AWSDbg("[ANSAWS] Acquire FAIL: handle=%p is being destroyed (destructionStarted=true)\n", (void*)h); + return nullptr; + } + it->second.refcount++; + AWSDbg("[ANSAWS] Acquire OK: handle=%p refcount=%d\n", (void*)h, it->second.refcount); return h; } @@ -135,13 +153,11 @@ static bool ReleaseAWSHandleRef(ANSCENTER::ANSAWSS3* h) { std::lock_guard lk(AWSHandleRegistryMutex()); auto it = AWSHandleRegistry().find(h); if (it == AWSHandleRegistry().end()) return false; - it->second--; - if (it->second <= 0) { - AWSHandleRegistry().erase(it); + it->second.refcount--; + if (it->second.refcount <= 0) { AWSHandleRegistryCV().notify_all(); - return true; } - return false; + return false; // Only Unregister deletes. Ref drop just signals the CV. } static bool UnregisterAWSHandle(ANSCENTER::ANSAWSS3* h) { @@ -151,11 +167,16 @@ static bool UnregisterAWSHandle(ANSCENTER::ANSAWSS3* h) { AWSDbg("[ANSAWS] Unregister: handle=%p NOT in registry (already gone)\n", (void*)h); return false; } - AWSDbg("[ANSAWS] Unregister: handle=%p starting (refcount before=%d)\n", (void*)h, it->second); - it->second--; + if (it->second.destructionStarted) { + AWSDbg("[ANSAWS] Unregister: handle=%p already being destroyed by another thread, returning false\n", (void*)h); + return false; + } + AWSDbg("[ANSAWS] Unregister: handle=%p starting (refcount before=%d)\n", (void*)h, it->second.refcount); + it->second.destructionStarted = true; + it->second.refcount--; bool ok = AWSHandleRegistryCV().wait_for(lk, std::chrono::seconds(120), [&]() { auto it2 = AWSHandleRegistry().find(h); - return it2 == AWSHandleRegistry().end() || it2->second <= 0; + return it2 == AWSHandleRegistry().end() || it2->second.refcount <= 0; }); if (!ok) { OutputDebugStringA("WARNING: UnregisterAWSHandle timed out waiting for in-flight operations\n");