From d6c31d0f2da3904d93f6c62483c9c23e5e8cc1df Mon Sep 17 00:00:00 2001 From: Mohcine Chraibi Date: Tue, 11 Aug 2026 11:08:42 +0200 Subject: [PATCH] Serialize HDF5 access and stop tearing down the library The bundled HDF5 is built without thread safety (Threadsafety: OFF in libhdf5.settings), yet geometry and trajectory loading can read the same .h5 concurrently from different threads. Worse, CloseFile(), OpenFile()'s error path and DetectFormat() called H5close(), which shuts down the HDF5 library process-wide and crashes any reader that is mid-operation. Observed as a SIGSEGV at address 0x20 when one thread ran H5close() while another was still inside H5open(): Hdf5DataPlugin!H5P__access_class -> H5P_create_id -> H5T_init -> H5_init_library -> H5open Hdf5DataPlugin!FHdf5SimulationReader::OpenFile ProjectMobius!FProcessSimulationDataRunnable::LoadAndDeserializeHDF5File - Guard every public reader method with a process-wide recursive critical section. - Remove all H5close() calls; the library now lives for the process. H5Fclose() still closes individual files. --- .../Private/Hdf5SimulationReader.cpp | 49 +++++++++++++++++-- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/UnrealFolder/ProjectMobius/Plugins/Hdf5DataPlugin/Source/Hdf5DataPlugin/Private/Hdf5SimulationReader.cpp b/UnrealFolder/ProjectMobius/Plugins/Hdf5DataPlugin/Source/Hdf5DataPlugin/Private/Hdf5SimulationReader.cpp index 5acbaece0..96c3333a3 100644 --- a/UnrealFolder/ProjectMobius/Plugins/Hdf5DataPlugin/Source/Hdf5DataPlugin/Private/Hdf5SimulationReader.cpp +++ b/UnrealFolder/ProjectMobius/Plugins/Hdf5DataPlugin/Source/Hdf5DataPlugin/Private/Hdf5SimulationReader.cpp @@ -23,9 +23,26 @@ #include "Hdf5SimulationReader.h" #include "Misc/Paths.h" +#include "Misc/ScopeLock.h" DEFINE_LOG_CATEGORY_STATIC(LogHdf5SimulationReader, Log, All); +namespace +{ + /** + * The bundled HDF5 library is built without thread safety (Threadsafety: OFF in + * libhdf5.settings), so no two threads may execute HDF5 code concurrently. + * Geometry and trajectory loading both read the same .h5 from different threads, + * so every public reader method serializes on this process-wide lock. + * The lock is recursive on all UE platforms, so public methods may call each other. + */ + FCriticalSection& GetHdf5LibraryLock() + { + static FCriticalSection Hdf5LibraryLock; + return Hdf5LibraryLock; + } +} + FHdf5SimulationReader::FHdf5SimulationReader() { } @@ -37,6 +54,8 @@ FHdf5SimulationReader::~FHdf5SimulationReader() bool FHdf5SimulationReader::OpenFile(const FString& FilePath) { + FScopeLock Hdf5Guard(&GetHdf5LibraryLock()); + // Close any previously open file CloseFile(); @@ -58,7 +77,8 @@ bool FHdf5SimulationReader::OpenFile(const FString& FilePath) if (FileId < 0) { UE_LOG(LogHdf5SimulationReader, Error, TEXT("Failed to open HDF5 file: %s"), *FilePath); - H5close(); + // Do not call H5close() here: it tears down the HDF5 library process-wide, + // crashing any other reader that is mid-operation. return false; } @@ -111,10 +131,13 @@ bool FHdf5SimulationReader::OpenFile(const FString& FilePath) void FHdf5SimulationReader::CloseFile() { + FScopeLock Hdf5Guard(&GetHdf5LibraryLock()); + if (FileId >= 0) { H5Fclose(FileId); - H5close(); + // Deliberately no H5close(): that shuts down the HDF5 library for the whole + // process, not just this file, and other readers may still be active. FileId = -1; DetectedFormat = EHdf5FormatType::Unknown; TimestepCount = 0; @@ -276,6 +299,8 @@ bool FHdf5SimulationReader::ReadStringAttribute(hid_t GroupId, const char* AttrN bool FHdf5SimulationReader::ReadMetadata(FHdf5SimulationMetadata& OutMetadata) { + FScopeLock Hdf5Guard(&GetHdf5LibraryLock()); + if (FileId < 0) { UE_LOG(LogHdf5SimulationReader, Error, TEXT("No file is open")); @@ -320,6 +345,8 @@ bool FHdf5SimulationReader::ReadMetadata(FHdf5SimulationMetadata& OutMetadata) bool FHdf5SimulationReader::ReadEntities(TArray& OutEntities) { + FScopeLock Hdf5Guard(&GetHdf5LibraryLock()); + if (FileId < 0) { UE_LOG(LogHdf5SimulationReader, Error, TEXT("No file is open")); @@ -417,6 +444,8 @@ bool FHdf5SimulationReader::ReadEntities(TArray& OutEntities) bool FHdf5SimulationReader::ReadTimesteps(TArray& OutTimesteps) { + FScopeLock Hdf5Guard(&GetHdf5LibraryLock()); + if (FileId < 0) { UE_LOG(LogHdf5SimulationReader, Error, TEXT("No file is open")); @@ -453,6 +482,8 @@ bool FHdf5SimulationReader::ReadTimesteps(TArray& OutTimesteps) bool FHdf5SimulationReader::ReadSamplesPerTimestep(TArray& OutSamplesPerTimestep) { + FScopeLock Hdf5Guard(&GetHdf5LibraryLock()); + if (FileId < 0) { UE_LOG(LogHdf5SimulationReader, Error, TEXT("No file is open")); @@ -496,6 +527,8 @@ bool FHdf5SimulationReader::ReadSamplesPerTimestep(TArray& OutSamplesPerT bool FHdf5SimulationReader::ReadAllSamples(TArray& OutSamples, bool* OutHasRotationField, bool* OutHasSpeedField) { + FScopeLock Hdf5Guard(&GetHdf5LibraryLock()); + if (FileId < 0) { UE_LOG(LogHdf5SimulationReader, Error, TEXT("No file is open")); @@ -608,6 +641,8 @@ bool FHdf5SimulationReader::ReadAllSamples(TArray& OutSamples, bool FHdf5SimulationReader::ReadSamplesForTimestepRange(int32 StartTimestep, int32 EndTimestep, TArray& OutSamples) { + FScopeLock Hdf5Guard(&GetHdf5LibraryLock()); + if (FileId < 0) { UE_LOG(LogHdf5SimulationReader, Error, TEXT("No file is open")); @@ -718,6 +753,8 @@ EHdf5FormatType FHdf5SimulationReader::DetectFormat(const FString& FilePath) return EHdf5FormatType::Unknown; } + FScopeLock Hdf5Guard(&GetHdf5LibraryLock()); + FTCHARToUTF8 FilePathUtf8(*FilePath); // Check if it's a valid HDF5 file @@ -731,7 +768,6 @@ EHdf5FormatType FHdf5SimulationReader::DetectFormat(const FString& FilePath) hid_t TempFileId = H5Fopen(FilePathUtf8.Get(), H5F_ACC_RDONLY, H5P_DEFAULT); if (TempFileId < 0) { - H5close(); return EHdf5FormatType::Unknown; } @@ -750,7 +786,6 @@ EHdf5FormatType FHdf5SimulationReader::DetectFormat(const FString& FilePath) } H5Fclose(TempFileId); - H5close(); return Result; } @@ -759,6 +794,8 @@ EHdf5FormatType FHdf5SimulationReader::DetectFormat(const FString& FilePath) bool FHdf5SimulationReader::ReadJuelichMetadata(FHdf5JuelichMetadata& OutMetadata) { + FScopeLock Hdf5Guard(&GetHdf5LibraryLock()); + if (FileId < 0) { UE_LOG(LogHdf5SimulationReader, Error, TEXT("No file is open")); @@ -845,6 +882,8 @@ bool FHdf5SimulationReader::ReadJuelichMetadata(FHdf5JuelichMetadata& OutMetadat bool FHdf5SimulationReader::ReadJuelichTrajectories(TArray& OutRecords) { + FScopeLock Hdf5Guard(&GetHdf5LibraryLock()); + if (FileId < 0) { UE_LOG(LogHdf5SimulationReader, Error, TEXT("No file is open")); @@ -924,6 +963,8 @@ bool FHdf5SimulationReader::ReadJuelichTrajectories(TArray