1010// or submit itself to any jurisdiction.
1111
1212#include " AODJAlienReaderHelpers.h"
13+ #include < algorithm>
1314#include < charconv>
15+ #include < cctype>
16+ #include < cstdlib>
17+ #include < exception>
1418#include < memory>
1519#include < ranges>
20+ #include < string>
21+ #include < string_view>
1622#include < vector>
1723#include " Framework/TableTreeHelpers.h"
1824#include " Framework/AnalysisHelpers.h"
1925#include " Framework/DataProcessingStats.h"
2026#include " Framework/RootArrowFilesystem.h"
2127#include " Framework/AlgorithmSpec.h"
28+ #include " Framework/ArrowContext.h"
2229#include " Framework/ConfigParamRegistry.h"
2330#include " Framework/ControlService.h"
2431#include " Framework/CallbackService.h"
2532#include " Framework/EndOfStreamContext.h"
2633#include " Framework/DeviceSpec.h"
2734#include " Framework/RawDeviceService.h"
2835#include " Framework/DataSpecUtils.h"
36+ #include " Framework/MessageContext.h"
37+ #include " Framework/StringContext.h"
2938#include " Framework/ConfigContext.h"
3039#include " DataInputDirector.h"
3140#include " Framework/SourceInfoHeader.h"
@@ -101,6 +110,31 @@ using o2::monitoring::tags::Value;
101110
102111namespace o2 ::framework::readers
103112{
113+ static bool shouldSkipInvalidReads ()
114+ {
115+ auto const * envValue = getenv (" DPL_AOD_READER_SKIP_INVALID" );
116+ if (envValue == nullptr ) {
117+ return false ;
118+ }
119+
120+ std::string value{envValue};
121+ std::ranges::transform (value, value.begin (), [](unsigned char c) { return std::tolower (c); });
122+ return !value.empty () && value != " 0" && value != " false" ;
123+ }
124+
125+ static std::string describeException (std::exception const & exception)
126+ {
127+ std::string description{exception.what ()};
128+ try {
129+ std::rethrow_if_nested (exception);
130+ } catch (std::exception const & nested) {
131+ description += " : " + describeException (nested);
132+ } catch (...) {
133+ description += " : unknown exception" ;
134+ }
135+ return description;
136+ }
137+
104138AlgorithmSpec AODJAlienReaderHelpers::rootFileReaderCallback (ConfigContext const & ctx)
105139{
106140 // aod-parent-base-path-replacement is now a workflow option, so it needs to be
@@ -193,14 +227,16 @@ AlgorithmSpec AODJAlienReaderHelpers::rootFileReaderCallback(ConfigContext const
193227 int level = originLevelMapping.empty () ? -1 : 0 ;
194228 auto fileCounter = std::make_shared<int >(0 );
195229 auto numTF = std::make_shared<int >(-1 );
230+ bool const skipInvalidReads = shouldSkipInvalidReads ();
196231 return adaptStateless ([TFNumberHeader,
197232 TFFileNameHeader,
198233 requestedTables,
199234 fileCounter,
200235 numTF,
201236 watchdog,
202237 maxRate,
203- didir, reportTFN, reportTFFileName, level](Monitoring& monitoring, DataAllocator& outputs, ControlService& control, DeviceSpec const & device, DataProcessingStats& dpstats) {
238+ skipInvalidReads,
239+ didir, reportTFN, reportTFFileName, level](Monitoring& monitoring, DataAllocator& outputs, ControlService& control, DeviceSpec const & device, DataProcessingStats& dpstats, ArrowContext& arrowContext, MessageContext& messageContext, StringContext& stringContext) {
204240 // Each parallel reader device.inputTimesliceId reads the files fileCounter*device.maxInputTimeslices+device.inputTimesliceId
205241 // the TF to read is numTF
206242 assert (device.inputTimesliceId < device.maxInputTimeslices );
@@ -214,10 +250,10 @@ AlgorithmSpec AODJAlienReaderHelpers::rootFileReaderCallback(ConfigContext const
214250 }
215251
216252 // loop over requested tables
217- bool first = true ;
218253 static size_t totalSizeUncompressed = 0 ;
219254 static size_t totalSizeCompressed = 0 ;
220255 static uint64_t totalDFSent = 0 ;
256+ static uint64_t totalInvalidReadSkipped = 0 ;
221257
222258 // check if RuntimeLimit is reached
223259 if (!watchdog->update ()) {
@@ -232,19 +268,98 @@ AlgorithmSpec AODJAlienReaderHelpers::rootFileReaderCallback(ConfigContext const
232268
233269 int64_t startTime = uv_hrtime ();
234270 int64_t startSize = totalSizeCompressed;
235- for (auto & route : requestedTables) {
236- if ((device.inputTimesliceId % route.maxTimeslices ) != route.timeslice ) {
237- continue ;
271+ auto skipInvalidRead = [&](o2::header::DataOrigin const & origin, InvalidAODReadError const & e) {
272+ auto skippedTimeframes = ++totalInvalidReadSkipped;
273+ LOGP (error, " Invalid AOD read for table {}: fileCounter {}, timeFrame {}. Skipping timeframe (skipped timeframes: {}). Reason: {}" ,
274+ origin.as <std::string>(), fcnt, ntf, skippedTimeframes, describeException (e));
275+ arrowContext.clear ();
276+ messageContext.discard ();
277+ stringContext.clear ();
278+ dpstats.updateStats ({static_cast <short >(ProcessingStatsId::AOD_INVALID_READ_SKIPPED_TIMEFRAMES ), DataProcessingStats::Op::Add, 1 });
279+ *fileCounter = (fcnt - device.inputTimesliceId ) / device.maxInputTimeslices ;
280+ *numTF = ntf;
281+ };
282+ enum class TFReaderState {
283+ READ_FIRST_TABLE ,
284+ READ_FIRST_TABLE_FROM_NEXT_FILE ,
285+ READ_NEXT_TABLE ,
286+ TRY_NEXT_FILE ,
287+ TIMEFRAME_READ ,
288+ INVALID_TIMEFRAME ,
289+ };
290+ auto readState = TFReaderState::READ_FIRST_TABLE ;
291+ size_t routeIndex = 0 ;
292+ auto reportTimeframe = [&didir, &fcnt, &ntf, &outputs, &TFNumberHeader, &TFFileNameHeader, reportTFN, reportTFFileName](header::DataHeader const & dh) {
293+ if (reportTFN) {
294+ // TF number
295+ auto timeFrameNumber = didir->getTimeFrameNumber (dh, fcnt, ntf);
296+ auto o = Output (TFNumberHeader);
297+ outputs.make <uint64_t >(o) = timeFrameNumber;
298+ }
299+
300+ if (reportTFFileName) {
301+ // Origin file name for derived output map
302+ auto o2 = Output (TFFileNameHeader);
303+ auto fileAndFolder = didir->getFileFolder (dh, fcnt, ntf);
304+ auto rootFS = std::dynamic_pointer_cast<TFileFileSystem>(fileAndFolder.filesystem ());
305+ auto * f = dynamic_cast <TFile*>(rootFS->GetFile ());
306+ std::string currentFilename (f->GetFile ()->GetName ());
307+ if (strcmp (f->GetEndpointUrl ()->GetProtocol (), " file" ) == 0 && f->GetEndpointUrl ()->GetFile ()[0 ] != ' /' ) {
308+ // This is not an absolute local path. Make it absolute.
309+ static std::string pwd = gSystem ->pwd () + std::string (" /" );
310+ currentFilename = pwd + std::string (f->GetName ());
311+ }
312+ outputs.make <std::string>(o2) = currentFilename;
313+ }
314+ };
315+ auto tryReadTable = [&device, &didir, &fcnt, &ntf, &outputs, &reportTimeframe, &requestedTables, &routeIndex, &skipInvalidRead, skipInvalidReads](TFReaderState currentState) -> TFReaderState {
316+ while (routeIndex < requestedTables.size () &&
317+ (device.inputTimesliceId % requestedTables[routeIndex].maxTimeslices ) != requestedTables[routeIndex].timeslice ) {
318+ ++routeIndex;
319+ }
320+ if (routeIndex == requestedTables.size ()) {
321+ return TFReaderState::TIMEFRAME_READ ;
238322 }
239323
240- // create header
324+ auto & route = requestedTables[routeIndex];
241325 auto concrete = DataSpecUtils::asConcreteDataMatcher (route.matcher );
242326 auto dh = header::DataHeader (concrete.description , concrete.origin , concrete.subSpec );
243327 bool wasAOD = std::ranges::any_of (route.matcher .metadata , [](ConfigParamSpec const & p) { return p.name .starts_with (" aod-origin-replaced" ); });
244328
245- if (!didir->readTree (outputs, dh, fcnt, ntf, totalSizeCompressed, totalSizeUncompressed, wasAOD)) {
246- if (first) {
247- // check if there is a next file to read
329+ try {
330+ if (!didir->readTree (outputs, dh, fcnt, ntf, totalSizeCompressed, totalSizeUncompressed, wasAOD)) {
331+ return TFReaderState::TRY_NEXT_FILE ;
332+ }
333+ } catch (InvalidAODReadError const & e) {
334+ if (!skipInvalidReads) {
335+ throw ;
336+ }
337+ skipInvalidRead (concrete.origin , e);
338+ return TFReaderState::INVALID_TIMEFRAME ;
339+ }
340+
341+ if (currentState == TFReaderState::READ_FIRST_TABLE || currentState == TFReaderState::READ_FIRST_TABLE_FROM_NEXT_FILE ) {
342+ reportTimeframe (dh);
343+ }
344+ ++routeIndex;
345+ return TFReaderState::READ_NEXT_TABLE ;
346+ };
347+ while (readState != TFReaderState::TIMEFRAME_READ ) {
348+ switch (readState) {
349+ case TFReaderState::READ_FIRST_TABLE :
350+ readState = tryReadTable (readState);
351+ break ;
352+ case TFReaderState::READ_FIRST_TABLE_FROM_NEXT_FILE :
353+ case TFReaderState::READ_NEXT_TABLE :
354+ readState = tryReadTable (readState);
355+ if (readState == TFReaderState::TRY_NEXT_FILE ) {
356+ // Once a file has been selected, every requested table must exist.
357+ auto concrete = DataSpecUtils::asConcreteDataMatcher (requestedTables[routeIndex].matcher );
358+ LOGP (fatal, " Can not retrieve tree for table {}: fileCounter {}, timeFrame {}" , concrete.origin .as <std::string>(), fcnt, ntf);
359+ throw std::runtime_error (" Processing is stopped!" );
360+ }
361+ break ;
362+ case TFReaderState::TRY_NEXT_FILE :
248363 fcnt += device.maxInputTimeslices ;
249364 if (didir->atEnd (fcnt)) {
250365 LOGP (info, " No input files left to read for reader {}!" , device.inputTimesliceId );
@@ -254,42 +369,15 @@ AlgorithmSpec AODJAlienReaderHelpers::rootFileReaderCallback(ConfigContext const
254369 control.readyToQuit (QuitRequest::Me);
255370 return ;
256371 }
257- // get first folder of next file
258372 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 {
264- LOGP (fatal, " Can not retrieve tree for table {}: fileCounter {}, timeFrame {}" , concrete.origin .as <std::string>(), fcnt, ntf);
265- throw std::runtime_error (" Processing is stopped!" );
266- }
267- }
268-
269- if (first) {
270- if (reportTFN) {
271- // TF number
272- auto timeFrameNumber = didir->getTimeFrameNumber (dh, fcnt, ntf);
273- auto o = Output (TFNumberHeader);
274- outputs.make <uint64_t >(o) = timeFrameNumber;
275- }
276-
277- if (reportTFFileName) {
278- // Origin file name for derived output map
279- auto o2 = Output (TFFileNameHeader);
280- auto fileAndFolder = didir->getFileFolder (dh, fcnt, ntf);
281- auto rootFS = std::dynamic_pointer_cast<TFileFileSystem>(fileAndFolder.filesystem ());
282- auto * f = dynamic_cast <TFile*>(rootFS->GetFile ());
283- std::string currentFilename (f->GetFile ()->GetName ());
284- if (strcmp (f->GetEndpointUrl ()->GetProtocol (), " file" ) == 0 && f->GetEndpointUrl ()->GetFile ()[0 ] != ' /' ) {
285- // This is not an absolute local path. Make it absolute.
286- static std::string pwd = gSystem ->pwd () + std::string (" /" );
287- currentFilename = pwd + std::string (f->GetName ());
288- }
289- outputs.make <std::string>(o2) = currentFilename;
290- }
373+ routeIndex = 0 ;
374+ readState = TFReaderState::READ_FIRST_TABLE_FROM_NEXT_FILE ;
375+ break ;
376+ case TFReaderState::INVALID_TIMEFRAME :
377+ return ;
378+ case TFReaderState::TIMEFRAME_READ :
379+ break ;
291380 }
292- first = false ;
293381 }
294382 int64_t stopSize = totalSizeCompressed;
295383 int64_t bytesDelta = stopSize - startSize;
0 commit comments