diff --git a/source/framework/core/src/TRestDataSet.cxx b/source/framework/core/src/TRestDataSet.cxx index 22ba8334a..321e090f6 100644 --- a/source/framework/core/src/TRestDataSet.cxx +++ b/source/framework/core/src/TRestDataSet.cxx @@ -992,6 +992,7 @@ void TRestDataSet::Export(const std::string& filename, std::vector fDataFrame.Snapshot("AnalysisTree", filename); TFile* f = TFile::Open(filename.c_str(), "UPDATE"); + TRestTools::PreserveStreamerInfos(f); std::string name = this->GetName(); if (name.empty()) name = "mock"; this->Write(name.c_str()); diff --git a/source/framework/core/src/TRestProcessRunner.cxx b/source/framework/core/src/TRestProcessRunner.cxx index 2ff4d4ff0..6578ccca7 100644 --- a/source/framework/core/src/TRestProcessRunner.cxx +++ b/source/framework/core/src/TRestProcessRunner.cxx @@ -891,6 +891,7 @@ void TRestProcessRunner::FillThreadEventFunc(TRestThread* t) { fRunInfo->SetNFilesSplit(fNFilesSplit); if (fOutputDataFile->GetName() != fOutputDataFileName) { auto Mainfile = std::unique_ptr{TFile::Open(fOutputDataFileName, "update")}; + TRestTools::PreserveStreamerInfos(Mainfile.get()); WriteProcessesMetadata(); Mainfile->Write(0, TObject::kOverwrite); Mainfile->Close(); diff --git a/source/framework/core/src/TRestRun.cxx b/source/framework/core/src/TRestRun.cxx index af011ab61..5647e37bf 100644 --- a/source/framework/core/src/TRestRun.cxx +++ b/source/framework/core/src/TRestRun.cxx @@ -1020,6 +1020,10 @@ TFile* TRestRun::MergeToOutputFile(vector filenames, string outputfilena // write metadata into the output file fOutputFile = new TFile(fOutputFileName, "update"); + // Without this, closing the file would keep only the StreamerInfos of the classes + // streamed during this metadata session, wiping the event-class StreamerInfos + // written by the merge above (see TRestTools::PreserveStreamerInfos) + TRestTools::PreserveStreamerInfos(fOutputFile); RESTDebug << "TRestRun::FormOutputFile. Calling WriteWithDataBase()" << RESTendl; this->WriteWithDataBase(); @@ -1060,6 +1064,7 @@ TFile* TRestRun::UpdateOutputFile() { if (fOutputFile->IsOpen()) { fOutputFile->ReOpen("update"); } + TRestTools::PreserveStreamerInfos(fOutputFile); fOutputFile->cd(); diff --git a/source/framework/tools/inc/TRestTools.h b/source/framework/tools/inc/TRestTools.h index 649df089b..f499ff3c1 100644 --- a/source/framework/tools/inc/TRestTools.h +++ b/source/framework/tools/inc/TRestTools.h @@ -57,6 +57,8 @@ EXTERN_DEF std::string REST_USER_PATH; EXTERN_DEF std::string REST_TMP_PATH; EXTERN_DEF std::map REST_ARGS; +class TFile; + /// A generic class with useful static methods. class TRestTools { public: @@ -115,6 +117,8 @@ class TRestTools { template static int ExportBinaryTable(std::string fname, std::vector>& data); + static void PreserveStreamerInfos(TFile* file); + static Int_t isValidFile(const std::string& path); static bool fileExists(const std::string& filename); static bool isRootFile(const std::string& filename); diff --git a/source/framework/tools/src/TRestTools.cxx b/source/framework/tools/src/TRestTools.cxx index 44ef9b01b..7be7b1529 100644 --- a/source/framework/tools/src/TRestTools.cxx +++ b/source/framework/tools/src/TRestTools.cxx @@ -43,9 +43,11 @@ /// #include "TRestTools.h" +#include #include #include #include +#include #include #include @@ -713,6 +715,49 @@ int TRestTools::ReadCSVFile(std::string fName, std::vector> return ReadASCIITable(fName, data, skipLines, ","); } +/////////////////////////////////////////////// +/// \brief Re-tag the StreamerInfos already stored in a file opened in UPDATE mode, +/// so that they survive the session. +/// +/// When a ROOT file is closed, TFile::WriteStreamerInfo() REPLACES the StreamerInfo +/// record of the file, keeping only the infos of the classes streamed during that +/// session. An UPDATE session that only adds metadata (e.g. the reopen done by +/// TRestRun::MergeToOutputFile after merging the threads' output) therefore wipes +/// the StreamerInfos of the event classes written earlier. Without them, ROOT +/// cannot apply schema evolution when the class definitions change, and old files +/// become unreadable (see rest-for-physics/detectorlib#125). +/// +/// Calling this method right after opening a file in UPDATE mode marks every +/// StreamerInfo already present in the file to be written out again on close. +/// +void TRestTools::PreserveStreamerInfos(TFile* file) { + if (file == nullptr || !file->IsOpen() || !file->IsWritable()) return; + TArrayC* classIndex = file->GetClassIndex(); + if (classIndex == nullptr) return; + + std::unique_ptr infos(file->GetStreamerInfoList()); + if (infos == nullptr) return; + infos->SetOwner(kTRUE); + + TIter next(infos.get()); + TObject* obj; + while ((obj = next())) { + auto info = dynamic_cast(obj); + if (info == nullptr) continue; + TClass* cl = TClass::GetClass(info->GetName()); + if (cl == nullptr) continue; + auto current = dynamic_cast(cl->GetStreamerInfo(info->GetClassVersion())); + if (current == nullptr) continue; + // Same marking as the (deprecated) TStreamerInfo::TagFile: flag the class + // so that TFile::WriteStreamerInfo includes it when the file is closed + const Int_t number = current->GetNumber(); + if (number > 0 && number < classIndex->GetSize()) { + classIndex->fArray[number] = 1; + classIndex->fArray[0] = 1; + } + } +} + /////////////////////////////////////////////// /// \brief Returns true if the file with path filename exists. ///