From 740b329b55a4d426996f656cf384d2b0004180ae Mon Sep 17 00:00:00 2001 From: Autumn McKee Date: Wed, 14 Oct 2026 17:12:48 +0200 Subject: [PATCH] Detect failed reads when a tree is missing from a DF --- .../AnalysisSupport/src/DataInputDirector.cxx | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/Framework/AnalysisSupport/src/DataInputDirector.cxx b/Framework/AnalysisSupport/src/DataInputDirector.cxx index 3c53f8fc0c632..3988de2ba6e99 100644 --- a/Framework/AnalysisSupport/src/DataInputDirector.cxx +++ b/Framework/AnalysisSupport/src/DataInputDirector.cxx @@ -30,12 +30,14 @@ #include "TObjString.h" #include "TMap.h" #include "TFile.h" +#include "TError.h" #include #include #include #include #include +#include #if __has_include() #include @@ -58,6 +60,42 @@ FileNameHolder makeFileNameHolder(std::string fileName) return holder; } +namespace +{ +// ROOT reports a failed key read (a folder's key list or the object itself) and carries on, +// so the lookup returns nullptr, or an object built from whatever was in the buffer, and a +// failed read looks like a missing object. Count the reports while the watch is alive. +struct RootReadErrorWatch { + RootReadErrorWatch() + : mPrevious(SetErrorHandler(handler)) + { + sPrevious = mPrevious; + sErrors = 0; + } + ~RootReadErrorWatch() + { + SetErrorHandler(mPrevious); + } + RootReadErrorWatch(RootReadErrorWatch const&) = delete; + RootReadErrorWatch& operator=(RootReadErrorWatch const&) = delete; + + [[nodiscard]] bool failed() const { return sErrors > 0; } + + static void handler(int level, Bool_t abort, char const* location, char const* msg) + { + if (level >= kError && location && std::string_view{location} == "TKey::ReadFile") { + ++sErrors; + } + (sPrevious ? sPrevious : DefaultErrorHandler)(level, abort, location, msg); + } + + ErrorHandlerFunc_t mPrevious; + // Errors from other threads are passed on but not counted. + static inline thread_local int sErrors = 0; + static inline ErrorHandlerFunc_t sPrevious = nullptr; +}; +} // namespace + DataInputDescriptor::DataInputDescriptor(bool alienSupport, int level, DataInputDirectorContext& context) : mAlienSupport(alienSupport), mContext(context), @@ -605,7 +643,19 @@ try { for (auto& capability : mFactory.capabilities) { auto objectPath = capability.lfn2objectPath(fullpath.path()); - void* handle = capability.getHandle(rootFS, objectPath); + void* handle = nullptr; + bool readFailed = false; + try { + RootReadErrorWatch watch; + handle = capability.getHandle(rootFS, objectPath); + readFailed = watch.failed(); + } catch (...) { + // Sizes from a partial read can be garbage, e.g. giving std::bad_alloc. + std::throw_with_nested(InvalidAODReadError(fmt::format(R"(Unable to look up "{}" in "{}")", objectPath, rootFS->GetFile()->GetName()))); + } + if (readFailed) { + throw InvalidAODReadError(fmt::format(R"(Unable to read "{}" from "{}")", objectPath, rootFS->GetFile()->GetName())); + } if (handle) { format = capability.factory().format(); creator = capability.factory().deferredOutputStreamer;