FhSim  3.1.0
Marine systems simulation
Loading...
Searching...
No Matches
0204 — On Windows, unloading the engine DLL races libxml2's thread cleanup and can crash at FMU release
ID 0204
Class BUG
Severity 2
Status ready

Models: —

Found: 2026-10-26 Windows round of feature/macos (MSVC 19.44, Debug, with visualization)

Ruling (owner, 2026-10-26): file now, fix later.

Evidence

fmu-directional-derivative.FirstOrderStateFixture3.SerializedStateContinuesInASecondInstance reports [ OK ] and then the process ends with an access violation (ctest: Exception: SegFault). Once in about 60 Windows ctest logs. Reproduced with ctest -C Debug -R fmu-directional-derivative -j 16 --repeat until-fail:10, and under cdb (crash on run 50 of the case, while ctest loaded the machine):

fhVisDll_d!xmlResetError
fhVisDll_d!xmlFreeGlobalState libxml2 2.15.4 globals.c:450
fhVisDll_d!xmlGlobalStateDtor globals.c:461 (thread-pool thread)
...
ntdll!LdrUnloadDll (main thread)
KERNELBASE!FreeLibrary
fhFMU3Dll_d!FhsimDllBridge::Unload FhsimDllBridge.cpp:136
fhFMU3Dll_d!FhsimSlaveCore::ReleaseSimulation_ FhsimSlaveCoreLifecycle.cpp:495

libxml2 is linked statically into the engine DLL. A static libxml2 on Windows (LIBXML_STATIC without LIBXML_STATIC_FOR_DLL) frees the state of each thread that used it with a wait callback (RegisterWaitForSingleObject on the thread handle, USE_WAIT_DTOR), which runs on a thread-pool thread after the thread exits. When the last instance is released, FreeLibrary can unmap the DLL before or while that callback runs.

Effect

An FMU host on Windows can crash when it frees the last FhSim FMU instance. The run itself completes; the crash is in the teardown. Seen in the test only, which brings up a second instance in the same process.

Possible fix

  • libxml2 shared on Windows: libxml2's own DllMain frees a thread's state as the thread exits, so nothing runs after the unload. libxml2.dll then has to ship with the package, the playpen and every FMU (ResourceBundler, fhsim_conan, the FMU archive checks).
  • Pin the engine DLL (GetModuleHandleEx with GET_MODULE_HANDLE_EX_FLAG_PIN) in FhsimDllBridge::Load: never unloaded, so the callback always finds its code. The DLL stays loaded until the process exits.

Test that would prove it

ctest -C Debug -R fmu-directional-derivative -j 16 --repeat until-fail:50 on Windows without a failure.

Risk

A shared libxml2 changes what an FMU carries; a pinned DLL changes what a second load after CleanupXmlParser gets.