diff --git a/Detectors/CPV/workflow/src/ClusterReaderSpec.cxx b/Detectors/CPV/workflow/src/ClusterReaderSpec.cxx index f9d0817325c36..62dc5efe4e0c0 100644 --- a/Detectors/CPV/workflow/src/ClusterReaderSpec.cxx +++ b/Detectors/CPV/workflow/src/ClusterReaderSpec.cxx @@ -41,8 +41,17 @@ void ClusterReader::init(InitContext& ic) void ClusterReader::run(ProcessingContext& pc) { auto ent = mTree->GetReadEntry() + 1; - assert(ent < mTree->GetEntries()); // this should not happen - mTree->GetEntry(ent); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + mTree->GetEntry(ent); + } LOG(info) << "Pushing " << mClusters.size() << " Clusters in " << mTRs.size() << " TriggerRecords at entry " << ent; pc.outputs().snapshot(Output{mOrigin, "CLUSTERS", 0}, mClusters); pc.outputs().snapshot(Output{mOrigin, "CLUSTERTRIGRECS", 0}, mTRs); @@ -50,7 +59,7 @@ void ClusterReader::run(ProcessingContext& pc) pc.outputs().snapshot(Output{mOrigin, "CLUSTERTRUEMC", 0}, mMCTruth); } - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); } diff --git a/Detectors/CPV/workflow/src/DigitReaderSpec.cxx b/Detectors/CPV/workflow/src/DigitReaderSpec.cxx index 20fe497eb5d0c..1a4999847f68c 100644 --- a/Detectors/CPV/workflow/src/DigitReaderSpec.cxx +++ b/Detectors/CPV/workflow/src/DigitReaderSpec.cxx @@ -41,8 +41,17 @@ void DigitReader::init(InitContext& ic) void DigitReader::run(ProcessingContext& pc) { auto ent = mTree->GetReadEntry() + 1; - assert(ent < mTree->GetEntries()); // this should not happen - mTree->GetEntry(ent); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + mTree->GetEntry(ent); + } LOG(info) << "Pushing " << mDigits.size() << " Digits in " << mTRs.size() << " TriggerRecords at entry " << ent; pc.outputs().snapshot(Output{mOrigin, "DIGITS", 0}, mDigits); pc.outputs().snapshot(Output{mOrigin, "DIGITTRIGREC", 0}, mTRs); @@ -50,7 +59,7 @@ void DigitReader::run(ProcessingContext& pc) pc.outputs().snapshot(Output{mOrigin, "DIGITSMCTR", 0}, mMCTruth); } - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); } diff --git a/Detectors/CTP/workflowIO/src/DigitReaderSpec.cxx b/Detectors/CTP/workflowIO/src/DigitReaderSpec.cxx index 81e6f53f42dcc..dfc9851f06b8e 100644 --- a/Detectors/CTP/workflowIO/src/DigitReaderSpec.cxx +++ b/Detectors/CTP/workflowIO/src/DigitReaderSpec.cxx @@ -86,12 +86,21 @@ void DigitReader::run(ProcessingContext& pc) auto ent = mTree->GetReadEntry(); if (!mUseIRFrames) { ent++; - assert(ent < mTree->GetEntries()); // this should not happen - mTree->GetEntry(ent); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + mTree->GetEntry(ent); + } LOG(info) << "DigitReader pushes " << mDigits.size() << " digits at entry " << ent; pc.outputs().snapshot(Output{"CTP", "DIGITS", 0}, mDigits); pc.outputs().snapshot(Output{"CTP", "LUMI", 0}, mLumi); - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); } diff --git a/Detectors/FIT/FDD/workflow/src/DigitReaderSpec.cxx b/Detectors/FIT/FDD/workflow/src/DigitReaderSpec.cxx index 628a2160c6d0c..a75f24787b2f5 100644 --- a/Detectors/FIT/FDD/workflow/src/DigitReaderSpec.cxx +++ b/Detectors/FIT/FDD/workflow/src/DigitReaderSpec.cxx @@ -77,24 +77,40 @@ void DigitReader::run(ProcessingContext& pc) } } auto ent = mTree->GetReadEntry() + 1; - assert(ent < mTree->GetEntries()); // this should not happen - mTree->GetEntry(ent); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + mTree->GetEntry(ent); + } - LOG(info) << "FDD DigitReader pushes " << digitsBC->size() << " digits"; - pc.outputs().snapshot(Output{mOrigin, "DIGITSBC", 0}, *digitsBC); - pc.outputs().snapshot(Output{mOrigin, "DIGITSCH", 0}, *digitsCh); + static const std::vector noDigitsBC; + static const std::vector noDigitsCh; + static const std::vector noDigitsTrig; + const auto& digitsBCOut = noEntry ? noDigitsBC : *digitsBC; + const auto& digitsChOut = noEntry ? noDigitsCh : *digitsCh; + LOG(info) << "FDD DigitReader pushes " << digitsBCOut.size() << " digits"; + pc.outputs().snapshot(Output{mOrigin, "DIGITSBC", 0}, digitsBCOut); + pc.outputs().snapshot(Output{mOrigin, "DIGITSCH", 0}, digitsChOut); if (mUseMC) { // TODO: To be replaced with sending ConstMCTruthContainer as soon as reco workflow supports it - pc.outputs().snapshot(Output{mOrigin, "TRIGGERINPUT", 0}, *digitsTrig); + pc.outputs().snapshot(Output{mOrigin, "TRIGGERINPUT", 0}, noEntry ? noDigitsTrig : *digitsTrig); - std::vector flatbuffer; - mcTruthRootBuffer->copyandflatten(flatbuffer); o2::dataformats::MCTruthContainer mcTruth; - mcTruth.restore_from(flatbuffer.data(), flatbuffer.size()); + if (!noEntry) { + std::vector flatbuffer; + mcTruthRootBuffer->copyandflatten(flatbuffer); + mcTruth.restore_from(flatbuffer.data(), flatbuffer.size()); + } pc.outputs().snapshot(Output{mOrigin, "DIGITLBL", 0}, mcTruth); } - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); } diff --git a/Detectors/FIT/FDD/workflow/src/RecPointReaderSpec.cxx b/Detectors/FIT/FDD/workflow/src/RecPointReaderSpec.cxx index 3c4812c75b251..6fee8e5d4ecc4 100644 --- a/Detectors/FIT/FDD/workflow/src/RecPointReaderSpec.cxx +++ b/Detectors/FIT/FDD/workflow/src/RecPointReaderSpec.cxx @@ -45,14 +45,27 @@ void RecPointReader::init(InitContext& ic) void RecPointReader::run(ProcessingContext& pc) { auto ent = mTree->GetReadEntry() + 1; - assert(ent < mTree->GetEntries()); // this should not happen - mTree->GetEntry(ent); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + mTree->GetEntry(ent); + } - LOG(info) << "FDD RecPointReader pushes " << mRecPoints->size() << " recpoints with " << mChannelData->size() << " channels at entry " << ent; - pc.outputs().snapshot(Output{mOrigin, "RECPOINTS", 0}, *mRecPoints); - pc.outputs().snapshot(Output{mOrigin, "RECCHDATA", 0}, *mChannelData); + static const std::vector noRecPoints; + static const std::vector noChannelData; + const auto& recPoints = noEntry ? noRecPoints : *mRecPoints; + const auto& channelData = noEntry ? noChannelData : *mChannelData; + LOG(info) << "FDD RecPointReader pushes " << recPoints.size() << " recpoints with " << channelData.size() << " channels at entry " << ent; + pc.outputs().snapshot(Output{mOrigin, "RECPOINTS", 0}, recPoints); + pc.outputs().snapshot(Output{mOrigin, "RECCHDATA", 0}, channelData); - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); } diff --git a/Detectors/FIT/FT0/workflow/src/DigitReaderSpec.cxx b/Detectors/FIT/FT0/workflow/src/DigitReaderSpec.cxx index 09586d778ac15..8cfdb91fa4797 100644 --- a/Detectors/FIT/FT0/workflow/src/DigitReaderSpec.cxx +++ b/Detectors/FIT/FT0/workflow/src/DigitReaderSpec.cxx @@ -61,8 +61,17 @@ void DigitReader::run(ProcessingContext& pc) mTree->SetBranchAddress("FT0DIGITSMCTR", &plabels); } auto ent = mTree->GetReadEntry() + 1; - assert(ent < mTree->GetEntries()); // this should not happen - mTree->GetEntry(ent); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + mTree->GetEntry(ent); + } LOG(debug) << "FT0DigitReader pushed " << channels.size() << " channels in " << digits.size() << " digits"; pc.outputs().snapshot(Output{"FT0", "DIGITSBC", 0}, digits); pc.outputs().snapshot(Output{"FT0", "DIGITSCH", 0}, channels); @@ -72,7 +81,7 @@ void DigitReader::run(ProcessingContext& pc) if (mUseTrgInput) { pc.outputs().snapshot(Output{"FT0", "TRIGGERINPUT", 0}, trgInput); } - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); } diff --git a/Detectors/FIT/FT0/workflow/src/RecPointReaderSpec.cxx b/Detectors/FIT/FT0/workflow/src/RecPointReaderSpec.cxx index ba5ae4aa1356c..be184d38155f1 100644 --- a/Detectors/FIT/FT0/workflow/src/RecPointReaderSpec.cxx +++ b/Detectors/FIT/FT0/workflow/src/RecPointReaderSpec.cxx @@ -45,14 +45,27 @@ void RecPointReader::init(InitContext& ic) void RecPointReader::run(ProcessingContext& pc) { auto ent = mTree->GetReadEntry() + 1; - assert(ent < mTree->GetEntries()); // this should not happen - mTree->GetEntry(ent); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + mTree->GetEntry(ent); + } - LOG(debug) << "FT0 RecPointReader pushes " << mRecPoints->size() << " recpoints with " << mChannelData->size() << " channels at entry " << ent; - pc.outputs().snapshot(Output{mOrigin, "RECPOINTS", 0}, *mRecPoints); - pc.outputs().snapshot(Output{mOrigin, "RECCHDATA", 0}, *mChannelData); + static const std::vector noRecPoints; + static const std::vector noChannelData; + const auto& recPoints = noEntry ? noRecPoints : *mRecPoints; + const auto& channelData = noEntry ? noChannelData : *mChannelData; + LOG(debug) << "FT0 RecPointReader pushes " << recPoints.size() << " recpoints with " << channelData.size() << " channels at entry " << ent; + pc.outputs().snapshot(Output{mOrigin, "RECPOINTS", 0}, recPoints); + pc.outputs().snapshot(Output{mOrigin, "RECCHDATA", 0}, channelData); - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); } diff --git a/Detectors/FIT/FV0/workflow/src/DigitReaderSpec.cxx b/Detectors/FIT/FV0/workflow/src/DigitReaderSpec.cxx index a49bda2cec18b..79df17caf9da0 100644 --- a/Detectors/FIT/FV0/workflow/src/DigitReaderSpec.cxx +++ b/Detectors/FIT/FV0/workflow/src/DigitReaderSpec.cxx @@ -62,8 +62,17 @@ void DigitReader::run(ProcessingContext& pc) mTree->SetBranchAddress("FV0DigitLabels", &plabels); } auto ent = mTree->GetReadEntry() + 1; - assert(ent < mTree->GetEntries()); // this should not happen - mTree->GetEntry(ent); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + mTree->GetEntry(ent); + } LOG(debug) << "FV0DigitReader pushed " << channels.size() << " channels in " << digits.size() << " digits"; pc.outputs().snapshot(Output{"FV0", "DIGITSBC", 0}, digits); pc.outputs().snapshot(Output{"FV0", "DIGITSCH", 0}, channels); @@ -73,7 +82,7 @@ void DigitReader::run(ProcessingContext& pc) if (mUseTrgInput) { pc.outputs().snapshot(Output{"FV0", "TRIGGERINPUT", 0}, trgInput); } - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); } diff --git a/Detectors/FIT/FV0/workflow/src/RecPointReaderSpec.cxx b/Detectors/FIT/FV0/workflow/src/RecPointReaderSpec.cxx index 5997cac500ee6..053c28b7a987e 100644 --- a/Detectors/FIT/FV0/workflow/src/RecPointReaderSpec.cxx +++ b/Detectors/FIT/FV0/workflow/src/RecPointReaderSpec.cxx @@ -45,14 +45,27 @@ void RecPointReader::init(InitContext& ic) void RecPointReader::run(ProcessingContext& pc) { auto ent = mTree->GetReadEntry() + 1; - assert(ent < mTree->GetEntries()); // this should not happen - mTree->GetEntry(ent); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + mTree->GetEntry(ent); + } - LOG(debug) << "FV0 RecPointReader pushes " << mRecPoints->size() << " recpoints with " << mChannelData->size() << " channels at entry " << ent; - pc.outputs().snapshot(Output{mOrigin, "RECPOINTS", 0}, *mRecPoints); - pc.outputs().snapshot(Output{mOrigin, "RECCHDATA", 0}, *mChannelData); + static const std::vector noRecPoints; + static const std::vector noChannelData; + const auto& recPoints = noEntry ? noRecPoints : *mRecPoints; + const auto& channelData = noEntry ? noChannelData : *mChannelData; + LOG(debug) << "FV0 RecPointReader pushes " << recPoints.size() << " recpoints with " << channelData.size() << " channels at entry " << ent; + pc.outputs().snapshot(Output{mOrigin, "RECPOINTS", 0}, recPoints); + pc.outputs().snapshot(Output{mOrigin, "RECCHDATA", 0}, channelData); - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); } diff --git a/Detectors/Filtering/src/FilteredTFReaderSpec.cxx b/Detectors/Filtering/src/FilteredTFReaderSpec.cxx index 22fe1370040db..96be122485541 100644 --- a/Detectors/Filtering/src/FilteredTFReaderSpec.cxx +++ b/Detectors/Filtering/src/FilteredTFReaderSpec.cxx @@ -40,8 +40,17 @@ void FilteredTFReader::run(ProcessingContext& pc) // FIXME: fill all output headers by TF specific info (extend findMessageHeaderStack) auto ent = mTree->GetReadEntry() + 1; - assert(ent < mTree->GetEntries()); // this should not happen - mTree->GetEntry(ent); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + mTree->GetEntry(ent); + } LOG(info) << "Pushing filtered TF: " << mFiltTF.header.asString(); // ITS @@ -55,7 +64,7 @@ void FilteredTFReader::run(ProcessingContext& pc) pc.outputs().snapshot(Output{"ITS", "COMPCLUSTERS", 0}, mFiltTF.ITSClusters); pc.outputs().snapshot(Output{"ITS", "PATTERNS", 0}, mFiltTF.ITSClusterPatterns); - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); } diff --git a/Detectors/GlobalTrackingWorkflow/readers/src/GlobalFwdTrackReaderSpec.cxx b/Detectors/GlobalTrackingWorkflow/readers/src/GlobalFwdTrackReaderSpec.cxx index 11fa58333f89b..e92a85268c000 100644 --- a/Detectors/GlobalTrackingWorkflow/readers/src/GlobalFwdTrackReaderSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/readers/src/GlobalFwdTrackReaderSpec.cxx @@ -61,8 +61,17 @@ void GlobalFwdTrackReader::init(InitContext& ic) void GlobalFwdTrackReader::run(ProcessingContext& pc) { auto ent = mTree->GetReadEntry() + 1; - assert(ent < mTree->GetEntries()); // this should not happen - mTree->GetEntry(ent); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + mTree->GetEntry(ent); + } LOG(info) << "Pushing " << mTracks.size() << " Global Forward tracks at entry " << ent; pc.outputs().snapshot(Output{"GLO", "GLFWD", 0}, mTracks); @@ -70,7 +79,7 @@ void GlobalFwdTrackReader::run(ProcessingContext& pc) pc.outputs().snapshot(Output{"GLO", "GLFWD_MC", 0}, mLabels); } - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); } diff --git a/Detectors/GlobalTrackingWorkflow/readers/src/IRFrameReaderSpec.cxx b/Detectors/GlobalTrackingWorkflow/readers/src/IRFrameReaderSpec.cxx index c1810a1deb743..0017fd8fea3d6 100644 --- a/Detectors/GlobalTrackingWorkflow/readers/src/IRFrameReaderSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/readers/src/IRFrameReaderSpec.cxx @@ -60,12 +60,21 @@ void IRFrameReaderSpec::init(InitContext& ic) void IRFrameReaderSpec::run(ProcessingContext& pc) { auto ent = mTree->GetReadEntry() + 1; - assert(ent < mTree->GetEntries()); // this should not happen - mTree->GetEntry(ent); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + mTree->GetEntry(ent); + } LOG(debug) << "Pushing " << mIRF.size() << " IR-frames in at entry " << ent; pc.outputs().snapshot(Output{mDataOrigin, "IRFRAMES", mSubSpec}, mIRF); - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); } diff --git a/Detectors/GlobalTrackingWorkflow/readers/src/MatchedMCHMIDReaderSpec.cxx b/Detectors/GlobalTrackingWorkflow/readers/src/MatchedMCHMIDReaderSpec.cxx index dc8cf71575787..c1cc5f7e649cf 100644 --- a/Detectors/GlobalTrackingWorkflow/readers/src/MatchedMCHMIDReaderSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/readers/src/MatchedMCHMIDReaderSpec.cxx @@ -61,8 +61,17 @@ void MatchMCHMIDReader::init(InitContext& ic) void MatchMCHMIDReader::run(ProcessingContext& pc) { auto ent = mTree->GetReadEntry() + 1; - assert(ent < mTree->GetEntries()); // this should not happen - mTree->GetEntry(ent); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + mTree->GetEntry(ent); + } LOG(info) << "Pushing " << mTracks.size() << " MCHMID matches at entry " << ent; pc.outputs().snapshot(OutputRef{"muontracks"}, mTracks); @@ -70,7 +79,7 @@ void MatchMCHMIDReader::run(ProcessingContext& pc) pc.outputs().snapshot(OutputRef{"muontracklabels"}, mLabels); } - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); } diff --git a/Detectors/GlobalTrackingWorkflow/readers/src/MatchedMFTMCHReaderSpec.cxx b/Detectors/GlobalTrackingWorkflow/readers/src/MatchedMFTMCHReaderSpec.cxx index 5f02beebd1746..36c577b392c35 100644 --- a/Detectors/GlobalTrackingWorkflow/readers/src/MatchedMFTMCHReaderSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/readers/src/MatchedMFTMCHReaderSpec.cxx @@ -61,13 +61,22 @@ void MatchMFTMCHReader::init(InitContext& ic) void MatchMFTMCHReader::run(ProcessingContext& pc) { auto ent = mTree->GetReadEntry() + 1; - assert(ent < mTree->GetEntries()); // this should not happen - mTree->GetEntry(ent); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + mTree->GetEntry(ent); + } LOG(info) << "Pushing " << mTracks.size() << " MFTMCH matches at entry " << ent; pc.outputs().snapshot(Output{"GLO", "MTC_MFTMCH", 0}, mTracks); - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); } diff --git a/Detectors/GlobalTrackingWorkflow/readers/src/PrimaryVertexReaderSpec.cxx b/Detectors/GlobalTrackingWorkflow/readers/src/PrimaryVertexReaderSpec.cxx index 6e1aba8b2e1f3..8f9c52e26a980 100644 --- a/Detectors/GlobalTrackingWorkflow/readers/src/PrimaryVertexReaderSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/readers/src/PrimaryVertexReaderSpec.cxx @@ -80,8 +80,17 @@ void PrimaryVertexReader::init(InitContext& ic) void PrimaryVertexReader::run(ProcessingContext& pc) { auto ent = mTree->GetReadEntry() + 1; - assert(ent < mTree->GetEntries()); // this should not happen - mTree->GetEntry(ent); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + mTree->GetEntry(ent); + } LOG(info) << "Pushing " << mVerticesPtr->size() << " vertices at entry " << ent; pc.outputs().snapshot(Output{"GLO", "PVTX", 0}, mVertices); @@ -140,7 +149,7 @@ void PrimaryVertexReader::run(ProcessingContext& pc) } } - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); } diff --git a/Detectors/GlobalTrackingWorkflow/readers/src/SecondaryVertexReaderSpec.cxx b/Detectors/GlobalTrackingWorkflow/readers/src/SecondaryVertexReaderSpec.cxx index 9f252616c9d55..891b2bac3e155 100644 --- a/Detectors/GlobalTrackingWorkflow/readers/src/SecondaryVertexReaderSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/readers/src/SecondaryVertexReaderSpec.cxx @@ -89,8 +89,17 @@ void SecondaryVertexReader::init(InitContext& ic) void SecondaryVertexReader::run(ProcessingContext& pc) { auto ent = mTree->GetReadEntry() + 1; - assert(ent < mTree->GetEntries()); // this should not happen - mTree->GetEntry(ent); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + mTree->GetEntry(ent); + } LOGP(info, "Pushing {} V0s ({} indices), {} cascades ({} indices) and {} 3-body ({} indices ) at entry {}", mV0s.size(), mV0sIdx.size(), mCascs.size(), mCascsIdx.size(), m3Bodys.size(), m3BodysIdx.size(), ent); @@ -104,7 +113,7 @@ void SecondaryVertexReader::run(ProcessingContext& pc) pc.outputs().snapshot(Output{"GLO", "DECAYS3BODY", 0}, m3Bodys); pc.outputs().snapshot(Output{"GLO", "PVTX_3BODYREFS", 0}, mPV23BodyRef); - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); } diff --git a/Detectors/GlobalTrackingWorkflow/readers/src/StrangenessTrackingReaderSpec.cxx b/Detectors/GlobalTrackingWorkflow/readers/src/StrangenessTrackingReaderSpec.cxx index 8c7f87a720925..060a8957985b3 100644 --- a/Detectors/GlobalTrackingWorkflow/readers/src/StrangenessTrackingReaderSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/readers/src/StrangenessTrackingReaderSpec.cxx @@ -73,8 +73,17 @@ void StrangenessTrackingReader::init(InitContext& ic) void StrangenessTrackingReader::run(ProcessingContext& pc) { auto ent = mTree->GetReadEntry() + 1; - assert(ent < mTree->GetEntries()); // this should not happen - mTree->GetEntry(ent); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + mTree->GetEntry(ent); + } LOG(info) << "Pushing " << mStrangeTrack.size() << " strange tracks at entry " << ent; pc.outputs().snapshot(Output{"GLO", "STRANGETRACKS", 0}, mStrangeTrack); @@ -85,7 +94,7 @@ void StrangenessTrackingReader::run(ProcessingContext& pc) // pc.outputs().snapshot(Output{"GLO", "PVTX_V0REFS", 0}, mPV2V0Ref); - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); } diff --git a/Detectors/GlobalTrackingWorkflow/readers/src/TrackCosmicsReaderSpec.cxx b/Detectors/GlobalTrackingWorkflow/readers/src/TrackCosmicsReaderSpec.cxx index 7e3cdffd84a6d..a673676bc9dc8 100644 --- a/Detectors/GlobalTrackingWorkflow/readers/src/TrackCosmicsReaderSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/readers/src/TrackCosmicsReaderSpec.cxx @@ -37,8 +37,17 @@ void TrackCosmicsReader::init(InitContext& ic) void TrackCosmicsReader::run(ProcessingContext& pc) { auto ent = mTree->GetReadEntry() + 1; - assert(ent < mTree->GetEntries()); // this should not happen - mTree->GetEntry(ent); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + mTree->GetEntry(ent); + } LOG(info) << "Pushing " << mTracks.size() << " Cosmic Tracks at entry " << ent; pc.outputs().snapshot(Output{"GLO", "COSMICTRC", 0}, mTracks); @@ -46,7 +55,7 @@ void TrackCosmicsReader::run(ProcessingContext& pc) pc.outputs().snapshot(Output{"GLO", "COSMICTRC_MC", 0}, mLabels); } - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); } diff --git a/Detectors/GlobalTrackingWorkflow/readers/src/TrackTPCITSReaderSpec.cxx b/Detectors/GlobalTrackingWorkflow/readers/src/TrackTPCITSReaderSpec.cxx index c7fd0d543ecf6..3064d8cd3006d 100644 --- a/Detectors/GlobalTrackingWorkflow/readers/src/TrackTPCITSReaderSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/readers/src/TrackTPCITSReaderSpec.cxx @@ -64,8 +64,17 @@ void TrackTPCITSReader::init(InitContext& ic) void TrackTPCITSReader::run(ProcessingContext& pc) { auto ent = mTree->GetReadEntry() + 1; - assert(ent < mTree->GetEntries()); // this should not happen - mTree->GetEntry(ent); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + mTree->GetEntry(ent); + } LOG(info) << "Pushing " << mTracks.size() << " TPC-ITS matches at entry " << ent; pc.outputs().snapshot(Output{"GLO", "TPCITS", 0}, mTracks); @@ -76,7 +85,7 @@ void TrackTPCITSReader::run(ProcessingContext& pc) pc.outputs().snapshot(Output{"GLO", "TPCITSAB_MC", 0}, mLabelsAB); } - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); } diff --git a/Detectors/HMPID/workflow/src/ClustersReaderSpec.cxx b/Detectors/HMPID/workflow/src/ClustersReaderSpec.cxx index 9ac5074acb505..5802355b326a8 100644 --- a/Detectors/HMPID/workflow/src/ClustersReaderSpec.cxx +++ b/Detectors/HMPID/workflow/src/ClustersReaderSpec.cxx @@ -68,15 +68,24 @@ void ClusterReaderTask::run(ProcessingContext& pc) { auto ent = mTree->GetReadEntry() + 1; - assert(ent < mTree->GetEntries()); // this should not happen - mTree->GetEntry(ent); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + mTree->GetEntry(ent); + } pc.outputs().snapshot(Output{"HMP", "CLUSTERS", 0}, mClustersFromFile); pc.outputs().snapshot(Output{"HMP", "INTRECORDS1", 0}, mClusterTriggersFromFile); mClustersReceived += mClustersFromFile.size(); LOG(info) << "[HMPID ClusterReader - run() ] clusters = " << mClustersFromFile.size(); - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); mExTimer.stop(); diff --git a/Detectors/HMPID/workflow/src/DigitsReaderSpec.cxx b/Detectors/HMPID/workflow/src/DigitsReaderSpec.cxx index 88f6df2bce2e7..8953fd37fa47d 100644 --- a/Detectors/HMPID/workflow/src/DigitsReaderSpec.cxx +++ b/Detectors/HMPID/workflow/src/DigitsReaderSpec.cxx @@ -112,15 +112,24 @@ void DigitReader::run(ProcessingContext& pc) // mTree->Print("toponly"); auto ent = mTree->GetReadEntry() + 1; - assert(ent < mTree->GetEntries()); // this should not happen - mTree->GetEntry(ent); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + mTree->GetEntry(ent); + } pc.outputs().snapshot(Output{"HMP", "DIGITS", 0}, mDigitsFromFile); pc.outputs().snapshot(Output{"HMP", "INTRECORDS", 0}, mTriggersFromFile); mDigitsReceived += mDigitsFromFile.size(); LOG(info) << "[HMPID DigitsReader - run() ] digits = " << mDigitsFromFile.size(); - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); mExTimer.stop(); diff --git a/Detectors/ITSMFT/ITS/workflow/src/TrackReaderSpec.cxx b/Detectors/ITSMFT/ITS/workflow/src/TrackReaderSpec.cxx index 2f081a11c28b9..1a8056b91c7d2 100644 --- a/Detectors/ITSMFT/ITS/workflow/src/TrackReaderSpec.cxx +++ b/Detectors/ITSMFT/ITS/workflow/src/TrackReaderSpec.cxx @@ -34,8 +34,17 @@ void TrackReader::init(InitContext& ic) void TrackReader::run(ProcessingContext& pc) { auto ent = mTree->GetReadEntry() + 1; - assert(ent < mTree->GetEntries()); // this should not happen - mTree->GetEntry(ent); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + mTree->GetEntry(ent); + } LOG(info) << "Pushing " << mTracks.size() << " track at entry " << ent; pc.outputs().snapshot(Output{mOrigin, "ITSTrackROF", 0}, mROFRec); pc.outputs().snapshot(Output{mOrigin, "TRACKS", 0}, mTracks); @@ -47,7 +56,7 @@ void TrackReader::run(ProcessingContext& pc) pc.outputs().snapshot(Output{mOrigin, "VERTICESMCTR", 0}, mMCVertTruth); } - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); } diff --git a/Detectors/ITSMFT/ITS/workflow/src/VertexReaderSpec.cxx b/Detectors/ITSMFT/ITS/workflow/src/VertexReaderSpec.cxx index e92f08af23c0d..eca02da2b8b79 100644 --- a/Detectors/ITSMFT/ITS/workflow/src/VertexReaderSpec.cxx +++ b/Detectors/ITSMFT/ITS/workflow/src/VertexReaderSpec.cxx @@ -37,14 +37,23 @@ void VertexReader::init(InitContext& ic) void VertexReader::run(ProcessingContext& pc) { auto ent = mTree->GetReadEntry() + 1; - assert(ent < mTree->GetEntries()); // this should not happen - mTree->GetEntry(ent); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + mTree->GetEntry(ent); + } LOG(info) << "Pushing " << mVerticesPtr->size() << " vertices in " << mVerticesROFRecPtr->size() << " ROFs at entry " << ent; pc.outputs().snapshot(Output{"ITS", "VERTICES", 0}, mVertices); pc.outputs().snapshot(Output{"ITS", "VERTICESROF", 0}, mVerticesROFRec); - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); } diff --git a/Detectors/ITSMFT/MFT/workflow/src/TrackReaderSpec.cxx b/Detectors/ITSMFT/MFT/workflow/src/TrackReaderSpec.cxx index 1a2ae573af536..356e629f9839e 100644 --- a/Detectors/ITSMFT/MFT/workflow/src/TrackReaderSpec.cxx +++ b/Detectors/ITSMFT/MFT/workflow/src/TrackReaderSpec.cxx @@ -42,8 +42,17 @@ void TrackReader::init(InitContext& ic) void TrackReader::run(ProcessingContext& pc) { auto ent = mTree->GetReadEntry() + 1; - assert(ent < mTree->GetEntries()); // this should not happen - mTree->GetEntry(ent); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + mTree->GetEntry(ent); + } LOG(info) << "Pushing " << mTracks.size() << " track in " << mROFRec.size() << " ROFs at entry " << ent; pc.outputs().snapshot(Output{mOrigin, "MFTTrackROF", 0}, mROFRec); pc.outputs().snapshot(Output{mOrigin, "TRACKS", 0}, mTracks); @@ -52,7 +61,7 @@ void TrackReader::run(ProcessingContext& pc) pc.outputs().snapshot(Output{mOrigin, "TRACKSMCTR", 0}, mMCTruth); } - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); } diff --git a/Detectors/ITSMFT/common/workflow/src/ClusterReaderSpec.cxx b/Detectors/ITSMFT/common/workflow/src/ClusterReaderSpec.cxx index 6174938171336..5e8ee4b99c092 100644 --- a/Detectors/ITSMFT/common/workflow/src/ClusterReaderSpec.cxx +++ b/Detectors/ITSMFT/common/workflow/src/ClusterReaderSpec.cxx @@ -57,18 +57,33 @@ template void ClusterReader::run(ProcessingContext& pc) { auto ent = mTree->GetReadEntry() + 1; - assert(ent < mTree->GetEntries()); // this should not happen - mTree->GetEntry(ent); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + mTree->GetEntry(ent); + } + static const std::vector noClusROFRec; + static const std::vector noClusters; + static const std::vector noPatterns; + static const o2::dataformats::MCTruthContainer noLabels; for (uint32_t iLayer = 0; iLayer < mLayers; ++iLayer) { - LOG(info) << mDetName << "ClusterReader" << (mDoStaggering ? std::format(" on layer {}", iLayer) : "") << " pushes " << mClusROFRec[iLayer]->size() << " ROFRecords, " << mClusterCompArray[iLayer]->size() << " compact clusters at entry " << ent; - pc.outputs().snapshot(Output{Origin, "CLUSTERSROF", iLayer}, *mClusROFRec[iLayer]); - pc.outputs().snapshot(Output{Origin, "COMPCLUSTERS", iLayer}, *mClusterCompArray[iLayer]); + const auto& clusROFRec = noEntry ? noClusROFRec : *mClusROFRec[iLayer]; + const auto& clusters = noEntry ? noClusters : *mClusterCompArray[iLayer]; + LOG(info) << mDetName << "ClusterReader" << (mDoStaggering ? std::format(" on layer {}", iLayer) : "") << " pushes " << clusROFRec.size() << " ROFRecords, " << clusters.size() << " compact clusters at entry " << ent; + pc.outputs().snapshot(Output{Origin, "CLUSTERSROF", iLayer}, clusROFRec); + pc.outputs().snapshot(Output{Origin, "COMPCLUSTERS", iLayer}, clusters); if (mUsePatterns) { - pc.outputs().snapshot(Output{Origin, "PATTERNS", iLayer}, *mPatternsArray[iLayer]); + pc.outputs().snapshot(Output{Origin, "PATTERNS", iLayer}, noEntry ? noPatterns : *mPatternsArray[iLayer]); } if (mUseMC) { - pc.outputs().snapshot(Output{Origin, "CLUSTERSMCTR", iLayer}, *mClusterMCTruth[iLayer]); + pc.outputs().snapshot(Output{Origin, "CLUSTERSMCTR", iLayer}, noEntry ? noLabels : *mClusterMCTruth[iLayer]); // read dummy MC2ROF vector to keep writer/readers backward compatible static std::vector dummyMC2ROF; pc.outputs().snapshot(Output{Origin, "CLUSTERSMC2ROF", iLayer}, dummyMC2ROF); @@ -78,7 +93,7 @@ void ClusterReader::run(ProcessingContext& pc) std::vector dummyTrig; pc.outputs().snapshot(Output{Origin, "PHYSTRIG", 0}, dummyTrig); } - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); } diff --git a/Detectors/ITSMFT/common/workflow/src/DigitReaderSpec.cxx b/Detectors/ITSMFT/common/workflow/src/DigitReaderSpec.cxx index b6c3ab5386179..e9db31ec3222e 100644 --- a/Detectors/ITSMFT/common/workflow/src/DigitReaderSpec.cxx +++ b/Detectors/ITSMFT/common/workflow/src/DigitReaderSpec.cxx @@ -26,6 +26,7 @@ #include "ITSMFTReconstruction/ChipMappingMFT.h" #include "SimulationDataFormat/MCCompLabel.h" #include "SimulationDataFormat/ConstMCTruthContainer.h" +#include "SimulationDataFormat/MCTruthContainer.h" #include "DataFormatsITSMFT/PhysTrigger.h" #include "CommonUtils/NameConf.h" #include "CommonDataFormat/IRFrame.h" @@ -101,7 +102,32 @@ void DigitReader::run(ProcessingContext& pc) auto ent = mTree->GetReadEntry(); if (!mUseIRFrames) { ent++; - assert(ent < mTree->GetEntries()); // this should not happen + if (ent >= mTree->GetEntries()) { + // A timeframe holds no collision at all whenever the interaction rate is low enough, and the + // digit tree then has no entry to read. Send empty output rather than dereferencing the + // branch addresses, which GetEntry has not filled. (This used to be an assert, which is + // compiled out of every production build since ENABLE_CASSERT defaults to OFF.) + LOG(info) << mDetName << "DigitReader has no entry to read, sending empty output"; + for (uint32_t iLayer = 0; iLayer < mLayers; ++iLayer) { + pc.outputs().snapshot(Output{Origin, "DIGITSROF", iLayer}, std::vector{}); + pc.outputs().snapshot(Output{Origin, "DIGITS", iLayer}, std::vector{}); + if (mUseMC) { + auto& sharedlabels = pc.outputs().make>(Output{Origin, "DIGITSMCTR", iLayer}); + o2::dataformats::MCTruthContainer noLabels; + noLabels.flatten_to(sharedlabels); + pc.outputs().snapshot(Output{Origin, "DIGITSMC2ROF", iLayer}, std::vector{}); + } + } + if (mUseCalib) { + pc.outputs().snapshot(Output{Origin, "GBTCALIB", 0}, std::vector{}); + } + if (mTriggerOut) { + pc.outputs().snapshot(Output{Origin, "PHYSTRIG", 0}, std::vector{}); + } + pc.services().get().endOfStream(); + pc.services().get().readyToQuit(QuitRequest::Me); + return; + } mTree->GetEntry(ent); for (uint32_t iLayer = 0; iLayer < mLayers; ++iLayer) { LOG(info) << mDetName << "DigitReader" << ((mDoStaggering) ? std::format(": {}", iLayer) : "") << " pushes " << mDigROFRec[iLayer]->size() << " ROFRecords, " << mDigits[iLayer]->size() << " digits at entry " << ent; @@ -215,9 +241,13 @@ void DigitReader::connectTree(const std::string& filename) { mTree.reset(nullptr); // in case it was already loaded mFile.reset(TFile::Open(filename.c_str())); - assert(mFile && !mFile->IsZombie()); + if (!mFile || mFile->IsZombie()) { + throw std::runtime_error(std::format("Cannot open {}", filename)); + } mTree.reset((TTree*)mFile->Get(mDigTreeName.c_str())); - assert(mTree); + if (!mTree) { + throw std::runtime_error(std::format("Tree {} not found in {}", mDigTreeName, filename)); + } for (uint32_t iLayer = 0; iLayer < mLayers; ++iLayer) { setBranchAddress(mDigitROFBranchName, mDigROFRec[iLayer], iLayer); setBranchAddress(mDigitBranchName, mDigits[iLayer], iLayer); diff --git a/Detectors/MUON/MCH/IO/src/DigitReaderSpec.cxx b/Detectors/MUON/MCH/IO/src/DigitReaderSpec.cxx index 78a0022e07166..af13460a42dd0 100644 --- a/Detectors/MUON/MCH/IO/src/DigitReaderSpec.cxx +++ b/Detectors/MUON/MCH/IO/src/DigitReaderSpec.cxx @@ -37,6 +37,7 @@ #include "DataFormatsMCH/ROFRecord.h" #include "Framework/ConfigParamRegistry.h" #include "Framework/ControlService.h" +#include "Framework/Logger.h" #include "Framework/DataSpecUtils.h" #include "Framework/Task.h" #include "Framework/WorkflowSpec.h" @@ -109,6 +110,20 @@ class DigitsReaderDeviceDPL void sendNextTF(ProcessingContext& pc) { + // A timeframe holds no collision at all whenever the interaction rate is low enough, and the + // digit tree then has no entry. Send empty containers and finish, rather than throwing. + if (mTreeReader.GetEntries() == 0) { + LOG(info) << "digit tree has no entry, sending empty output"; + pc.outputs().snapshot(OutputRef{"rofs"}, std::vector{}); + pc.outputs().snapshot(OutputRef{"digits"}, std::vector{}); + if (mUseMC) { + pc.outputs().snapshot(OutputRef{"labels"}, dataformats::MCTruthContainer{}); + } + pc.services().get().endOfStream(); + pc.services().get().readyToQuit(QuitRequest::Me); + return; + } + // load the next TF and check its validity (missing branch, ...) if (!mTreeReader.Next()) { throw std::invalid_argument(mTreeReader.fgEntryStatusText[mTreeReader.GetEntryStatus()]); diff --git a/Detectors/MUON/MID/Workflow/src/DigitReaderSpec.cxx b/Detectors/MUON/MID/Workflow/src/DigitReaderSpec.cxx index f65415b8d701a..0479452bcd6f5 100644 --- a/Detectors/MUON/MID/Workflow/src/DigitReaderSpec.cxx +++ b/Detectors/MUON/MID/Workflow/src/DigitReaderSpec.cxx @@ -28,6 +28,7 @@ #include "Framework/ConfigParamRegistry.h" #include "Framework/ControlService.h" +#include "Framework/Logger.h" #include "Framework/DataSpecUtils.h" #include "Framework/Task.h" #include "Framework/WorkflowSpec.h" @@ -103,6 +104,20 @@ class DigitsReaderDeviceDPL void sendNextTF(ProcessingContext& pc) { + // A timeframe holds no collision at all whenever the interaction rate is low enough, and the + // digit tree then has no entry. Send empty containers and finish, rather than throwing. + if (mTreeReader.GetEntries() == 0) { + LOG(info) << "digit tree has no entry, sending empty output"; + pc.outputs().snapshot(OutputRef{"rofs"}, std::vector{}); + pc.outputs().snapshot(OutputRef{"digits"}, std::vector{}); + if (mUseMC) { + pc.outputs().snapshot(OutputRef{"labels"}, dataformats::MCTruthContainer{}); + } + pc.services().get().endOfStream(); + pc.services().get().readyToQuit(QuitRequest::Me); + return; + } + // load the next TF and check its validity (missing branch, ...) if (!mTreeReader.Next()) { throw std::invalid_argument(mTreeReader.fgEntryStatusText[mTreeReader.GetEntryStatus()]); diff --git a/Detectors/PHOS/workflow/src/CellReaderSpec.cxx b/Detectors/PHOS/workflow/src/CellReaderSpec.cxx index c7d93fc20301f..733e55a505974 100644 --- a/Detectors/PHOS/workflow/src/CellReaderSpec.cxx +++ b/Detectors/PHOS/workflow/src/CellReaderSpec.cxx @@ -41,8 +41,17 @@ void CellReader::init(InitContext& ic) void CellReader::run(ProcessingContext& pc) { auto ent = mTree->GetReadEntry() + 1; - assert(ent < mTree->GetEntries()); // this should not happen - mTree->GetEntry(ent); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + mTree->GetEntry(ent); + } LOG(info) << "Pushing " << mCells.size() << " Cells in " << mTRs.size() << " TriggerRecords at entry " << ent; pc.outputs().snapshot(Output{mOrigin, "CELLS", 0}, mCells); pc.outputs().snapshot(Output{mOrigin, "CELLTRIGREC", 0}, mTRs); @@ -50,7 +59,7 @@ void CellReader::run(ProcessingContext& pc) pc.outputs().snapshot(Output{mOrigin, "CELLSMCTR", 0}, mMCTruth); } - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); } diff --git a/Detectors/PHOS/workflow/src/DigitReaderSpec.cxx b/Detectors/PHOS/workflow/src/DigitReaderSpec.cxx index 70f5077b2f0c9..d4aa1d54748fe 100644 --- a/Detectors/PHOS/workflow/src/DigitReaderSpec.cxx +++ b/Detectors/PHOS/workflow/src/DigitReaderSpec.cxx @@ -41,8 +41,17 @@ void DigitReader::init(InitContext& ic) void DigitReader::run(ProcessingContext& pc) { auto ent = mTree->GetReadEntry() + 1; - assert(ent < mTree->GetEntries()); // this should not happen - mTree->GetEntry(ent); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + mTree->GetEntry(ent); + } LOG(info) << "Pushing " << mDigits.size() << " Digits in " << mTRs.size() << " TriggerRecords at entry " << ent; pc.outputs().snapshot(Output{mOrigin, "DIGITS", 0}, mDigits); pc.outputs().snapshot(Output{mOrigin, "DIGITTRIGREC", 0}, mTRs); @@ -50,7 +59,7 @@ void DigitReader::run(ProcessingContext& pc) pc.outputs().snapshot(Output{mOrigin, "DIGITSMCTR", 0}, mMCTruth); } - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); } diff --git a/Detectors/TOF/workflowIO/src/CalibClusReaderSpec.cxx b/Detectors/TOF/workflowIO/src/CalibClusReaderSpec.cxx index 116f93a06c208..56b246c87b513 100644 --- a/Detectors/TOF/workflowIO/src/CalibClusReaderSpec.cxx +++ b/Detectors/TOF/workflowIO/src/CalibClusReaderSpec.cxx @@ -36,8 +36,17 @@ void CalibClusReader::init(InitContext& ic) void CalibClusReader::run(ProcessingContext& pc) { auto ent = mTree->GetReadEntry() + 1; - assert(ent < mTree->GetEntries()); // this should not happen - mTree->GetEntry(ent); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + mTree->GetEntry(ent); + } LOG(debug) << "Pushing " << mPclusInfos->size() << " TOF clusters calib info at entry " << ent; pc.outputs().snapshot(Output{o2::header::gDataOriginTOF, "INFOCALCLUS", 0}, mClusInfos); @@ -48,7 +57,7 @@ void CalibClusReader::run(ProcessingContext& pc) pc.outputs().snapshot(Output{o2::header::gDataOriginTOF, "INFOTRACKSIZE", 0}, mCosmicTrackSize); } - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); } diff --git a/Detectors/TOF/workflowIO/src/ClusterReaderSpec.cxx b/Detectors/TOF/workflowIO/src/ClusterReaderSpec.cxx index e2979a8fc0dbf..ea76c8c7dbb56 100644 --- a/Detectors/TOF/workflowIO/src/ClusterReaderSpec.cxx +++ b/Detectors/TOF/workflowIO/src/ClusterReaderSpec.cxx @@ -40,8 +40,17 @@ void ClusterReader::init(InitContext& ic) void ClusterReader::run(ProcessingContext& pc) { auto ent = mTree->GetReadEntry() + 1; - assert(ent < mTree->GetEntries()); // this should not happen - mTree->GetEntry(ent); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + mTree->GetEntry(ent); + } LOG(debug) << "Pushing " << mClustersPtr->size() << " TOF clusters at entry " << ent; pc.outputs().snapshot(Output{o2::header::gDataOriginTOF, "CLUSTERS", 0}, mClusters); @@ -50,7 +59,7 @@ void ClusterReader::run(ProcessingContext& pc) pc.outputs().snapshot(Output{o2::header::gDataOriginTOF, "CLUSTERSMCTR", 0}, mLabels); } - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); } diff --git a/Detectors/TPC/workflow/readers/src/TrackReaderSpec.cxx b/Detectors/TPC/workflow/readers/src/TrackReaderSpec.cxx index d73da0cb0d33c..c511603d407a7 100644 --- a/Detectors/TPC/workflow/readers/src/TrackReaderSpec.cxx +++ b/Detectors/TPC/workflow/readers/src/TrackReaderSpec.cxx @@ -41,9 +41,17 @@ void TrackReader::init(InitContext& ic) void TrackReader::run(ProcessingContext& pc) { auto ent = mTree->GetReadEntry() + 1; - accumulate(ent, 1); // to really accumulate all, use accumulate(ent,mTree->GetEntries()); - assert(ent < mTree->GetEntries()); // this should not happen - mTree->GetEntry(ent); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + accumulate(ent, 1); // to really accumulate all, use accumulate(ent,mTree->GetEntries()); + } using TrackTunePar = o2::globaltracking::TrackTuneParams; const auto& trackTune = TrackTunePar::Instance(); // Normally we should not apply tuning here as with sourceLevelTPC==true it is already applied in the tracking. @@ -75,7 +83,7 @@ void TrackReader::run(ProcessingContext& pc) if (mUseMC) { pc.outputs().snapshot(Output{"TPC", "TRACKSMCLBL", 0}, mMCTruthOut); } - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); } diff --git a/Detectors/TRD/workflow/io/src/TRDTrackReaderSpec.cxx b/Detectors/TRD/workflow/io/src/TRDTrackReaderSpec.cxx index cd9702a3d2385..965dd252b26bd 100644 --- a/Detectors/TRD/workflow/io/src/TRDTrackReaderSpec.cxx +++ b/Detectors/TRD/workflow/io/src/TRDTrackReaderSpec.cxx @@ -38,8 +38,17 @@ void TRDTrackReader::init(InitContext& ic) void TRDTrackReader::run(ProcessingContext& pc) { auto ent = mTree->GetReadEntry() + 1; - assert(ent < mTree->GetEntries()); // this should not happen - mTree->GetEntry(ent); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + mTree->GetEntry(ent); + } LOG(info) << "Pushing " << mTracks.size() << " tracks and " << mTrigRec.size() << " trigger records at entry " << ent; if (mUseMC) { if (mLabelsTrd.size() != mLabelsMatch.size()) { @@ -65,7 +74,7 @@ void TRDTrackReader::run(ProcessingContext& pc) } } - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); } diff --git a/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/DigitReaderSpec.cxx b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/DigitReaderSpec.cxx index ec2b6d4d66192..16bb1941fe785 100644 --- a/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/DigitReaderSpec.cxx +++ b/Detectors/Upgrades/ALICE3/TRKFT3/common/workflow/src/DigitReaderSpec.cxx @@ -19,6 +19,7 @@ #include "TRKWorkflow/DigitReaderSpec.h" #include "SimulationDataFormat/MCCompLabel.h" #include "SimulationDataFormat/ConstMCTruthContainer.h" +#include "SimulationDataFormat/MCTruthContainer.h" #include "SimulationDataFormat/IOMCTruthContainerView.h" #include @@ -60,21 +61,39 @@ void DigitReader::init(InitContext& ic) void DigitReader::run(ProcessingContext& pc) { auto ent = mTree->GetReadEntry() + 1; - assert(ent < mTree->GetEntries()); // this should not happen - mTree->GetEntry(ent); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + mTree->GetEntry(ent); + } + static const std::vector noDigROFRec; + static const std::vector noDigits; for (int iLayer = 0; iLayer < mLayers; ++iLayer) { - LOG(info) << mDetName << "DigitReader on layer " << iLayer << " pushes " << mDigROFRec[iLayer]->size() << " ROFRecords, " - << mDigits[iLayer]->size() << " digits at entry " << ent; + const auto& digROFRec = noEntry ? noDigROFRec : *mDigROFRec[iLayer]; + const auto& digits = noEntry ? noDigits : *mDigits[iLayer]; + LOG(info) << mDetName << "DigitReader on layer " << iLayer << " pushes " << digROFRec.size() << " ROFRecords, " + << digits.size() << " digits at entry " << ent; - pc.outputs().snapshot(Output{mOrigin, "DIGITSROF", static_cast(iLayer)}, *mDigROFRec[iLayer]); - pc.outputs().snapshot(Output{mOrigin, "DIGITS", static_cast(iLayer)}, *mDigits[iLayer]); + pc.outputs().snapshot(Output{mOrigin, "DIGITSROF", static_cast(iLayer)}, digROFRec); + pc.outputs().snapshot(Output{mOrigin, "DIGITS", static_cast(iLayer)}, digits); if (mUseMC) { auto& sharedlabels = pc.outputs().make>(Output{mOrigin, "DIGITSMCTR", static_cast(iLayer)}); - mPLabels[iLayer]->copyandflatten(sharedlabels); - delete mPLabels[iLayer]; - mPLabels[iLayer] = nullptr; + if (noEntry) { + o2::dataformats::MCTruthContainer noLabels; + noLabels.flatten_to(sharedlabels); + } else { + mPLabels[iLayer]->copyandflatten(sharedlabels); + delete mPLabels[iLayer]; + mPLabels[iLayer] = nullptr; + } } } @@ -82,7 +101,7 @@ void DigitReader::run(ProcessingContext& pc) pc.outputs().snapshot(Output{mOrigin, "GBTCALIB", 0}, mCalib); } - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); } diff --git a/Detectors/Upgrades/ITS3/workflow/src/DigitReaderSpec.cxx b/Detectors/Upgrades/ITS3/workflow/src/DigitReaderSpec.cxx index 141457c319b9b..04300e43ac343 100644 --- a/Detectors/Upgrades/ITS3/workflow/src/DigitReaderSpec.cxx +++ b/Detectors/Upgrades/ITS3/workflow/src/DigitReaderSpec.cxx @@ -19,6 +19,7 @@ #include "ITS3Workflow/DigitReaderSpec.h" #include "SimulationDataFormat/MCCompLabel.h" #include "SimulationDataFormat/ConstMCTruthContainer.h" +#include "SimulationDataFormat/MCTruthContainer.h" #include "SimulationDataFormat/IOMCTruthContainerView.h" #include #include @@ -47,28 +48,45 @@ void ITS3DigitReader::init(InitContext& ic) void ITS3DigitReader::run(ProcessingContext& pc) { auto ent = mTree->GetReadEntry() + 1; - assert(ent < mTree->GetEntries()); // this should not happen - - mTree->GetEntry(ent); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + mTree->GetEntry(ent); + } + static const std::vector noDigROFRec; + static const std::vector noDigits; for (uint32_t iLayer = 0; iLayer < (mDoStaggering ? NLayers : 1); ++iLayer) { - if (!mDigROFRec[iLayer] || !mDigits[iLayer]) { + if (!noEntry && (!mDigROFRec[iLayer] || !mDigits[iLayer])) { throw std::runtime_error("ITS3 digit reader requires all 7 layer branches to be present and populated in every entry"); } - LOG(info) << mDetName << "DigitReader pushes " << mDigROFRec[iLayer]->size() << " ROFRecords, " << mDigits[iLayer]->size() << " digits at entry " << ent << " on layer " << iLayer; - pc.outputs().snapshot(Output{mOrigin, "DIGITSROF", iLayer}, *mDigROFRec[iLayer]); - pc.outputs().snapshot(Output{mOrigin, "DIGITS", iLayer}, *mDigits[iLayer]); + const auto& digROFRec = noEntry ? noDigROFRec : *mDigROFRec[iLayer]; + const auto& digits = noEntry ? noDigits : *mDigits[iLayer]; + LOG(info) << mDetName << "DigitReader pushes " << digROFRec.size() << " ROFRecords, " << digits.size() << " digits at entry " << ent << " on layer " << iLayer; + pc.outputs().snapshot(Output{mOrigin, "DIGITSROF", iLayer}, digROFRec); + pc.outputs().snapshot(Output{mOrigin, "DIGITS", iLayer}, digits); if (mUseMC) { - if (!mPLabels[iLayer]) { + if (!noEntry && !mPLabels[iLayer]) { throw std::runtime_error("ITS3 digit reader requires MC truth branches for all 7 layers to be present and populated in every entry"); } auto& sharedlabels = pc.outputs().make>(Output{mOrigin, "DIGITSMCTR", iLayer}); - mPLabels[iLayer]->copyandflatten(sharedlabels); - delete mPLabels[iLayer]; - mPLabels[iLayer] = nullptr; + if (noEntry) { + o2::dataformats::MCTruthContainer noLabels; + noLabels.flatten_to(sharedlabels); + } else { + mPLabels[iLayer]->copyandflatten(sharedlabels); + delete mPLabels[iLayer]; + mPLabels[iLayer] = nullptr; + } } } - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); } diff --git a/Detectors/Vertexing/src/SVertexer.cxx b/Detectors/Vertexing/src/SVertexer.cxx index bf7d436ca150c..66e85d498e553 100644 --- a/Detectors/Vertexing/src/SVertexer.cxx +++ b/Detectors/Vertexing/src/SVertexer.cxx @@ -463,7 +463,9 @@ void SVertexer::buildT2V(const o2::globaltracking::RecoContainer& recoData) // a std::unordered_map> tmap; std::unordered_map rejmap; - int nv = vtxRefs.size() - 1; // The last entry is for unassigned tracks, ignore them + // The last entry is for unassigned tracks, ignore them. A timeframe holding no collision at + // all has no entry, and the subtraction would then wrap around. + int nv = vtxRefs.size() > 0 ? vtxRefs.size() - 1 : 0; for (int i = 0; i < 2; i++) { mTracksPool[i].clear(); mVtxFirstTrack[i].clear(); diff --git a/Detectors/ZDC/workflow/src/DigitReaderSpec.cxx b/Detectors/ZDC/workflow/src/DigitReaderSpec.cxx index e952111e0c6c3..0384115816da5 100644 --- a/Detectors/ZDC/workflow/src/DigitReaderSpec.cxx +++ b/Detectors/ZDC/workflow/src/DigitReaderSpec.cxx @@ -66,8 +66,17 @@ void DigitReader::run(ProcessingContext& pc) } auto ent = mTree->GetReadEntry() < 0 ? mTree->GetReadEntry() + mFirstEntry + 1 : mTree->GetReadEntry() + 1; - assert(ent < mTree->GetEntries()); // this should not happen - mTree->GetEntry(ent); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + mTree->GetEntry(ent); + } LOG(info) << "ZDCDigitReader pushed " << zdcOrbitData.size() << " orbits with " << zdcBCData.size() << " bcs and " << zdcChData.size() << " digits"; pc.outputs().snapshot(Output{"ZDC", "DIGITSPD", 0}, zdcOrbitData); pc.outputs().snapshot(Output{"ZDC", "DIGITSBC", 0}, zdcBCData); @@ -76,7 +85,7 @@ void DigitReader::run(ProcessingContext& pc) pc.outputs().snapshot(Output{"ZDC", "DIGITSLBL", 0}, labels); } uint64_t nextEntry = mTree->GetReadEntry() + 1; - if (nextEntry >= mTree->GetEntries() || (mLastEntry >= 0 && nextEntry > mLastEntry)) { + if (noEntry || nextEntry >= mTree->GetEntries() || (mLastEntry >= 0 && nextEntry > mLastEntry)) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); } diff --git a/Detectors/ZDC/workflow/src/RecEventReaderSpec.cxx b/Detectors/ZDC/workflow/src/RecEventReaderSpec.cxx index 18c620e427569..f5678b4065a5e 100644 --- a/Detectors/ZDC/workflow/src/RecEventReaderSpec.cxx +++ b/Detectors/ZDC/workflow/src/RecEventReaderSpec.cxx @@ -45,16 +45,33 @@ void RecEventReader::init(InitContext& ic) void RecEventReader::run(ProcessingContext& pc) { auto ent = mTree->GetReadEntry() + 1; - assert(ent < mTree->GetEntries()); // this should not happen - mTree->GetEntry(ent); - - LOG(info) << "ZDC RecEventReader pushes " << mBCRecData->size() << " events with " << mBCRecData->size() << " energy, " << mZDCTDCData->size() << " TDC and " << mZDCInfo->size() << " info records at entry " << ent; - pc.outputs().snapshot(Output{"ZDC", "BCREC", 0}, *mBCRecData); - pc.outputs().snapshot(Output{"ZDC", "ENERGY", 0}, *mZDCEnergy); - pc.outputs().snapshot(Output{"ZDC", "TDCDATA", 0}, *mZDCTDCData); - pc.outputs().snapshot(Output{"ZDC", "INFO", 0}, *mZDCInfo); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + mTree->GetEntry(ent); + } - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + static const std::vector noBCRecData; + static const std::vector noEnergy; + static const std::vector noTDCData; + static const std::vector noInfo; + const auto& bcRecData = noEntry ? noBCRecData : *mBCRecData; + const auto& energy = noEntry ? noEnergy : *mZDCEnergy; + const auto& tdcData = noEntry ? noTDCData : *mZDCTDCData; + const auto& info = noEntry ? noInfo : *mZDCInfo; + LOG(info) << "ZDC RecEventReader pushes " << bcRecData.size() << " events with " << energy.size() << " energy, " << tdcData.size() << " TDC and " << info.size() << " info records at entry " << ent; + pc.outputs().snapshot(Output{"ZDC", "BCREC", 0}, bcRecData); + pc.outputs().snapshot(Output{"ZDC", "ENERGY", 0}, energy); + pc.outputs().snapshot(Output{"ZDC", "TDCDATA", 0}, tdcData); + pc.outputs().snapshot(Output{"ZDC", "INFO", 0}, info); + + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); } diff --git a/Detectors/ZDC/workflow/src/RecoReaderSpec.cxx b/Detectors/ZDC/workflow/src/RecoReaderSpec.cxx index 33b2b59d8247b..87c6b6c36cc8b 100644 --- a/Detectors/ZDC/workflow/src/RecoReaderSpec.cxx +++ b/Detectors/ZDC/workflow/src/RecoReaderSpec.cxx @@ -64,8 +64,17 @@ void RecoReader::run(ProcessingContext& pc) mTree->SetBranchAddress("ZDCWaveform", &WaveformDataPtr); auto ent = mTree->GetReadEntry() + 1; - assert(ent < mTree->GetEntries()); // this should not happen - mTree->GetEntry(ent); + // A timeframe holds no collision at all whenever the interaction rate is low enough, and + // the tree then has no entry to read. Publish empty containers instead of reading past the + // end and pushing branch addresses that GetEntry has not filled, so that the consumers + // downstream still see the timeframe. (This used to be an assert, which is compiled out of + // every production build since ENABLE_CASSERT defaults to OFF.) + const bool noEntry = ent >= mTree->GetEntries(); + if (noEntry) { + LOG(info) << "no entry to read, publishing empty output"; + } else { + mTree->GetEntry(ent); + } LOG(info) << "ZDCRecoReader pushed " << RecBC.size() << " b.c. " << Energy.size() << " Energies " << TDCData.size() << " TDCs " << Info.size() << " Infos " << WaveformData.size() << " Waveform chunks"; pc.outputs().snapshot(Output{"ZDC", "BCREC", 0}, RecBC); pc.outputs().snapshot(Output{"ZDC", "ENERGY", 0}, Energy); @@ -73,7 +82,7 @@ void RecoReader::run(ProcessingContext& pc) pc.outputs().snapshot(Output{"ZDC", "INFO", 0}, Info); pc.outputs().snapshot(Output{"ZDC", "WAVE", 0}, WaveformData); - if (mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { + if (noEntry || mTree->GetReadEntry() + 1 >= mTree->GetEntries()) { pc.services().get().endOfStream(); pc.services().get().readyToQuit(QuitRequest::Me); } diff --git a/Framework/Utils/include/DPLUtils/RootTreeReader.h b/Framework/Utils/include/DPLUtils/RootTreeReader.h index bc743d713b520..f2c5ebcc107e3 100644 --- a/Framework/Utils/include/DPLUtils/RootTreeReader.h +++ b/Framework/Utils/include/DPLUtils/RootTreeReader.h @@ -305,9 +305,21 @@ class GenericRootTreeReader context.outputs().snapshot(Output{key.origin, key.description, key.subSpec, std::move(stackcreator())}, object); }; + // A tree can have no entry at all, which is what a timeframe without a single collision + // looks like. Publish a default-constructed object in that case, so that the consumers + // downstream still see the timeframe instead of getting nothing at all. Everything below + // stays the same, including a registered publishing hook, which needs a valid object. char* data = nullptr; - mBranch->SetAddress(&data); - mBranch->GetEntry(entry); + if (entry >= 0) { + mBranch->SetAddress(&data); + mBranch->GetEntry(entry); + } else { + data = reinterpret_cast(mClassInfo->New()); + if (data == nullptr) { + LOG(error) << "branch " << mName << ": cannot create an empty " << mClassInfo->GetName() << ", nothing published"; + return; + } + } // execute hook if it was registered; if this return true do not proceed further if (mPublishHook != nullptr && (*mPublishHook).hook(mName, context, Output{mKey.origin, mKey.description, mKey.subSpec, std::move(stackcreator())}, data)) { @@ -317,8 +329,10 @@ class GenericRootTreeReader else { if (mSizeBranch != nullptr) { size_t datasize = 0; - mSizeBranch->SetAddress(&datasize); - mSizeBranch->GetEntry(entry); + if (entry >= 0) { + mSizeBranch->SetAddress(&datasize); + mSizeBranch->GetEntry(entry); + } auto* buffer = reinterpret_cast(data); if (buffer->size() == datasize) { LOG(debug) << "branch " << mName << ": publishing binary chunk of " << datasize << " bytes(s)"; @@ -345,7 +359,9 @@ class GenericRootTreeReader if (delfunc) { (*delfunc)(data); } - mBranch->DropBaskets("all"); + if (entry >= 0) { + mBranch->DropBaskets("all"); + } } private: @@ -412,7 +428,16 @@ class GenericRootTreeReader /// @return true if data is available bool next() { - if ((mReadEntry + 1) >= mNEntries || mNEntries == 0) { + if (mNEntries == 0) { + // The tree has no entry at all. Publish one empty entry and stop, in every publishing + // mode: looping over nothing would never produce anything to publish. + if (mNofPublished >= 0) { + return false; + } + ++mNofPublished; + return true; + } + if ((mReadEntry + 1) >= mNEntries) { if (mPublishingMode == PublishingMode::Single) { // stop here if (mReadEntry < mNEntries) { @@ -458,7 +483,11 @@ class GenericRootTreeReader bool operator()(ContextType& context, HeaderTypes&&... headers) const { - if (mReadEntry >= mNEntries || mNEntries == 0 || (mMaxEntries > 0 && mNofPublished >= mMaxEntries)) { + if (mNEntries == 0) { + if (mNofPublished != 0) { // next() has to have selected the one empty entry + return false; + } + } else if (mReadEntry >= mNEntries || (mMaxEntries > 0 && mNofPublished >= mMaxEntries)) { return false; }