Commit 7472d37a by Ralf W. Grosse-Kunstleve Committed by GitHub

Adding iostream.h thread-safety documentation. (#2995)

* Adding iostream.h thread-safety documentation.

* Restoring `TestThread` code with added `std::lock_guard<std::mutex>`.

* Updating new comments to reflect new information.

* Fixing up `git rebase -X theirs` accidents.
parent 2d468697
...@@ -47,6 +47,17 @@ redirects output to the corresponding Python streams: ...@@ -47,6 +47,17 @@ redirects output to the corresponding Python streams:
call_noisy_func(); call_noisy_func();
}); });
.. warning::
The implementation in ``pybind11/iostream.h`` is NOT thread safe. Multiple
threads writing to a redirected ostream concurrently cause data races
and potentially buffer overflows. Therefore it is currrently a requirement
that all (possibly) concurrent redirected ostream writes are protected by
a mutex. #HelpAppreciated: Work on iostream.h thread safety. For more
background see the discussions under
`PR #2982 <https://github.com/pybind/pybind11/pull/2982>`_ and
`PR #2995 <https://github.com/pybind/pybind11/pull/2995>`_.
This method respects flushes on the output streams and will flush if needed This method respects flushes on the output streams and will flush if needed
when the scoped guard is destroyed. This allows the output to be redirected in when the scoped guard is destroyed. This allows the output to be redirected in
real time, such as to a Jupyter notebook. The two arguments, the C++ stream and real time, such as to a Jupyter notebook. The two arguments, the C++ stream and
......
...@@ -5,6 +5,16 @@ ...@@ -5,6 +5,16 @@
All rights reserved. Use of this source code is governed by a All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file. BSD-style license that can be found in the LICENSE file.
WARNING: The implementation in this file is NOT thread safe. Multiple
threads writing to a redirected ostream concurrently cause data races
and potentially buffer overflows. Therefore it is currrently a requirement
that all (possibly) concurrent redirected ostream writes are protected by
a mutex.
#HelpAppreciated: Work on iostream.h thread safety.
For more background see the discussions under
https://github.com/pybind/pybind11/pull/2982 and
https://github.com/pybind/pybind11/pull/2995.
*/ */
#pragma once #pragma once
...@@ -85,30 +95,25 @@ private: ...@@ -85,30 +95,25 @@ private:
return remainder; return remainder;
} }
// This function must be non-virtual to be called in a destructor. If the // This function must be non-virtual to be called in a destructor.
// rare MSVC test failure shows up with this version, then this should be
// simplified to a fully qualified call.
int _sync() { int _sync() {
if (pbase() != pptr()) { // If buffer is not empty if (pbase() != pptr()) { // If buffer is not empty
gil_scoped_acquire tmp; gil_scoped_acquire tmp;
// Placed inside gil_scoped_acquire as a mutex to avoid a race. // This subtraction cannot be negative, so dropping the sign.
if (pbase() != pptr()) { // Check again under the lock auto size = static_cast<size_t>(pptr() - pbase());
// This subtraction cannot be negative, so dropping the sign. size_t remainder = utf8_remainder();
auto size = static_cast<size_t>(pptr() - pbase());
size_t remainder = utf8_remainder(); if (size > remainder) {
str line(pbase(), size - remainder);
if (size > remainder) { pywrite(line);
str line(pbase(), size - remainder); pyflush();
pywrite(line);
pyflush();
}
// Copy the remainder at the end of the buffer to the beginning:
if (remainder > 0)
std::memmove(pbase(), pptr() - remainder, remainder);
setp(pbase(), epptr());
pbump(static_cast<int>(remainder));
} }
// Copy the remainder at the end of the buffer to the beginning:
if (remainder > 0)
std::memmove(pbase(), pptr() - remainder, remainder);
setp(pbase(), epptr());
pbump(static_cast<int>(remainder));
} }
return 0; return 0;
} }
......
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
#include "pybind11_tests.h" #include "pybind11_tests.h"
#include <atomic> #include <atomic>
#include <iostream> #include <iostream>
#include <mutex>
#include <string>
#include <thread> #include <thread>
void noisy_function(const std::string &msg, bool flush) { void noisy_function(const std::string &msg, bool flush) {
...@@ -35,8 +37,18 @@ void noisy_funct_dual(const std::string &msg, const std::string &emsg) { ...@@ -35,8 +37,18 @@ void noisy_funct_dual(const std::string &msg, const std::string &emsg) {
struct TestThread { struct TestThread {
TestThread() : stop_{false} { TestThread() : stop_{false} {
auto thread_f = [this] { auto thread_f = [this] {
static std::mutex cout_mutex;
while (!stop_) { while (!stop_) {
std::cout << "x" << std::flush; {
// #HelpAppreciated: Work on iostream.h thread safety.
// Without this lock, the clang ThreadSanitizer (tsan) reliably reports a
// data race, and this test is predictably flakey on Windows.
// For more background see the discussion under
// https://github.com/pybind/pybind11/pull/2982 and
// https://github.com/pybind/pybind11/pull/2995.
const std::lock_guard<std::mutex> lock(cout_mutex);
std::cout << "x" << std::flush;
}
std::this_thread::sleep_for(std::chrono::microseconds(50)); std::this_thread::sleep_for(std::chrono::microseconds(50));
} }; } };
t_ = new std::thread(std::move(thread_f)); t_ = new std::thread(std::move(thread_f));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment