Skip to content

Commit 39c7515

Browse files
committed
Skip invalid timeframes during ROOT input
Handle corrupt reads as recoverable and discard the affected timeframe when DPL_AOD_READER_SKIP_INVALID is enabled.
1 parent be5a2bb commit 39c7515

5 files changed

Lines changed: 110 additions & 31 deletions

File tree

Framework/AnalysisSupport/src/AODJAlienReaderHelpers.cxx

Lines changed: 55 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
#include "AODJAlienReaderHelpers.h"
1313
#include <charconv>
14+
#include <cstdlib>
1415
#include <memory>
1516
#include <ranges>
1617
#include <vector>
@@ -19,13 +20,16 @@
1920
#include "Framework/DataProcessingStats.h"
2021
#include "Framework/RootArrowFilesystem.h"
2122
#include "Framework/AlgorithmSpec.h"
23+
#include "Framework/ArrowContext.h"
2224
#include "Framework/ConfigParamRegistry.h"
2325
#include "Framework/ControlService.h"
2426
#include "Framework/CallbackService.h"
2527
#include "Framework/EndOfStreamContext.h"
2628
#include "Framework/DeviceSpec.h"
2729
#include "Framework/RawDeviceService.h"
2830
#include "Framework/DataSpecUtils.h"
31+
#include "Framework/MessageContext.h"
32+
#include "Framework/StringContext.h"
2933
#include "Framework/ConfigContext.h"
3034
#include "DataInputDirector.h"
3135
#include "Framework/SourceInfoHeader.h"
@@ -200,7 +204,7 @@ AlgorithmSpec AODJAlienReaderHelpers::rootFileReaderCallback(ConfigContext const
200204
numTF,
201205
watchdog,
202206
maxRate,
203-
didir, reportTFN, reportTFFileName, level](Monitoring& monitoring, DataAllocator& outputs, ControlService& control, DeviceSpec const& device, DataProcessingStats& dpstats) {
207+
didir, reportTFN, reportTFFileName, level](Monitoring& monitoring, DataAllocator& outputs, ControlService& control, DeviceSpec const& device, DataProcessingStats& dpstats, ArrowContext& arrowContext, MessageContext& messageContext, StringContext& stringContext) {
204208
// Each parallel reader device.inputTimesliceId reads the files fileCounter*device.maxInputTimeslices+device.inputTimesliceId
205209
// the TF to read is numTF
206210
assert(device.inputTimesliceId < device.maxInputTimeslices);
@@ -218,6 +222,8 @@ AlgorithmSpec AODJAlienReaderHelpers::rootFileReaderCallback(ConfigContext const
218222
static size_t totalSizeUncompressed = 0;
219223
static size_t totalSizeCompressed = 0;
220224
static uint64_t totalDFSent = 0;
225+
static uint64_t totalInvalidReadSkipped = 0;
226+
static bool skipInvalidReads = getenv("DPL_AOD_READER_SKIP_INVALID") && atoi(getenv("DPL_AOD_READER_SKIP_INVALID"));
221227

222228
// check if RuntimeLimit is reached
223229
if (!watchdog->update()) {
@@ -232,6 +238,17 @@ AlgorithmSpec AODJAlienReaderHelpers::rootFileReaderCallback(ConfigContext const
232238

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

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+
bool treeRead = false;
263+
try {
264+
treeRead = didir->readTree(outputs, dh, fcnt, ntf, totalSizeCompressed, totalSizeUncompressed, wasAOD);
265+
} catch (InvalidAODReadError const& e) {
266+
if (!skipInvalidReads) {
267+
throw;
268+
}
269+
skipInvalidRead(concrete.origin, e);
270+
return;
271+
}
272+
273+
if (!treeRead) {
274+
if (!first) {
275+
LOGP(fatal, "Can not retrieve tree for table {}: fileCounter {}, timeFrame {}", concrete.origin.as<std::string>(), fcnt, ntf);
276+
throw std::runtime_error("Processing is stopped!");
277+
}
278+
// check if there is a next file to read
279+
fcnt += device.maxInputTimeslices;
280+
if (didir->atEnd(fcnt)) {
281+
LOGP(info, "No input files left to read for reader {}!", device.inputTimesliceId);
282+
didir->closeInputFiles();
283+
monitoring.flushBuffer();
284+
control.endOfStream();
285+
control.readyToQuit(QuitRequest::Me);
286+
return;
287+
}
288+
// get first folder of next file
289+
ntf = 0;
290+
try {
291+
treeRead = didir->readTree(outputs, dh, fcnt, ntf, totalSizeCompressed, totalSizeUncompressed, wasAOD);
292+
} catch (InvalidAODReadError const& e) {
293+
if (!skipInvalidReads) {
294+
throw;
262295
}
263-
} else {
296+
skipInvalidRead(concrete.origin, e);
297+
return;
298+
}
299+
if (!treeRead) {
264300
LOGP(fatal, "Can not retrieve tree for table {}: fileCounter {}, timeFrame {}", concrete.origin.as<std::string>(), fcnt, ntf);
265301
throw std::runtime_error("Processing is stopped!");
266302
}

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/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)