Skip to content

Commit 0001be5

Browse files
authored
fix(sim): close_gui segfault (#290)
close_gui in sim doesnt segfault anymore by checking if the gui still has a value in the optional variable which will be set to None if the gui gets closed/deregistered
1 parent 33b8a04 commit 0001be5

6 files changed

Lines changed: 60 additions & 15 deletions

File tree

python/rcs/sim/sim.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import atexit
2+
import contextlib
23
import multiprocessing as mp
34
import uuid
45
from logging import getLogger
@@ -63,6 +64,7 @@ def __init__(self, mjmdl: str | PathLike | ModelComposer, cfg: SimConfig | None
6364
self._gui_client: Optional[_GuiClient] = None
6465
self._gui_process: Optional[mp.context.SpawnProcess] = None
6566
self._stop_event: Optional[EventClass] = None
67+
self._gui_atexit_registered = False
6668
if cfg is not None:
6769
self.set_config(cfg)
6870

@@ -72,17 +74,26 @@ def close_gui(self):
7274
if self._gui_process is not None:
7375
self._gui_process.join()
7476
self._stop_gui_server()
77+
self._gui_uuid = None
78+
self._gui_client = None
79+
self._gui_process = None
80+
self._stop_event = None
81+
if self._gui_atexit_registered:
82+
with contextlib.suppress(ValueError):
83+
atexit.unregister(self.close_gui)
84+
self._gui_atexit_registered = False
7585

7686
def open_gui(self):
7787
if self._gui_uuid is None:
7888
self._gui_uuid = "rcs_" + str(uuid.uuid4())
7989
self._start_gui_server(self._gui_uuid)
80-
if self._gui_client is None:
81-
ctx = mp.get_context("spawn")
82-
self._stop_event = ctx.Event()
83-
self._gui_process = ctx.Process(
90+
if self._gui_process is None or not self._gui_process.is_alive():
91+
self._stop_event = self._mp_context.Event()
92+
self._gui_process = self._mp_context.Process(
8493
target=gui_loop,
8594
args=(self._gui_uuid, self._stop_event),
8695
)
8796
self._gui_process.start()
88-
atexit.register(self.close_gui)
97+
if not self._gui_atexit_registered:
98+
atexit.register(self.close_gui)
99+
self._gui_atexit_registered = True

python/tests/test_sim_gui.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import multiprocessing
2+
from pathlib import Path
3+
from uuid import uuid4
4+
5+
REPO_ROOT = Path(__file__).resolve().parents[2]
6+
7+
8+
def _gui_server_stop_then_step():
9+
import os
10+
11+
os.chdir(REPO_ROOT)
12+
from rcs.sim import Sim
13+
14+
sim = Sim(Path("assets/scenes/empty_world/scene.xml"))
15+
sim._gui_uuid = "rcs_test_" + str(uuid4())
16+
sim._start_gui_server(sim._gui_uuid)
17+
sim.step(2)
18+
sim.close_gui()
19+
sim.step(1)
20+
21+
22+
def test_gui_server_can_be_stopped_before_later_steps():
23+
ctx = multiprocessing.get_context("spawn")
24+
proc = ctx.Process(target=_gui_server_stop_then_step)
25+
proc.start()
26+
proc.join(timeout=10)
27+
assert proc.exitcode == 0, f"process exited with {proc.exitcode}"

src/sim/gui.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ class GuiClient {
7575
private:
7676
mjModel* m;
7777
mjData* d;
78-
const std::string& id;
78+
const std::string id;
7979
struct shm shm;
8080
};
8181

src/sim/gui_client.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ namespace sim {
99
using namespace boost::interprocess;
1010

1111
GuiClient::GuiClient(const std::string& id)
12-
: shm{.manager{open_only, id.c_str()},
13-
.state_lock{open_only, (id + STATE_LOCK_POSTFIX).c_str()},
14-
.info_lock{open_only, (id + INFO_LOCK_POSTFIX).c_str()}},
12+
: m{nullptr},
13+
d{nullptr},
1514
id{id},
16-
m{nullptr},
17-
d{nullptr} {
15+
shm{.manager{open_only, id.c_str()},
16+
.state_lock{open_only, (id + STATE_LOCK_POSTFIX).c_str()},
17+
.info_lock{open_only, (id + INFO_LOCK_POSTFIX).c_str()}} {
1818
// setup shared memory
1919
std::tie(this->shm.info_byte, std::ignore) =
2020
this->shm.manager.find<bool>(INFO_BYTE);

src/sim/sim.cpp

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -176,11 +176,17 @@ void Sim::start_gui_server(const std::string& id) {
176176
throw std::runtime_error("Start gui server should be called only once.");
177177
}
178178
this->gui.emplace(this->m, this->d, id);
179-
this->register_cb(
180-
std::bind(&GuiServer::update_mjdata_callback, &this->gui.value()), 0);
179+
if (!this->gui_callback_registered) {
180+
this->register_cb(
181+
[this]() {
182+
if (this->gui.has_value()) {
183+
this->gui->update_mjdata_callback();
184+
}
185+
},
186+
0);
187+
this->gui_callback_registered = true;
188+
}
181189
}
182-
// TODO: when stop_gui_server is called, the callback still exists but now
183-
// points to an non existing gui
184190
void Sim::stop_gui_server() { this->gui.reset(); }
185191
} // namespace sim
186192
} // namespace rcs

src/sim/sim.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ class Sim {
6868
size_t convergence_steps = 0;
6969
bool converged = true;
7070
std::optional<GuiServer> gui;
71+
bool gui_callback_registered = false;
7172

7273
public:
7374
// TODO: hide m & d, pass as parameter to callback (easier refactoring)

0 commit comments

Comments
 (0)