Skip to content

Commit 847ce5b

Browse files
sawenzelclaude
andcommitted
Let the digit readers accept a timeframe without collisions
This fixes a crash in the ITS, MFT, MCH and MID digit readers when the digit tree of a timeframe has no entry. - A timeframe holds no collision at all whenever the interaction rate is low enough, and the digitiser then writes a valid tree with zero entries. - The ITS/MFT reader guarded this with an assert, which is compiled out of every production build because ENABLE_CASSERT defaults to OFF, and then dereferenced branch addresses that GetEntry had not filled. - The MCH and MID readers threw on the failed TTreeReader::Next(). - All four now send empty output and end the stream. - The two asserts in the ITS/MFT connectTree become real errors for the same reason. https://its.cern.ch/jira/browse/O2-7132 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 77c2fc3 commit 847ce5b

3 files changed

Lines changed: 63 additions & 3 deletions

File tree

Detectors/ITSMFT/common/workflow/src/DigitReaderSpec.cxx

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "ITSMFTReconstruction/ChipMappingMFT.h"
2727
#include "SimulationDataFormat/MCCompLabel.h"
2828
#include "SimulationDataFormat/ConstMCTruthContainer.h"
29+
#include "SimulationDataFormat/MCTruthContainer.h"
2930
#include "DataFormatsITSMFT/PhysTrigger.h"
3031
#include "CommonUtils/NameConf.h"
3132
#include "CommonDataFormat/IRFrame.h"
@@ -101,7 +102,32 @@ void DigitReader<N>::run(ProcessingContext& pc)
101102
auto ent = mTree->GetReadEntry();
102103
if (!mUseIRFrames) {
103104
ent++;
104-
assert(ent < mTree->GetEntries()); // this should not happen
105+
if (ent >= mTree->GetEntries()) {
106+
// A timeframe holds no collision at all whenever the interaction rate is low enough, and the
107+
// digit tree then has no entry to read. Send empty output rather than dereferencing the
108+
// branch addresses, which GetEntry has not filled. (This used to be an assert, which is
109+
// compiled out of every production build since ENABLE_CASSERT defaults to OFF.)
110+
LOG(info) << mDetName << "DigitReader has no entry to read, sending empty output";
111+
for (uint32_t iLayer = 0; iLayer < mLayers; ++iLayer) {
112+
pc.outputs().snapshot(Output{Origin, "DIGITSROF", iLayer}, std::vector<o2::itsmft::ROFRecord>{});
113+
pc.outputs().snapshot(Output{Origin, "DIGITS", iLayer}, std::vector<o2::itsmft::Digit>{});
114+
if (mUseMC) {
115+
auto& sharedlabels = pc.outputs().make<o2::dataformats::ConstMCTruthContainer<o2::MCCompLabel>>(Output{Origin, "DIGITSMCTR", iLayer});
116+
o2::dataformats::MCTruthContainer<o2::MCCompLabel> noLabels;
117+
noLabels.flatten_to(sharedlabels);
118+
pc.outputs().snapshot(Output{Origin, "DIGITSMC2ROF", iLayer}, std::vector<o2::itsmft::MC2ROFRecord>{});
119+
}
120+
}
121+
if (mUseCalib) {
122+
pc.outputs().snapshot(Output{Origin, "GBTCALIB", 0}, std::vector<o2::itsmft::GBTCalibData>{});
123+
}
124+
if (mTriggerOut) {
125+
pc.outputs().snapshot(Output{Origin, "PHYSTRIG", 0}, std::vector<o2::itsmft::PhysTrigger>{});
126+
}
127+
pc.services().get<ControlService>().endOfStream();
128+
pc.services().get<ControlService>().readyToQuit(QuitRequest::Me);
129+
return;
130+
}
105131
mTree->GetEntry(ent);
106132
for (uint32_t iLayer = 0; iLayer < mLayers; ++iLayer) {
107133
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<N>::connectTree(const std::string& filename)
215241
{
216242
mTree.reset(nullptr); // in case it was already loaded
217243
mFile.reset(TFile::Open(filename.c_str()));
218-
assert(mFile && !mFile->IsZombie());
244+
if (!mFile || mFile->IsZombie()) {
245+
throw std::runtime_error(std::format("Cannot open {}", filename));
246+
}
219247
mTree.reset((TTree*)mFile->Get(mDigTreeName.c_str()));
220-
assert(mTree);
248+
if (!mTree) {
249+
throw std::runtime_error(std::format("Tree {} not found in {}", mDigTreeName, filename));
250+
}
221251
for (uint32_t iLayer = 0; iLayer < mLayers; ++iLayer) {
222252
setBranchAddress(mDigitROFBranchName, mDigROFRec[iLayer], iLayer);
223253
setBranchAddress(mDigitBranchName, mDigits[iLayer], iLayer);

Detectors/MUON/MCH/IO/src/DigitReaderSpec.cxx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include "DataFormatsMCH/ROFRecord.h"
3838
#include "Framework/ConfigParamRegistry.h"
3939
#include "Framework/ControlService.h"
40+
#include "Framework/Logger.h"
4041
#include "Framework/DataSpecUtils.h"
4142
#include "Framework/Task.h"
4243
#include "Framework/WorkflowSpec.h"
@@ -109,6 +110,20 @@ class DigitsReaderDeviceDPL
109110

110111
void sendNextTF(ProcessingContext& pc)
111112
{
113+
// A timeframe holds no collision at all whenever the interaction rate is low enough, and the
114+
// digit tree then has no entry. Send empty containers and finish, rather than throwing.
115+
if (mTreeReader.GetEntries() == 0) {
116+
LOG(info) << "digit tree has no entry, sending empty output";
117+
pc.outputs().snapshot(OutputRef{"rofs"}, std::vector<ROFRecord>{});
118+
pc.outputs().snapshot(OutputRef{"digits"}, std::vector<Digit>{});
119+
if (mUseMC) {
120+
pc.outputs().snapshot(OutputRef{"labels"}, dataformats::MCTruthContainer<MCCompLabel>{});
121+
}
122+
pc.services().get<ControlService>().endOfStream();
123+
pc.services().get<ControlService>().readyToQuit(QuitRequest::Me);
124+
return;
125+
}
126+
112127
// load the next TF and check its validity (missing branch, ...)
113128
if (!mTreeReader.Next()) {
114129
throw std::invalid_argument(mTreeReader.fgEntryStatusText[mTreeReader.GetEntryStatus()]);

Detectors/MUON/MID/Workflow/src/DigitReaderSpec.cxx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
#include "Framework/ConfigParamRegistry.h"
3030
#include "Framework/ControlService.h"
31+
#include "Framework/Logger.h"
3132
#include "Framework/DataSpecUtils.h"
3233
#include "Framework/Task.h"
3334
#include "Framework/WorkflowSpec.h"
@@ -103,6 +104,20 @@ class DigitsReaderDeviceDPL
103104

104105
void sendNextTF(ProcessingContext& pc)
105106
{
107+
// A timeframe holds no collision at all whenever the interaction rate is low enough, and the
108+
// digit tree then has no entry. Send empty containers and finish, rather than throwing.
109+
if (mTreeReader.GetEntries() == 0) {
110+
LOG(info) << "digit tree has no entry, sending empty output";
111+
pc.outputs().snapshot(OutputRef{"rofs"}, std::vector<ROFRecord>{});
112+
pc.outputs().snapshot(OutputRef{"digits"}, std::vector<ColumnData>{});
113+
if (mUseMC) {
114+
pc.outputs().snapshot(OutputRef{"labels"}, dataformats::MCTruthContainer<MCLabel>{});
115+
}
116+
pc.services().get<ControlService>().endOfStream();
117+
pc.services().get<ControlService>().readyToQuit(QuitRequest::Me);
118+
return;
119+
}
120+
106121
// load the next TF and check its validity (missing branch, ...)
107122
if (!mTreeReader.Next()) {
108123
throw std::invalid_argument(mTreeReader.fgEntryStatusText[mTreeReader.GetEntryStatus()]);

0 commit comments

Comments
 (0)