FhSim  3.1.0
Marine systems simulation
Loading...
Searching...
No Matches
0041 — SimObject render teardown runs after the Ogre root is destroyed
ID 0041
Class ARCHITECTURE
Severity 2
Status blocked
Models Auv/Vehicle, Auv/Echosounder
Found 2026-09-23
Decision needed Whether FhSim core should gain a RenderRelease hook, or whether every SimObject is expected to guard its own destructor.

Evidence

The visualisation interface in fhsim/include/fhsim/simobject/SimObjectInclude.h:303-317 is exactly two virtuals:

#ifdef FH_VISUALIZATION
virtual void RenderInit(Ogre::Root* ogreRoot, ISimObjectCreator* creator) = 0;
virtual void RenderUpdate(double T, const double* X) = 0;
#endif

There is no counterpart to RenderInit, so the only place a SimObject can release the scene nodes and movable objects it created is its destructor. That destructor runs too late. Adding the obvious release to Auv::~Auv and running the AUV survey scenario under FhVis segfaults on the way out:

Thread 1 "FhVis" received signal SIGSEGV, Segmentation fault.
#0 0x0000000000000000 in ?? ()
#1 Auv::~Auv () from SimObjectLibraries/libfhsim_fisheryVis.so
#3 ModelAssemblyResult::~ModelAssemblyResult ()
#4 ModelStructure::~ModelStructure ()
#6 fhsim::IntegratorStateProvider::~IntegratorStateProvider ()
#8 std::unique_ptr<fhsim::SimulationManager>::~unique_ptr ()
#9 main ()

The null vtable entry is the Ogre::SceneManager the SimObject cached in RenderInit: the visualisation observer, and with it the Ogre root, is destroyed before ModelStructure destroys the SimObjects, so every scene pointer a SimObject holds is dangling by the time its destructor runs.

fhsim_environment/src/environment/visual/ScalarVolume.cpp:101-119 releases its node and its ManualObject the same unguarded way, and is reachable from the same teardown, so the same crash is latent there.

Effect

Any SimObject that creates Ogre resources and then frees them the way the rest of the code base does crashes the host at shutdown — after a successful run, so the simulation results are not affected, but the process exits on a signal and any exit code a script reads is meaningless. The alternative, not freeing anything, leaks the geometry for the lifetime of the process, which is harmless for a one-shot run and is not harmless for a host that builds and tears down several models.

Auv and SensorEchosounder work around it with a SceneIsStillAlive() guard (src/auv/Auv.cpp:31-50, src/auv/SensorEchosounder.cpp:57-79) that asks Ogre::Root::getSingletonPtr() whether it still has the "main" scene manager and does nothing when it does not. That is correct but it is a workaround every library has to rediscover, and it silently becomes a leak rather than a release.

Possible fix

  • Add a RenderRelease() virtual to the SimObject interface, called by ModelStructure before the visualisation observer drops the Ogre root, with an empty default so that no existing model has to change. This is the fix that makes the guard unnecessary.
  • Or destroy the SimObjects before the Ogre root, which is a change to the ownership order in the host and touches every application rather than the interface.
  • Or document the guard as the house rule and apply it in ScalarVolume too.

The choice belongs to the FhSim core owner, which is why this is blocked rather than ready.

Test that would prove it

Run FhVis on a scenario containing an Auv with the guard removed, with <Visualization/> as an observer, and check the exit status. It is 139 with the release unguarded and 0 with it guarded. A core test would assert that RenderRelease is called while Ogre::Root::getSingletonPtr() is still non-null.

Risk

Leaving it as it stands risks nothing in the simulation and costs a workaround in every visualised SimObject. Changing the teardown order in the host is the riskier of the two fixes; adding a hook is additive.