Skip to content

Commit 404624e

Browse files
committed
Skip invalid timeframes during ROOT input
Handle corrupt reads as recoverable, discarding the timeframe
1 parent 9835670 commit 404624e

6 files changed

Lines changed: 117 additions & 32 deletions

File tree

Framework/AnalysisSupport/src/AODJAlienReaderHelpers.cxx

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,16 @@
1919
#include "Framework/DataProcessingStats.h"
2020
#include "Framework/RootArrowFilesystem.h"
2121
#include "Framework/AlgorithmSpec.h"
22+
#include "Framework/ArrowContext.h"
2223
#include "Framework/ConfigParamRegistry.h"
2324
#include "Framework/ControlService.h"
2425
#include "Framework/CallbackService.h"
2526
#include "Framework/EndOfStreamContext.h"
2627
#include "Framework/DeviceSpec.h"
2728
#include "Framework/RawDeviceService.h"
2829
#include "Framework/DataSpecUtils.h"
30+
#include "Framework/MessageContext.h"
31+
#include "Framework/StringContext.h"
2932
#include "Framework/ConfigContext.h"
3033
#include "DataInputDirector.h"
3134
#include "Framework/SourceInfoHeader.h"
@@ -200,7 +203,7 @@ AlgorithmSpec AODJAlienReaderHelpers::rootFileReaderCallback(ConfigContext const
200203
numTF,
201204
watchdog,
202205
maxRate,
203-
didir, reportTFN, reportTFFileName, level](Monitoring& monitoring, DataAllocator& outputs, ControlService& control, DeviceSpec const& device, DataProcessingStats& dpstats) {
206+
didir, reportTFN, reportTFFileName, level](Monitoring& monitoring, DataAllocator& outputs, ControlService& control, DeviceSpec const& device, DataProcessingStats& dpstats, ArrowContext& arrowContext, MessageContext& messageContext, StringContext& stringContext) {
204207
// Each parallel reader device.inputTimesliceId reads the files fileCounter*device.maxInputTimeslices+device.inputTimesliceId
205208
// the TF to read is numTF
206209
assert(device.inputTimesliceId < device.maxInputTimeslices);
@@ -218,6 +221,7 @@ AlgorithmSpec AODJAlienReaderHelpers::rootFileReaderCallback(ConfigContext const
218221
static size_t totalSizeUncompressed = 0;
219222
static size_t totalSizeCompressed = 0;
220223
static uint64_t totalDFSent = 0;
224+
static uint64_t totalInvalidReadSkipped = 0;
221225

222226
// check if RuntimeLimit is reached
223227
if (!watchdog->update()) {
@@ -232,6 +236,17 @@ AlgorithmSpec AODJAlienReaderHelpers::rootFileReaderCallback(ConfigContext const
232236

233237
int64_t startTime = uv_hrtime();
234238
int64_t startSize = totalSizeCompressed;
239+
auto skipInvalidRead = [&](o2::header::DataOrigin const& origin, InvalidAODReadError const& e) {
240+
auto skippedTimeframes = ++totalInvalidReadSkipped;
241+
LOGP(error, "Invalid AOD read for table {}: fileCounter {}, timeFrame {}. Skipping timeframe (skipped timeframes: {}). Reason: {}",
242+
origin.as<std::string>(), fcnt, ntf, skippedTimeframes, e.what());
243+
arrowContext.clear();
244+
messageContext.discard();
245+
stringContext.clear();
246+
monitoring.send(Metric{skippedTimeframes, "aod-invalid-read-skipped-timeframes"}.addTag(Key::Subsystem, monitoring::tags::Value::DPL));
247+
*fileCounter = (fcnt - device.inputTimesliceId) / device.maxInputTimeslices;
248+
*numTF = ntf;
249+
};
235250
for (auto& route : requestedTables) {
236251
if ((device.inputTimesliceId % route.maxTimeslices) != route.timeslice) {
237252
continue;
@@ -242,25 +257,38 @@ AlgorithmSpec AODJAlienReaderHelpers::rootFileReaderCallback(ConfigContext const
242257
auto dh = header::DataHeader(concrete.description, concrete.origin, concrete.subSpec);
243258
bool wasAOD = std::ranges::any_of(route.matcher.metadata, [](ConfigParamSpec const& p) { return p.name.starts_with("aod-origin-replaced"); });
244259

245-
if (!didir->readTree(outputs, dh, fcnt, ntf, totalSizeCompressed, totalSizeUncompressed, wasAOD)) {
246-
if (first) {
247-
// check if there is a next file to read
248-
fcnt += device.maxInputTimeslices;
249-
if (didir->atEnd(fcnt)) {
250-
LOGP(info, "No input files left to read for reader {}!", device.inputTimesliceId);
251-
didir->closeInputFiles();
252-
monitoring.flushBuffer();
253-
control.endOfStream();
254-
control.readyToQuit(QuitRequest::Me);
255-
return;
256-
}
257-
// get first folder of next file
258-
ntf = 0;
259-
if (!didir->readTree(outputs, dh, fcnt, ntf, totalSizeCompressed, totalSizeUncompressed, wasAOD)) {
260-
LOGP(fatal, "Can not retrieve tree for table {}: fileCounter {}, timeFrame {}", concrete.origin.as<std::string>(), fcnt, ntf);
261-
throw std::runtime_error("Processing is stopped!");
262-
}
263-
} else {
260+
bool treeRead = false;
261+
try {
262+
treeRead = didir->readTree(outputs, dh, fcnt, ntf, totalSizeCompressed, totalSizeUncompressed, wasAOD);
263+
} catch (InvalidAODReadError const& e) {
264+
skipInvalidRead(concrete.origin, e);
265+
return;
266+
}
267+
268+
if (!treeRead) {
269+
if (!first) {
270+
LOGP(fatal, "Can not retrieve tree for table {}: fileCounter {}, timeFrame {}", concrete.origin.as<std::string>(), fcnt, ntf);
271+
throw std::runtime_error("Processing is stopped!");
272+
}
273+
// check if there is a next file to read
274+
fcnt += device.maxInputTimeslices;
275+
if (didir->atEnd(fcnt)) {
276+
LOGP(info, "No input files left to read for reader {}!", device.inputTimesliceId);
277+
didir->closeInputFiles();
278+
monitoring.flushBuffer();
279+
control.endOfStream();
280+
control.readyToQuit(QuitRequest::Me);
281+
return;
282+
}
283+
// get first folder of next file
284+
ntf = 0;
285+
try {
286+
treeRead = didir->readTree(outputs, dh, fcnt, ntf, totalSizeCompressed, totalSizeUncompressed, wasAOD);
287+
} catch (InvalidAODReadError const& e) {
288+
skipInvalidRead(concrete.origin, e);
289+
return;
290+
}
291+
if (!treeRead) {
264292
LOGP(fatal, "Can not retrieve tree for table {}: fileCounter {}, timeFrame {}", concrete.origin.as<std::string>(), fcnt, ntf);
265293
throw std::runtime_error("Processing is stopped!");
266294
}

Framework/AnalysisSupport/src/DataInputDirector.cxx

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#include <arrow/dataset/file_base.h>
3535
#include <arrow/dataset/dataset.h>
3636
#include <uv.h>
37+
#include <exception>
3738
#include <memory>
3839

3940
#if __has_include(<TJAlienFile.h>)
@@ -536,18 +537,25 @@ bool DataInputDescriptor::readTree(DataAllocator& outputs, header::DataHeader dh
536537
if (!format) {
537538
t.deactivate();
538539
LOGP(debug, "Could not find tree {}. Trying in parent file.", fullpath.path());
539-
auto parentFile = getParentFile(counter, numTF, treename, wantedLevel, wantedOrigin);
540-
if (parentFile != nullptr) {
541-
int parentNumTF = parentFile->findDFNumber(0, folder.path());
542-
if (parentNumTF == -1) {
543-
auto parentRootFS = std::dynamic_pointer_cast<TFileFileSystem>(parentFile->mCurrentFilesystem);
544-
throw std::runtime_error(fmt::format(R"(DF {} listed in parent file map but not found in the corresponding file "{}")", folder.path(), parentRootFS->GetFile()->GetName()));
545-
}
546-
// first argument is 0 as the parent file object contains only 1 file
547-
return parentFile->readTree(outputs, dh, 0, parentNumTF, treename, totalSizeCompressed, totalSizeUncompressed);
540+
std::shared_ptr<DataInputDescriptor> parentFile;
541+
try {
542+
parentFile = getParentFile(counter, numTF, treename, wantedLevel, wantedOrigin);
543+
} catch (std::exception const& e) {
544+
throw InvalidAODReadError(fmt::format("Unable to resolve parent file for tree {}: {}", treename, e.what()));
545+
} catch (...) {
546+
throw InvalidAODReadError(fmt::format("Unable to resolve parent file for tree {}", treename));
547+
}
548+
if (parentFile == nullptr) {
549+
auto rootFS = std::dynamic_pointer_cast<TFileFileSystem>(mCurrentFilesystem);
550+
throw std::runtime_error(fmt::format(R"(Couldn't get TTree "{}" from "{}". Please check https://aliceo2group.github.io/analysis-framework/docs/troubleshooting/#tree-not-found for more information.)", fullpath.path(), rootFS->GetFile()->GetName()));
548551
}
549-
auto rootFS = std::dynamic_pointer_cast<TFileFileSystem>(mCurrentFilesystem);
550-
throw std::runtime_error(fmt::format(R"(Couldn't get TTree "{}" from "{}". Please check https://aliceo2group.github.io/analysis-framework/docs/troubleshooting/#tree-not-found for more information.)", fullpath.path(), rootFS->GetFile()->GetName()));
552+
int parentNumTF = parentFile->findDFNumber(0, folder.path());
553+
if (parentNumTF == -1) {
554+
auto parentRootFS = std::dynamic_pointer_cast<TFileFileSystem>(parentFile->mCurrentFilesystem);
555+
throw InvalidAODReadError(fmt::format(R"(DF {} listed in parent file map but not found in the corresponding file "{}")", folder.path(), parentRootFS->GetFile()->GetName()));
556+
}
557+
// first argument is 0 as the parent file object contains only 1 file
558+
return parentFile->readTree(outputs, dh, 0, parentNumTF, treename, totalSizeCompressed, totalSizeUncompressed);
551559
}
552560

553561
auto schemaOpt = format->Inspect(fullpath);
@@ -573,7 +581,23 @@ bool DataInputDescriptor::readTree(DataAllocator& outputs, header::DataHeader dh
573581
//// add branches to read
574582
//// fill the table
575583
f2b->setLabel(treename.c_str());
576-
f2b->fill(datasetSchema, format);
584+
try {
585+
f2b->fill(datasetSchema, format);
586+
} catch (std::exception const& e) {
587+
f2b.discard();
588+
throw InvalidAODReadError(fmt::format("Unable to read tree {}: {}", treename, e.what()));
589+
} catch (...) {
590+
f2b.discard();
591+
throw InvalidAODReadError(fmt::format("Unable to read tree {}", treename));
592+
}
593+
594+
try {
595+
f2b.release();
596+
} catch (std::exception const& e) {
597+
throw InvalidAODReadError(fmt::format("Unable to finalize tree {}: {}", treename, e.what()));
598+
} catch (...) {
599+
throw InvalidAODReadError(fmt::format("Unable to finalize tree {}", treename));
600+
}
577601

578602
return true;
579603
}

Framework/AnalysisSupport/src/DataInputDirector.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <arrow/dataset/dataset.h>
2222

2323
#include <regex>
24+
#include <stdexcept>
2425
#include <vector>
2526
#include "rapidjson/fwd.h"
2627

@@ -32,6 +33,12 @@ class Monitoring;
3233
namespace o2::framework
3334
{
3435

36+
class InvalidAODReadError : public std::runtime_error
37+
{
38+
public:
39+
using std::runtime_error::runtime_error;
40+
};
41+
3542
struct FileNameHolder {
3643
std::string fileName;
3744
int numberOfTimeFrames = 0;

Framework/AnalysisSupport/src/TTreePlugin.cxx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,9 @@ auto readBoolValues = [](uint8_t* target, ReadOps& op, TBufferFile& rootBuffer)
211211
while (readEntries < op.rootBranchEntries) {
212212
auto beginValue = readEntries;
213213
readLast = op.branch->GetBulkRead().GetBulkEntries(readEntries, rootBuffer);
214+
if (readLast < 0) {
215+
throw runtime_error_f("Error while reading branch %s starting from %d.", op.branch->GetName(), readEntries);
216+
}
214217
int size = readLast * op.listSize;
215218
readEntries += readLast;
216219
for (int i = beginValue; i < beginValue + size; ++i) {
@@ -228,7 +231,18 @@ auto readVLAValues = [](uint8_t* target, ReadOps& op, ReadOps const& offsetOp, T
228231
rootBuffer.Reset();
229232
while (readEntries < op.rootBranchEntries) {
230233
auto readLast = op.branch->GetBulkRead().GetEntriesSerialized(readEntries, rootBuffer);
234+
if (readLast < 0) {
235+
throw runtime_error_f("Error while reading branch %s starting from %d.", op.branch->GetName(), readEntries);
236+
}
237+
if (readEntries + readLast > op.rootBranchEntries) {
238+
throw runtime_error_f("Invalid read range for branch %s: starting from %d, read %d entries, total entries %lld.",
239+
op.branch->GetName(), readEntries, readLast, static_cast<long long>(op.rootBranchEntries));
240+
}
231241
int size = offsets[readEntries + readLast] - offsets[readEntries];
242+
if (size < 0) {
243+
throw runtime_error_f("Invalid offset range for branch %s: offsets[%d]=%d, offsets[%d]=%d.",
244+
op.branch->GetName(), readEntries, offsets[readEntries], readEntries + readLast, offsets[readEntries + readLast]);
245+
}
232246
readEntries += readLast;
233247
bigEndianCopy(target, rootBuffer.GetCurrent(), size, op.typeSize);
234248
target += (ptrdiff_t)(size * op.typeSize);

Framework/Core/include/Framework/MessageContext.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,11 @@ class MessageContext
466466
/// discarded.
467467
void clear();
468468

469+
/// Discard pending output messages without asserting that they were sent. This
470+
/// is intended for exception teardown paths where normal post-processing will
471+
/// not run.
472+
void discard();
473+
469474
FairMQDeviceProxy& proxy()
470475
{
471476
return mProxy;

Framework/Core/src/MessageContext.cxx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,13 @@ void MessageContext::clear()
107107
mMessages.clear();
108108
}
109109

110+
void MessageContext::discard()
111+
{
112+
mDidDispatch = false;
113+
mScheduledMessages.clear();
114+
mMessages.clear();
115+
}
116+
110117
int64_t MessageContext::addToCache(std::unique_ptr<fair::mq::Message>& toCache)
111118
{
112119
auto&& cached = toCache->GetTransport()->CreateMessage();

0 commit comments

Comments
 (0)