Fix GIL deadlock in Python viewer methods
Removed the GIL lock in set_figures, clear_figures, set_texts, clear_texts, set_images, and clear_images PiperOrigin-RevId: 861245200 Change-Id: I52f21d82f9813752c7cb357213c24ea6188583db
This commit is contained in:
committed by
Copybara-Service
parent
5fc6ed4eda
commit
e977b0d660
+39
-27
@@ -13,7 +13,6 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <chrono> // NOLINT(build/c++11)
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
@@ -103,10 +102,7 @@ class SimulateWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WaitUntilExit() {
|
void WaitUntilExit() {
|
||||||
// TODO: replace with atomic wait when we migrate to C++20
|
WaitForAtomicNoGil(simulate_->exitrequest, 2);
|
||||||
while (simulate_ && simulate_->exitrequest.load() != 2) {
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Load(py::object m, py::object d, const std::string& path) {
|
void Load(py::object m, py::object d, const std::string& path) {
|
||||||
@@ -139,17 +135,16 @@ class SimulateWrapper {
|
|||||||
|
|
||||||
void SetFigures(
|
void SetFigures(
|
||||||
const std::vector<std::pair<mjrRect, py::object>>& viewports_figures) {
|
const std::vector<std::pair<mjrRect, py::object>>& viewports_figures) {
|
||||||
|
if (WaitForAtomicNoGil(simulate_->newfigurerequest, 0)) {
|
||||||
// TODO: replace with atomic wait when we migrate to C++20
|
return;
|
||||||
while (simulate_ && simulate_->newfigurerequest.load() != 0) {
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pairs of [viewport, figure], where viewport corresponds to the location
|
// Pairs of [viewport, figure], where viewport corresponds to the location
|
||||||
// of the figure on the viewer window.
|
// of the figure on the viewer window.
|
||||||
for (const auto& [viewport, figure] : viewports_figures) {
|
for (const auto& [viewport, figure] : viewports_figures) {
|
||||||
mjvFigure casted_figure = *figure.cast<MjvFigureWrapper&>().get();
|
mjvFigure casted_figure = *figure.cast<MjvFigureWrapper&>().get();
|
||||||
simulate_->user_figures_new_.push_back(std::make_pair(viewport, casted_figure));
|
simulate_->user_figures_new_.push_back(
|
||||||
|
std::make_pair(viewport, casted_figure));
|
||||||
}
|
}
|
||||||
|
|
||||||
int value = 0;
|
int value = 0;
|
||||||
@@ -157,9 +152,8 @@ class SimulateWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ClearFigures() {
|
void ClearFigures() {
|
||||||
// TODO: replace with atomic wait when we migrate to C++20
|
if (WaitForAtomicNoGil(simulate_->newfigurerequest, 0)) {
|
||||||
while (simulate_ && simulate_->newfigurerequest.load() != 0) {
|
return;
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
simulate_->user_figures_new_.clear();
|
simulate_->user_figures_new_.clear();
|
||||||
@@ -171,14 +165,14 @@ class SimulateWrapper {
|
|||||||
void SetTexts(
|
void SetTexts(
|
||||||
const std::vector<std::tuple<int, int, std::string, std::string>>&
|
const std::vector<std::tuple<int, int, std::string, std::string>>&
|
||||||
texts) {
|
texts) {
|
||||||
// TODO: replace with atomic wait when we migrate to C++20
|
if (WaitForAtomicNoGil(simulate_->newtextrequest, 0)) {
|
||||||
while (simulate_ && simulate_->newtextrequest.load() != 0) {
|
return;
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collection of [font, gridpos, text1, text2] tuples for overlay text
|
// Collection of [font, gridpos, text1, text2] tuples for overlay text
|
||||||
for (const auto& [font, gridpos, text1, text2] : texts) {
|
for (const auto& [font, gridpos, text1, text2] : texts) {
|
||||||
simulate_->user_texts_new_.push_back(std::make_tuple(font, gridpos, text1, text2));
|
simulate_->user_texts_new_.push_back(
|
||||||
|
std::make_tuple(font, gridpos, text1, text2));
|
||||||
}
|
}
|
||||||
|
|
||||||
int value = 0;
|
int value = 0;
|
||||||
@@ -186,9 +180,8 @@ class SimulateWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ClearTexts() {
|
void ClearTexts() {
|
||||||
// TODO: replace with atomic wait when we migrate to C++20
|
if (WaitForAtomicNoGil(simulate_->newtextrequest, 0)) {
|
||||||
while (simulate_ && simulate_->newtextrequest.load() != 0) {
|
return;
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
simulate_->user_texts_new_.clear();
|
simulate_->user_texts_new_.clear();
|
||||||
@@ -200,9 +193,8 @@ class SimulateWrapper {
|
|||||||
void SetImages(
|
void SetImages(
|
||||||
const std::vector<std::tuple<mjrRect, pybind11::array>> viewports_images
|
const std::vector<std::tuple<mjrRect, pybind11::array>> viewports_images
|
||||||
) {
|
) {
|
||||||
// TODO: replace with atomic wait when we migrate to C++20
|
if (WaitForAtomicNoGil(simulate_->newimagerequest, 0)) {
|
||||||
while (simulate_ && simulate_->newimagerequest.load() != 0) {
|
return;
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& [viewport, image] : viewports_images) {
|
for (const auto& [viewport, image] : viewports_images) {
|
||||||
@@ -227,7 +219,8 @@ class SimulateWrapper {
|
|||||||
std::unique_ptr<unsigned char[]> image_copy(new unsigned char[size]());
|
std::unique_ptr<unsigned char[]> image_copy(new unsigned char[size]());
|
||||||
std::memcpy(image_copy.get(), buf.ptr, size);
|
std::memcpy(image_copy.get(), buf.ptr, size);
|
||||||
|
|
||||||
simulate_->user_images_new_.push_back(std::make_tuple(viewport, std::move(image_copy)));
|
simulate_->user_images_new_.push_back(
|
||||||
|
std::make_tuple(viewport, std::move(image_copy)));
|
||||||
}
|
}
|
||||||
|
|
||||||
int value = 0;
|
int value = 0;
|
||||||
@@ -235,9 +228,8 @@ class SimulateWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ClearImages() {
|
void ClearImages() {
|
||||||
// TODO: replace with atomic wait when we migrate to C++20
|
if (WaitForAtomicNoGil(simulate_->newimagerequest, 0)) {
|
||||||
while (simulate_ && simulate_->newimagerequest.load() != 0) {
|
return;
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
simulate_->user_images_new_.clear();
|
simulate_->user_images_new_.clear();
|
||||||
@@ -247,6 +239,26 @@ class SimulateWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// Waits for an atomic value to become the expected value, releasing the GIL
|
||||||
|
// during the wait to prevent deadlock with render thread's key callback which
|
||||||
|
// needs to acquire the GIL. Returns true if simulate_ is null i.e. the
|
||||||
|
// viewer has been destroyed during the wait and the caller should return.
|
||||||
|
bool WaitForAtomicNoGil(std::atomic_int& atomic, int expected) {
|
||||||
|
if (simulate_) {
|
||||||
|
py::gil_scoped_release no_gil;
|
||||||
|
while (atomic.load() != expected) {
|
||||||
|
// TODO(robotics-simulation): replace with `atomic.wait(expected)` when
|
||||||
|
// we migrate python bindings to C++20 (we may need to drop GCC 10).
|
||||||
|
std::this_thread::yield();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-check after waiting because releasing the GIL allows other threads to
|
||||||
|
// run, including the thread which handles window close, hence simulate_
|
||||||
|
// could become invalid during the wait.
|
||||||
|
return simulate_ == nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
mujoco::Simulate* simulate_;
|
mujoco::Simulate* simulate_;
|
||||||
std::atomic_int destroyed_ = 0;
|
std::atomic_int destroyed_ = 0;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user