Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

y-guyon
Copy link
Contributor

@y-guyon y-guyon commented Jan 27, 2023

Omit the first check of the double-checked locking pattern in recordException() in parallel.cpp when CV_THREAD_SANITIZER is defined. This should only slow recordException() down when the thread sanitizer is used, and avoids the TSAN data race warning.

CV_THREAD_SANITIZER is defined in cvdef.h which is included by core.hpp which is included by utility.hpp which is included by precomp.hpp which is included by parallel.cpp. If this is too indirect, #include "cvdef.h" can be added in parallel.cpp.

Original report

bool hasException;
#if CV__EXCEPTION_PTR
std::exception_ptr pException;
#else
cv::String exception_message;
#endif
#if CV__EXCEPTION_PTR
void recordException()
#else
void recordException(const cv::String& msg)
#endif
{
if (!hasException)
{
cv::AutoLock lock(cv::getInitializationMutex());
if (!hasException)
{
hasException = true;
#if CV__EXCEPTION_PTR
pException = std::current_exception();
#else
exception_message = msg;
#endif
}
}
}

Clang's thread sanitizer found the following when running an internal fuzz target:

WARNING: ThreadSanitizer: data race (pid=8714)
Read of size 1 by thread T6:
    #0 cv::(anonymous namespace)::ParallelLoopBodyWrapperContext::recordException() OpenCV/modules/core/src/parallel.cpp:278
    #1 cv::(anonymous namespace)::ParallelLoopBodyWrapper::operator()(cv::Range const&) const third_party/OpenCV/modules/core/src/parallel.cpp:357
Previous write of size 1 at 0x7ffc7c20ee58 by main thread (mutexes: write M0):
    #0 cv::(anonymous namespace)::ParallelLoopBodyWrapperContext::recordException() third_party/OpenCV/modules/core/src/parallel.cpp:283
    #1 cv::(anonymous namespace)::ParallelLoopBodyWrapper::operator()(cv::Range const&) const third_party/OpenCV/modules/core/src/parallel.cpp:357

Location is stack of main thread.
Location is global '??'

Mutex M0 created at:
    #3 cv::getInitializationMutex() third_party/OpenCV/modules/core/src/system.cpp:93
Thread T6 (running) created by main thread at:
    #1 cv::WorkerThread::WorkerThread(cv::ThreadPool&, unsigned int) third_party/OpenCV/modules/core/src/parallel_impl.cpp:241

SUMMARY: ThreadSanitizer: data race third_party/OpenCV/modules/core/src/parallel.cpp:278 in cv::(anonymous namespace)::ParallelLoopBodyWrapperContext::recordException()

I believe it is a false positive in this case. As long as pException is not accessed until all threads are joined (in finalize() only), I do not see how the lack of order guarantee in the body of the second !hasException condition is an issue (unlike the anti-pattern DCLP singleton initialization).

In other words, I think the code at head works as intended as long as C++ guarantees the following, and I guess it does:

  • The second if (!hasException) is not optimized out by the first if (!hasException)
  • std::lock_guard happens before the second if (!hasException)
  • The second if (!hasException) happens before hasException = true;
  • The second if (!hasException) happens before exception = std::current_exception();

TSAN may be triggered because it detects a read during a write, or maybe it sees a branching on the first if (!hasException) that differs depending on the run. But the data race here has no effect: it is the purpose of the double-checked locking pattern.

Alternatives

The current solution is simple but I considered other options. Feel free to pick any or suggest another.

  • Just remove the first check all the time because recordException() is not performance-critical.
  • std::call_once():
    bool hasException;
    std::once_flag flagException;
    std::exception_ptr pException;
    void recordException()
    {
        std::call_once(flagException, [this] {
            hasException = true;
            pException = std::current_exception();
        });
    }
  • Something similar to https://preshing.com/20130930/double-checked-locking-is-fixed-in-cpp11/

Related

Pull Request Readiness Checklist

See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request

  • I agree to contribute to the project under Apache 2 License.
  • To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV
  • The PR is proposed to the proper branch
  • There is a reference to the original bug report and related work

Omit the first check of the double-checked locking pattern in
recordException() in parallel.cpp when CV_THREAD_SANITIZER is defined.
This should only slow recordException() down when the thread sanitizer
is used, and avoids the TSAN data race warning.
@y-guyon
Copy link
Contributor Author

y-guyon commented Jan 27, 2023

https://godbolt.org/z/4Tfjden3r shows that the second if (!hasException) is not optimized out by the first one thanks to the lock.

Copy link
Member

@alalek alalek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

@opencv-pushbot opencv-pushbot merged commit bd9d60c into opencv:3.4 Jan 27, 2023
This was referenced Jan 28, 2023
@asmorkalov asmorkalov mentioned this pull request May 31, 2023
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.

3 participants