From c48500b4d484d2b182ed06306795a570f917d728 Mon Sep 17 00:00:00 2001 From: Sean McBride Date: Mon, 29 Apr 2024 16:22:40 -0400 Subject: [PATCH] BUG: fixed race condition setting m_ANTSAssociate Found with thread sanitizer (TSan) running the `itkANTSNeighborhoodCorrelationImageToImageRegistrationTest` unit test. There was already a `m_ANTSAssociateOnceFlag` flag for use with `std::call_once`, so I just used that in this place too (it's already used elsewhere). Partial backtrace from TSan: ``` WARNING: ThreadSanitizer: data race (pid=79176) Write of size 8 at 0x000108f02240 by thread T2: #0 void itk::ANTSNeighborhoodCorrelationImageToImageMetricv4GetValueAndDerivativeThreader, itk::Image, itk::Image, double, itk::DefaultImageToImageMetricTraitsv4, itk::Image, itk::Image, double>>, itk::ANTSNeighborhoodCorrelationImageToImageMetricv4, itk::Image, itk::Image, double, itk::DefaultImageToImageMetricTraitsv4, itk::Image, itk::Image, double>>>::ThreadedExecution_impl(itk::IdentityHelper, itk::Index<2u> const&, unsigned int) itkANTSNeighborhoodCorrelationImageToImageMetricv4GetValueAndDerivativeThreader.hxx:118 (ITKMetricsv4TestDriver:arm64+0x1004bfe50) #1 itk::ANTSNeighborhoodCorrelationImageToImageMetricv4GetValueAndDerivativeThreader, itk::Image, itk::Image, double, itk::DefaultImageToImageMetricTraitsv4, itk::Image, itk::Image, double>>, itk::ANTSNeighborhoodCorrelationImageToImageMetricv4, itk::Image, itk::Image, double, itk::DefaultImageToImageMetricTraitsv4, itk::Image, itk::Image, double>>>::ThreadedExecution(itk::Index<2u> const&, unsigned int) itkANTSNeighborhoodCorrelationImageToImageMetricv4GetValueAndDerivativeThreader.h:235 (ITKMetricsv4TestDriver:arm64+0x1004bf9c0) #2 itk::DomainThreader, itk::Image, itk::Image, double, itk::DefaultImageToImageMetricTraitsv4, itk::Image, itk::Image, double>>>::ThreaderCallback(void*) itkDomainThreader.hxx:123 (ITKMetricsv4TestDriver:arm64+0x100259b6c) #3 std::__1::future::type> itk::ThreadPool::AddWork(void* (*&)(void*), itk::PoolMultiThreader::ThreadPoolInfoStruct*&&)::'lambda'()::operator()() const itkThreadPool.h:92 (ITKMetricsv4TestDriver:arm64+0x1007d3228) #4 decltype(std::declval()(std::declval())) std::__1::__invoke[abi:v160006]::type> itk::ThreadPool::AddWork(void* (*&)(void*), itk::PoolMultiThreader::ThreadPoolInfoStruct*&&)::'lambda'()&>(void* (*&)(void*), itk::PoolMultiThreader::ThreadPoolInfoStruct*&&) invoke.h:394 (ITKMetricsv4TestDriver:arm64+0x1007d31a4) Previous write of size 8 at 0x000108f02240 by thread T14: #0 void itk::ANTSNeighborhoodCorrelationImageToImageMetricv4GetValueAndDerivativeThreader, itk::Image, itk::Image, double, itk::DefaultImageToImageMetricTraitsv4, itk::Image, itk::Image, double>>, itk::ANTSNeighborhoodCorrelationImageToImageMetricv4, itk::Image, itk::Image, double, itk::DefaultImageToImageMetricTraitsv4, itk::Image, itk::Image, double>>>::ThreadedExecution_impl(itk::IdentityHelper, itk::Index<2u> const&, unsigned int) itkANTSNeighborhoodCorrelationImageToImageMetricv4GetValueAndDerivativeThreader.hxx:118 (ITKMetricsv4TestDriver:arm64+0x1004bfe50) #1 itk::ANTSNeighborhoodCorrelationImageToImageMetricv4GetValueAndDerivativeThreader, itk::Image, itk::Image, double, itk::DefaultImageToImageMetricTraitsv4, itk::Image, itk::Image, double>>, itk::ANTSNeighborhoodCorrelationImageToImageMetricv4, itk::Image, itk::Image, double, itk::DefaultImageToImageMetricTraitsv4, itk::Image, itk::Image, double>>>::ThreadedExecution(itk::Index<2u> const&, unsigned int) itkANTSNeighborhoodCorrelationImageToImageMetricv4GetValueAndDerivativeThreader.h:235 (ITKMetricsv4TestDriver:arm64+0x1004bf9c0) #2 itk::DomainThreader, itk::Image, itk::Image, double, itk::DefaultImageToImageMetricTraitsv4, itk::Image, itk::Image, double>>>::ThreaderCallback(void*) itkDomainThreader.hxx:123 (ITKMetricsv4TestDriver:arm64+0x100259b6c) #3 std::__1::future::type> itk::ThreadPool::AddWork(void* (*&)(void*), itk::PoolMultiThreader::ThreadPoolInfoStruct*&&)::'lambda'()::operator()() const itkThreadPool.h:92 (ITKMetricsv4TestDriver:arm64+0x1007d3228) #4 decltype(std::declval()(std::declval())) std::__1::__invoke[abi:v160006]::type> itk::ThreadPool::AddWork(void* (*&)(void*), itk::PoolMultiThreader::ThreadPoolInfoStruct*&&)::'lambda'()&>(void* (*&)(void*), itk::PoolMultiThreader::ThreadPoolInfoStruct*&&) invoke.h:394 (ITKMetricsv4TestDriver:arm64+0x1007d31a4) ``` --- ...mageMetricv4GetValueAndDerivativeThreader.hxx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Modules/Registration/Metricsv4/include/itkANTSNeighborhoodCorrelationImageToImageMetricv4GetValueAndDerivativeThreader.hxx b/Modules/Registration/Metricsv4/include/itkANTSNeighborhoodCorrelationImageToImageMetricv4GetValueAndDerivativeThreader.hxx index 5933c90efae..69b3a9c53c7 100644 --- a/Modules/Registration/Metricsv4/include/itkANTSNeighborhoodCorrelationImageToImageMetricv4GetValueAndDerivativeThreader.hxx +++ b/Modules/Registration/Metricsv4/include/itkANTSNeighborhoodCorrelationImageToImageMetricv4GetValueAndDerivativeThreader.hxx @@ -113,14 +113,16 @@ ANTSNeighborhoodCorrelationImageToImageMetricv4GetValueAndDerivativeThreader< const DomainType & domain, const ThreadIdType threadId) { - /* call base method */ - /* Store the casted pointer to avoid dynamic casting in tight loops. */ - this->m_ANTSAssociate = dynamic_cast(this->m_Associate); - if (this->m_ANTSAssociate == nullptr) - { - itkExceptionMacro("Dynamic casting of associate pointer failed."); - } + std::call_once(this->m_ANTSAssociateOnceFlag, [this]() { + /* Store the casted pointer to avoid dynamic casting in tight loops. */ + this->m_ANTSAssociate = dynamic_cast(this->m_Associate); + if (this->m_ANTSAssociate == nullptr) + { + itkExceptionMacro("Dynamic casting of associate pointer failed."); + } + }); + /* call base method */ Superclass::ThreadedExecution(domain, threadId); }