Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-11-01 10:27:00

0001 /// \file ROOT/RPageStorage.hxx
0002 /// \ingroup NTuple
0003 /// \author Jakob Blomer <jblomer@cern.ch>
0004 /// \date 2018-07-19
0005 
0006 /*************************************************************************
0007  * Copyright (C) 1995-2019, Rene Brun and Fons Rademakers.               *
0008  * All rights reserved.                                                  *
0009  *                                                                       *
0010  * For the licensing terms see $ROOTSYS/LICENSE.                         *
0011  * For the list of contributors see $ROOTSYS/README/CREDITS.             *
0012  *************************************************************************/
0013 
0014 #ifndef ROOT_RPageStorage
0015 #define ROOT_RPageStorage
0016 
0017 #include <ROOT/RError.hxx>
0018 #include <ROOT/RCluster.hxx>
0019 #include <ROOT/RClusterPool.hxx>
0020 #include <ROOT/RColumnElementBase.hxx>
0021 #include <ROOT/RNTupleDescriptor.hxx>
0022 #include <ROOT/RNTupleMetrics.hxx>
0023 #include <ROOT/RNTupleReadOptions.hxx>
0024 #include <ROOT/RNTupleSerialize.hxx>
0025 #include <ROOT/RNTupleWriteOptions.hxx>
0026 #include <ROOT/RNTupleTypes.hxx>
0027 #include <ROOT/RPage.hxx>
0028 #include <ROOT/RPagePool.hxx>
0029 #include <ROOT/RSpan.hxx>
0030 #include <string_view>
0031 
0032 #include <atomic>
0033 #include <cassert>
0034 #include <cstddef>
0035 #include <deque>
0036 #include <functional>
0037 #include <map>
0038 #include <memory>
0039 #include <mutex>
0040 #include <set>
0041 #include <shared_mutex>
0042 #include <unordered_map>
0043 #include <unordered_set>
0044 #include <vector>
0045 
0046 namespace ROOT {
0047 
0048 class RNTupleModel;
0049 
0050 namespace Internal {
0051 
0052 class RPageAllocator;
0053 class RColumn;
0054 struct RNTupleModelChangeset;
0055 
0056 enum class EPageStorageType {
0057    kSink,
0058    kSource,
0059 };
0060 
0061 // clang-format off
0062 /**
0063 \class ROOT::Internal::RPageStorage
0064 \ingroup NTuple
0065 \brief Common functionality of an ntuple storage for both reading and writing
0066 
0067 The RPageStore provides access to a storage container that keeps the bits of pages and clusters comprising
0068 an ntuple.  Concrete implementations can use a TFile, a raw file, an object store, and so on.
0069 */
0070 // clang-format on
0071 class RPageStorage {
0072 public:
0073    /// The page checksum is a 64bit xxhash3
0074    static constexpr std::size_t kNBytesPageChecksum = sizeof(std::uint64_t);
0075 
0076    /// The interface of a task scheduler to schedule page (de)compression tasks
0077    class RTaskScheduler {
0078    public:
0079       virtual ~RTaskScheduler() = default;
0080       /// Take a callable that represents a task
0081       virtual void AddTask(const std::function<void(void)> &taskFunc) = 0;
0082       /// Blocks until all scheduled tasks finished
0083       virtual void Wait() = 0;
0084    };
0085 
0086    /// A sealed page contains the bytes of a page as written to storage (packed & compressed).  It is used
0087    /// as an input to UnsealPages() as well as to transfer pages between different storage media.
0088    /// RSealedPage does _not_ own the buffer it is pointing to in order to not interfere with the memory management
0089    /// of concrete page sink and page source implementations.
0090    struct RSealedPage {
0091    private:
0092       const void *fBuffer = nullptr;
0093       std::size_t fBufferSize = 0; ///< Size of the page payload and the trailing checksum (if available)
0094       std::uint32_t fNElements = 0;
0095       bool fHasChecksum = false; ///< If set, the last 8 bytes of the buffer are the xxhash of the rest of the buffer
0096 
0097    public:
0098       RSealedPage() = default;
0099       RSealedPage(const void *buffer, std::size_t bufferSize, std::uint32_t nElements, bool hasChecksum = false)
0100          : fBuffer(buffer), fBufferSize(bufferSize), fNElements(nElements), fHasChecksum(hasChecksum)
0101       {
0102       }
0103       RSealedPage(const RSealedPage &other) = default;
0104       RSealedPage &operator=(const RSealedPage &other) = default;
0105       RSealedPage(RSealedPage &&other) = default;
0106       RSealedPage &operator=(RSealedPage &&other) = default;
0107 
0108       const void *GetBuffer() const { return fBuffer; }
0109       void SetBuffer(const void *buffer) { fBuffer = buffer; }
0110 
0111       std::size_t GetDataSize() const
0112       {
0113          assert(fBufferSize >= fHasChecksum * kNBytesPageChecksum);
0114          return fBufferSize - fHasChecksum * kNBytesPageChecksum;
0115       }
0116       std::size_t GetBufferSize() const { return fBufferSize; }
0117       void SetBufferSize(std::size_t bufferSize) { fBufferSize = bufferSize; }
0118 
0119       std::uint32_t GetNElements() const { return fNElements; }
0120       void SetNElements(std::uint32_t nElements) { fNElements = nElements; }
0121 
0122       bool GetHasChecksum() const { return fHasChecksum; }
0123       void SetHasChecksum(bool hasChecksum) { fHasChecksum = hasChecksum; }
0124 
0125       void ChecksumIfEnabled();
0126       RResult<void> VerifyChecksumIfEnabled() const;
0127       /// Returns a failure if the sealed page has no checksum
0128       RResult<std::uint64_t> GetChecksum() const;
0129    };
0130 
0131    using SealedPageSequence_t = std::deque<RSealedPage>;
0132    /// A range of sealed pages referring to the same column that can be used for vector commit
0133    struct RSealedPageGroup {
0134       ROOT::DescriptorId_t fPhysicalColumnId;
0135       SealedPageSequence_t::const_iterator fFirst;
0136       SealedPageSequence_t::const_iterator fLast;
0137 
0138       RSealedPageGroup() = default;
0139       RSealedPageGroup(ROOT::DescriptorId_t d, SealedPageSequence_t::const_iterator b,
0140                        SealedPageSequence_t::const_iterator e)
0141          : fPhysicalColumnId(d), fFirst(b), fLast(e)
0142       {
0143       }
0144    };
0145 
0146 protected:
0147    ROOT::Experimental::Detail::RNTupleMetrics fMetrics;
0148 
0149    /// For the time being, we will use the heap allocator for all sources and sinks. This may change in the future.
0150    std::unique_ptr<ROOT::Internal::RPageAllocator> fPageAllocator;
0151 
0152    std::string fNTupleName;
0153    RTaskScheduler *fTaskScheduler = nullptr;
0154    void WaitForAllTasks()
0155    {
0156       if (!fTaskScheduler)
0157          return;
0158       fTaskScheduler->Wait();
0159    }
0160 
0161 public:
0162    explicit RPageStorage(std::string_view name);
0163    RPageStorage(const RPageStorage &other) = delete;
0164    RPageStorage &operator=(const RPageStorage &other) = delete;
0165    RPageStorage(RPageStorage &&other) = default;
0166    RPageStorage &operator=(RPageStorage &&other) = default;
0167    virtual ~RPageStorage();
0168 
0169    /// Whether the concrete implementation is a sink or a source
0170    virtual EPageStorageType GetType() = 0;
0171 
0172    struct RColumnHandle {
0173       ROOT::DescriptorId_t fPhysicalId = ROOT::kInvalidDescriptorId;
0174       ROOT::Internal::RColumn *fColumn = nullptr;
0175 
0176       /// Returns true for a valid column handle; fColumn and fPhysicalId should always either both
0177       /// be valid or both be invalid.
0178       explicit operator bool() const { return fPhysicalId != ROOT::kInvalidDescriptorId && fColumn; }
0179    };
0180    /// The column handle identifies a column with the current open page storage
0181    using ColumnHandle_t = RColumnHandle;
0182 
0183    /// Register a new column.  When reading, the column must exist in the ntuple on disk corresponding to the metadata.
0184    /// When writing, every column can only be attached once.
0185    virtual ColumnHandle_t AddColumn(ROOT::DescriptorId_t fieldId, ROOT::Internal::RColumn &column) = 0;
0186    /// Unregisters a column.  A page source decreases the reference counter for the corresponding active column.
0187    /// For a page sink, dropping columns is currently a no-op.
0188    virtual void DropColumn(ColumnHandle_t columnHandle) = 0;
0189    ROOT::DescriptorId_t GetColumnId(ColumnHandle_t columnHandle) const { return columnHandle.fPhysicalId; }
0190 
0191    /// Returns the default metrics object.  Subclasses might alternatively provide their own metrics object by
0192    /// overriding this.
0193    virtual ROOT::Experimental::Detail::RNTupleMetrics &GetMetrics() { return fMetrics; }
0194 
0195    /// Returns the NTuple name.
0196    const std::string &GetNTupleName() const { return fNTupleName; }
0197 
0198    void SetTaskScheduler(RTaskScheduler *taskScheduler) { fTaskScheduler = taskScheduler; }
0199 }; // class RPageStorage
0200 
0201 // clang-format off
0202 /**
0203 \class ROOT::Internal::RWritePageMemoryManager
0204 \ingroup NTuple
0205 \brief Helper to maintain a memory budget for the write pages of a set of columns
0206 
0207 The memory manager keeps track of the sum of bytes used by the write pages of a set of columns.
0208 It will flush (and shrink) large pages of other columns on the attempt to expand a page.
0209 */
0210 // clang-format on
0211 class RWritePageMemoryManager {
0212 private:
0213    struct RColumnInfo {
0214       ROOT::Internal::RColumn *fColumn = nullptr;
0215       std::size_t fCurrentPageSize = 0;
0216       std::size_t fInitialPageSize = 0;
0217 
0218       bool operator>(const RColumnInfo &other) const;
0219    };
0220 
0221    /// Sum of all the write page sizes (their capacity) of the columns in `fColumnsSortedByPageSize`
0222    std::size_t fCurrentAllocatedBytes = 0;
0223    /// Maximum allowed value for `fCurrentAllocatedBytes`, set from RNTupleWriteOptions::fPageBufferBudget
0224    std::size_t fMaxAllocatedBytes = 0;
0225    /// All columns that called `ReservePage()` (hence `TryUpdate()`) at least once,
0226    /// sorted by their current write page size from large to small
0227    std::set<RColumnInfo, std::greater<RColumnInfo>> fColumnsSortedByPageSize;
0228 
0229    /// Flush columns in order of allocated write page size until the sum of all write page allocations
0230    /// leaves space for at least targetAvailableSize bytes. Only use columns with a write page size larger
0231    /// than pageSizeLimit.
0232    bool TryEvict(std::size_t targetAvailableSize, std::size_t pageSizeLimit);
0233 
0234 public:
0235    explicit RWritePageMemoryManager(std::size_t maxAllocatedBytes) : fMaxAllocatedBytes(maxAllocatedBytes) {}
0236 
0237    /// Try to register the new write page size for the given column. Flush large columns to make space, if necessary.
0238    /// If not enough space is available after all (sum of write pages would be larger than fMaxAllocatedBytes),
0239    /// return false.
0240    bool TryUpdate(ROOT::Internal::RColumn &column, std::size_t newWritePageSize);
0241 };
0242 
0243 // clang-format off
0244 /**
0245 \class ROOT::Internal::RPageSink
0246 \ingroup NTuple
0247 \brief Abstract interface to write data into an ntuple
0248 
0249 The page sink takes the list of columns and afterwards a series of page commits and cluster commits.
0250 The user is responsible to commit clusters at a consistent point, i.e. when all pages corresponding to data
0251 up to the given entry number are committed.
0252 
0253 An object of this class may either be a wrapper (for example a RPageSinkBuf) or a "persistent" sink,
0254 inheriting from RPagePersistentSink.
0255 */
0256 // clang-format on
0257 class RPageSink : public RPageStorage {
0258 public:
0259    using Callback_t = std::function<void(RPageSink &)>;
0260 
0261    /// Cluster that was staged, but not yet logically appended to the RNTuple
0262    struct RStagedCluster {
0263       std::uint64_t fNBytesWritten = 0;
0264       ROOT::NTupleSize_t fNEntries = 0;
0265 
0266       struct RColumnInfo {
0267          ROOT::RClusterDescriptor::RPageRange fPageRange;
0268          ROOT::NTupleSize_t fNElements = ROOT::kInvalidNTupleIndex;
0269          std::uint32_t fCompressionSettings;
0270          bool fIsSuppressed = false;
0271       };
0272 
0273       std::vector<RColumnInfo> fColumnInfos;
0274    };
0275 
0276 protected:
0277    std::unique_ptr<ROOT::RNTupleWriteOptions> fOptions;
0278 
0279    /// Flag if sink was initialized
0280    bool fIsInitialized = false;
0281 
0282    /// Helper for streaming a page. This is commonly used in derived, concrete page sinks. Note that if
0283    /// compressionSetting is 0 (uncompressed) and the page is mappable and not checksummed, the returned sealed page
0284    /// will point directly to the input page buffer. Otherwise, the sealed page references fSealPageBuffer.  Thus,
0285    /// the buffer pointed to by the RSealedPage should never be freed.
0286    RSealedPage SealPage(const ROOT::Internal::RPage &page, const ROOT::Internal::RColumnElementBase &element);
0287 
0288 private:
0289    std::vector<Callback_t> fOnDatasetCommitCallbacks;
0290    std::vector<unsigned char> fSealPageBuffer; ///< Used as destination buffer in the simple SealPage overload
0291 
0292    /// Used in ReservePage to maintain the page buffer budget
0293    RWritePageMemoryManager fWritePageMemoryManager;
0294 
0295 public:
0296    RPageSink(std::string_view ntupleName, const ROOT::RNTupleWriteOptions &options);
0297 
0298    RPageSink(const RPageSink &) = delete;
0299    RPageSink &operator=(const RPageSink &) = delete;
0300    RPageSink(RPageSink &&) = default;
0301    RPageSink &operator=(RPageSink &&) = default;
0302    ~RPageSink() override;
0303 
0304    EPageStorageType GetType() final { return EPageStorageType::kSink; }
0305    /// Returns the sink's write options.
0306    const ROOT::RNTupleWriteOptions &GetWriteOptions() const { return *fOptions; }
0307 
0308    void DropColumn(ColumnHandle_t /*columnHandle*/) final {}
0309 
0310    bool IsInitialized() const { return fIsInitialized; }
0311 
0312    /// Return the RNTupleDescriptor being constructed.
0313    virtual const ROOT::RNTupleDescriptor &GetDescriptor() const = 0;
0314 
0315    virtual ROOT::NTupleSize_t GetNEntries() const = 0;
0316 
0317    /// Physically creates the storage container to hold the ntuple (e.g., a keys a TFile or an S3 bucket)
0318    /// Init() associates column handles to the columns referenced by the model
0319    void Init(RNTupleModel &model)
0320    {
0321       if (fIsInitialized) {
0322          throw RException(R__FAIL("already initialized"));
0323       }
0324       InitImpl(model);
0325       fIsInitialized = true;
0326    }
0327 
0328 protected:
0329    virtual void InitImpl(RNTupleModel &model) = 0;
0330    virtual RNTupleLink CommitDatasetImpl() = 0;
0331 
0332 public:
0333    /// Parameters for the SealPage() method
0334    struct RSealPageConfig {
0335       const ROOT::Internal::RPage *fPage = nullptr; ///< Input page to be sealed
0336       const ROOT::Internal::RColumnElementBase *fElement =
0337          nullptr;                             ///< Corresponds to the page's elements, for size calculation etc.
0338       std::uint32_t fCompressionSettings = 0; ///< Compression algorithm and level to apply
0339       /// Adds a 8 byte little-endian xxhash3 checksum to the page payload. The buffer has to be large enough to
0340       /// to store the additional 8 bytes.
0341       bool fWriteChecksum = true;
0342       /// If false, the output buffer must not point to the input page buffer, which would otherwise be an option
0343       /// if the page is mappable and should not be compressed
0344       bool fAllowAlias = false;
0345       /// Location for sealed output. The memory buffer has to be large enough.
0346       void *fBuffer = nullptr;
0347    };
0348 
0349    /// Seal a page using the provided info.
0350    static RSealedPage SealPage(const RSealPageConfig &config);
0351 
0352    /// Incorporate incremental changes to the model into the ntuple descriptor. This happens, e.g. if new fields were
0353    /// added after the initial call to `RPageSink::Init(RNTupleModel &)`.
0354    /// `firstEntry` specifies the global index for the first stored element in the added columns.
0355    virtual void UpdateSchema(const ROOT::Internal::RNTupleModelChangeset &changeset, ROOT::NTupleSize_t firstEntry) = 0;
0356    /// Adds an extra type information record to schema. The extra type information will be written to the
0357    /// extension header. The information in the record will be merged with the existing information, e.g.
0358    /// duplicate streamer info records will be removed. This method is called by the "on commit dataset" callback
0359    /// registered by specific fields (e.g., streamer field) and during merging.
0360    virtual void UpdateExtraTypeInfo(const ROOT::RExtraTypeInfoDescriptor &extraTypeInfo) = 0;
0361 
0362    /// Commits a suppressed column for the current cluster. Can be called anytime before CommitCluster().
0363    /// For any given column and cluster, there must be no calls to both CommitSuppressedColumn() and page commits.
0364    virtual void CommitSuppressedColumn(ColumnHandle_t columnHandle) = 0;
0365    /// Write a page to the storage. The column must have been added before.
0366    virtual void CommitPage(ColumnHandle_t columnHandle, const ROOT::Internal::RPage &page) = 0;
0367    /// Write a preprocessed page to storage. The column must have been added before.
0368    virtual void
0369    CommitSealedPage(ROOT::DescriptorId_t physicalColumnId, const RPageStorage::RSealedPage &sealedPage) = 0;
0370    /// Write a vector of preprocessed pages to storage. The corresponding columns must have been added before.
0371    virtual void CommitSealedPageV(std::span<RPageStorage::RSealedPageGroup> ranges) = 0;
0372    /// Stage the current cluster and create a new one for the following data.
0373    /// Returns the object that must be passed to CommitStagedClusters to logically append the staged cluster to the
0374    /// ntuple descriptor.
0375    virtual RStagedCluster StageCluster(ROOT::NTupleSize_t nNewEntries) = 0;
0376    /// Commit staged clusters, logically appending them to the ntuple descriptor.
0377    virtual void CommitStagedClusters(std::span<RStagedCluster> clusters) = 0;
0378    /// Finalize the current cluster and create a new one for the following data.
0379    /// Returns the number of bytes written to storage (excluding metadata).
0380    virtual std::uint64_t CommitCluster(ROOT::NTupleSize_t nNewEntries)
0381    {
0382       RStagedCluster stagedClusters[] = {StageCluster(nNewEntries)};
0383       CommitStagedClusters(stagedClusters);
0384       return stagedClusters[0].fNBytesWritten;
0385    }
0386    /// Write out the page locations (page list envelope) for all the committed clusters since the last call of
0387    /// CommitClusterGroup (or the beginning of writing).
0388    virtual void CommitClusterGroup() = 0;
0389 
0390    /// Creates a new sink with the same underlying storage as this but writing to a different
0391    /// RNTuple named `name`. Only one of the two sinks can safely write at the same time.
0392    /// The RNTuple written by this cloned sink will be stored in a hidden key (this is a convenient assumption we
0393    /// make now since this method is only used to create attribute RNTuples).
0394    virtual std::unique_ptr<RPageSink> CloneAsHidden(std::string_view name, const RNTupleWriteOptions &opts) const = 0;
0395 
0396    /// Adds the given anchor information (name + locator) into the main RNTuple's descriptor as an attribute set
0397    /// linked to it with the given name.
0398    /// The attribute set must have already been written to storage via RNTupleAttrSetWriter::Commit().
0399    /// Note that, by RNTuple specs, this is only legal to call on a non-attribute RNTuple's sink.
0400    virtual void CommitAttributeSet(std::string_view attrSetName, const RNTupleLink &attrAnchorInfo) = 0;
0401 
0402    /// The registered callback is executed at the beginning of CommitDataset();
0403    void RegisterOnCommitDatasetCallback(Callback_t callback) { fOnDatasetCommitCallbacks.emplace_back(callback); }
0404    /// Run the registered callbacks and finalize the current cluster and the entrire data set.
0405    RNTupleLink CommitDataset();
0406 
0407    /// Get a new, empty page for the given column that can be filled with up to nElements;
0408    /// nElements must be larger than zero.
0409    virtual ROOT::Internal::RPage ReservePage(ColumnHandle_t columnHandle, std::size_t nElements);
0410 
0411    /// An RAII wrapper used to synchronize a page sink. See GetSinkGuard().
0412    class RSinkGuard {
0413       std::mutex *fLock;
0414 
0415    public:
0416       explicit RSinkGuard(std::mutex *lock) : fLock(lock)
0417       {
0418          if (fLock != nullptr) {
0419             fLock->lock();
0420          }
0421       }
0422       RSinkGuard(const RSinkGuard &) = delete;
0423       RSinkGuard &operator=(const RSinkGuard &) = delete;
0424       RSinkGuard(RSinkGuard &&) = delete;
0425       RSinkGuard &operator=(RSinkGuard &&) = delete;
0426       ~RSinkGuard()
0427       {
0428          if (fLock != nullptr) {
0429             fLock->unlock();
0430          }
0431       }
0432    };
0433 
0434    virtual RSinkGuard GetSinkGuard()
0435    {
0436       // By default, there is no lock and the guard does nothing.
0437       return RSinkGuard(nullptr);
0438    }
0439 }; // class RPageSink
0440 
0441 // clang-format off
0442 /**
0443 \class ROOT::Internal::RPagePersistentSink
0444 \ingroup NTuple
0445 \brief Base class for a sink with a physical storage backend
0446 */
0447 // clang-format on
0448 class RPagePersistentSink : public RPageSink {
0449 private:
0450    /// Used to map the IDs of the descriptor to the physical IDs issued during header/footer serialization
0451    ROOT::Internal::RNTupleSerializer::RContext fSerializationContext;
0452 
0453    /// Remembers the starting cluster id for the next cluster group
0454    std::uint64_t fNextClusterInGroup = 0;
0455    /// Used to calculate the number of entries in the current cluster
0456    ROOT::NTupleSize_t fPrevClusterNEntries = 0;
0457    /// Keeps track of the number of elements in the currently open cluster. Indexed by column id.
0458    std::vector<ROOT::RClusterDescriptor::RColumnRange> fOpenColumnRanges;
0459    /// Keeps track of the written pages in the currently open cluster. Indexed by column id.
0460    std::vector<ROOT::RClusterDescriptor::RPageRange> fOpenPageRanges;
0461 
0462    /// Union of the streamer info records that are sent from streamer fields to the sink before committing the dataset.
0463    ROOT::Internal::RNTupleSerializer::StreamerInfoMap_t fInfosOfStreamerFields;
0464 
0465 protected:
0466    /// Set of optional features supported by the persistent sink
0467    struct RFeatures {
0468       bool fCanMergePages = false;
0469    };
0470 
0471    RFeatures fFeatures;
0472    ROOT::Internal::RNTupleDescriptorBuilder fDescriptorBuilder;
0473 
0474    /// Default I/O performance counters that get registered in fMetrics
0475    struct RCounters {
0476       ROOT::Experimental::Detail::RNTupleAtomicCounter &fNPageCommitted;
0477       ROOT::Experimental::Detail::RNTupleAtomicCounter &fSzWritePayload;
0478       ROOT::Experimental::Detail::RNTupleAtomicCounter &fSzZip;
0479       ROOT::Experimental::Detail::RNTupleAtomicCounter &fTimeWallWrite;
0480       ROOT::Experimental::Detail::RNTupleAtomicCounter &fTimeWallZip;
0481       ROOT::Experimental::Detail::RNTupleTickCounter<ROOT::Experimental::Detail::RNTupleAtomicCounter> &fTimeCpuWrite;
0482       ROOT::Experimental::Detail::RNTupleTickCounter<ROOT::Experimental::Detail::RNTupleAtomicCounter> &fTimeCpuZip;
0483    };
0484    std::unique_ptr<RCounters> fCounters;
0485 
0486    virtual void InitImpl(unsigned char *serializedHeader, std::uint32_t length) = 0;
0487 
0488    virtual RNTupleLocator CommitPageImpl(ColumnHandle_t columnHandle, const ROOT::Internal::RPage &page) = 0;
0489    virtual RNTupleLocator
0490    CommitSealedPageImpl(ROOT::DescriptorId_t physicalColumnId, const RPageStorage::RSealedPage &sealedPage) = 0;
0491    /// Vector commit of preprocessed pages. The `ranges` array specifies a range of sealed pages to be
0492    /// committed for each column.  The returned vector contains, in order, the RNTupleLocator for each
0493    /// page on each range in `ranges`, i.e. the first N entries refer to the N pages in `ranges[0]`,
0494    /// followed by M entries that refer to the M pages in `ranges[1]`, etc.
0495    /// The mask allows to skip writing out certain pages. The vector has the size of all the pages.
0496    /// For every `false` value in the mask, the corresponding locator is skipped (missing) in the output vector.
0497    /// The default is to call `CommitSealedPageImpl` for each page; derived classes may provide an
0498    /// optimized implementation though.
0499    virtual std::vector<RNTupleLocator>
0500    CommitSealedPageVImpl(std::span<RPageStorage::RSealedPageGroup> ranges, const std::vector<bool> &mask);
0501    /// Returns the number of bytes written to storage (excluding metadata)
0502    virtual std::uint64_t StageClusterImpl() = 0;
0503    /// Returns the locator of the page list envelope of the given buffer that contains the serialized page list.
0504    /// Typically, the implementation takes care of compressing and writing the provided buffer.
0505    virtual RNTupleLocator CommitClusterGroupImpl(unsigned char *serializedPageList, std::uint32_t length) = 0;
0506    virtual RNTupleLink CommitDatasetImpl(unsigned char *serializedFooter, std::uint32_t length) = 0;
0507 
0508    /// Enables the default set of metrics provided by RPageSink. `prefix` will be used as the prefix for
0509    /// the counters registered in the internal RNTupleMetrics object.
0510    /// This set of counters can be extended by a subclass by calling `fMetrics.MakeCounter<...>()`.
0511    ///
0512    /// A subclass using the default set of metrics is always responsible for updating the counters
0513    /// appropriately, e.g. `fCounters->fNPageCommited.Inc()`
0514    void EnableDefaultMetrics(const std::string &prefix);
0515 
0516 public:
0517    RPagePersistentSink(std::string_view ntupleName, const ROOT::RNTupleWriteOptions &options);
0518 
0519    RPagePersistentSink(const RPagePersistentSink &) = delete;
0520    RPagePersistentSink &operator=(const RPagePersistentSink &) = delete;
0521    RPagePersistentSink(RPagePersistentSink &&) = default;
0522    RPagePersistentSink &operator=(RPagePersistentSink &&) = default;
0523    ~RPagePersistentSink() override;
0524 
0525    /// Guess the concrete derived page source from the location
0526    static std::unique_ptr<RPageSink> Create(std::string_view ntupleName, std::string_view location,
0527                                             const ROOT::RNTupleWriteOptions &options = ROOT::RNTupleWriteOptions());
0528 
0529    ColumnHandle_t AddColumn(ROOT::DescriptorId_t fieldId, ROOT::Internal::RColumn &column) final;
0530 
0531    const ROOT::RNTupleDescriptor &GetDescriptor() const final { return fDescriptorBuilder.GetDescriptor(); }
0532 
0533    ROOT::NTupleSize_t GetNEntries() const final { return fPrevClusterNEntries; }
0534 
0535    /// Updates the descriptor and calls InitImpl() that handles the backend-specific details (file, DAOS, etc.)
0536    void InitImpl(RNTupleModel &model) final;
0537    void UpdateSchema(const ROOT::Internal::RNTupleModelChangeset &changeset, ROOT::NTupleSize_t firstEntry) override;
0538    void UpdateExtraTypeInfo(const ROOT::RExtraTypeInfoDescriptor &extraTypeInfo) final;
0539 
0540    /// Initialize sink based on an existing descriptor and fill into the descriptor builder, optionally copying over
0541    /// the descriptor's clusters to this sink's descriptor.
0542    /// \return The model created from the new sink's descriptor. This model should be kept alive
0543    /// for at least as long as the sink.
0544    [[nodiscard]] std::unique_ptr<RNTupleModel>
0545    InitFromDescriptor(const ROOT::RNTupleDescriptor &descriptor, bool copyClusters);
0546 
0547    void CommitSuppressedColumn(ColumnHandle_t columnHandle) final;
0548    void CommitPage(ColumnHandle_t columnHandle, const ROOT::Internal::RPage &page) final;
0549    void CommitSealedPage(ROOT::DescriptorId_t physicalColumnId, const RPageStorage::RSealedPage &sealedPage) final;
0550    void CommitSealedPageV(std::span<RPageStorage::RSealedPageGroup> ranges) final;
0551    RStagedCluster StageCluster(ROOT::NTupleSize_t nNewEntries) final;
0552    void CommitStagedClusters(std::span<RStagedCluster> clusters) final;
0553    void CommitClusterGroup() final;
0554    void CommitAttributeSet(std::string_view attrSetName, const RNTupleLink &attrAnchorInfo) final;
0555    /// \return The locator and length of the written anchor.
0556    RNTupleLink CommitDatasetImpl() final;
0557 }; // class RPagePersistentSink
0558 
0559 // clang-format off
0560 /**
0561 \class ROOT::Internal::RPageSource
0562 \ingroup NTuple
0563 \brief Abstract interface to read data from an ntuple
0564 
0565 The page source is initialized with the columns of interest. Alias columns from projected fields are mapped to the
0566 corresponding physical columns. Pages from the columns of interest can then be mapped into memory.
0567 The page source also gives access to the ntuple's metadata.
0568 */
0569 // clang-format on
0570 class RPageSource : public RPageStorage {
0571 public:
0572    /// Used in SetEntryRange / GetEntryRange
0573    struct REntryRange {
0574       ROOT::NTupleSize_t fFirstEntry = ROOT::kInvalidNTupleIndex;
0575       ROOT::NTupleSize_t fNEntries = 0;
0576 
0577       /// Returns true if the given cluster has entries within the entry range
0578       bool IntersectsWith(const ROOT::RClusterDescriptor &clusterDesc) const;
0579    };
0580 
0581    /// An RAII wrapper used for the read-only access to `RPageSource::fDescriptor`. See `GetExclDescriptorGuard()``.
0582    class RSharedDescriptorGuard {
0583       const ROOT::RNTupleDescriptor &fDescriptor;
0584       std::shared_mutex &fLock;
0585 
0586    public:
0587       RSharedDescriptorGuard(const ROOT::RNTupleDescriptor &desc, std::shared_mutex &lock)
0588          : fDescriptor(desc), fLock(lock)
0589       {
0590          fLock.lock_shared();
0591       }
0592       RSharedDescriptorGuard(const RSharedDescriptorGuard &) = delete;
0593       RSharedDescriptorGuard &operator=(const RSharedDescriptorGuard &) = delete;
0594       RSharedDescriptorGuard(RSharedDescriptorGuard &&) = delete;
0595       RSharedDescriptorGuard &operator=(RSharedDescriptorGuard &&) = delete;
0596       ~RSharedDescriptorGuard() { fLock.unlock_shared(); }
0597       const ROOT::RNTupleDescriptor *operator->() const { return &fDescriptor; }
0598       const ROOT::RNTupleDescriptor &GetRef() const { return fDescriptor; }
0599    };
0600 
0601    /// An RAII wrapper used for the writable access to `RPageSource::fDescriptor`. See `GetSharedDescriptorGuard()`.
0602    class RExclDescriptorGuard {
0603       ROOT::RNTupleDescriptor &fDescriptor;
0604       std::shared_mutex &fLock;
0605 
0606    public:
0607       RExclDescriptorGuard(ROOT::RNTupleDescriptor &desc, std::shared_mutex &lock) : fDescriptor(desc), fLock(lock)
0608       {
0609          fLock.lock();
0610       }
0611       RExclDescriptorGuard(const RExclDescriptorGuard &) = delete;
0612       RExclDescriptorGuard &operator=(const RExclDescriptorGuard &) = delete;
0613       RExclDescriptorGuard(RExclDescriptorGuard &&) = delete;
0614       RExclDescriptorGuard &operator=(RExclDescriptorGuard &&) = delete;
0615       ~RExclDescriptorGuard()
0616       {
0617          fDescriptor.IncGeneration();
0618          fLock.unlock();
0619       }
0620       ROOT::RNTupleDescriptor *operator->() const { return &fDescriptor; }
0621       void MoveIn(ROOT::RNTupleDescriptor desc) { fDescriptor = std::move(desc); }
0622    };
0623 
0624 private:
0625    ROOT::RNTupleDescriptor fDescriptor;
0626    mutable std::shared_mutex fDescriptorLock;
0627    REntryRange fEntryRange;                  ///< Used by the cluster pool to prevent reading beyond the given range
0628    bool fHasStructure = false;               ///< Set to true once `LoadStructure()` is called
0629    bool fIsAttached = false;                 ///< Set to true once `Attach()` is called
0630    bool fHasStreamerInfosRegistered = false; ///< Set to true when RegisterStreamerInfos() is called.
0631 
0632    /// Remembers the last cluster id from which a page was requested
0633    ROOT::DescriptorId_t fLastUsedCluster = ROOT::kInvalidDescriptorId;
0634    /// Clusters from where pages got preloaded in UnzipClusterImpl(), ordered by first entry number
0635    /// of the clusters. If the last used cluster changes in LoadPage(), all unused pages from
0636    /// previous clusters are evicted from the page pool. Pinned clusters won't be evicted.
0637    std::map<ROOT::NTupleSize_t, ROOT::DescriptorId_t> fPreloadedClusters;
0638 
0639    /// Does nothing if fLastUsedCluster == clusterId. Otherwise, updated fLastUsedCluster
0640    /// and evict unused paged from the page pool of all previous clusters.
0641    /// Must not be called when the descriptor guard is taken.
0642    void UpdateLastUsedCluster(ROOT::DescriptorId_t clusterId);
0643 
0644 protected:
0645    /// Default I/O performance counters that get registered in `fMetrics`
0646    struct RCounters {
0647       ROOT::Experimental::Detail::RNTupleAtomicCounter &fNReadV;
0648       ROOT::Experimental::Detail::RNTupleAtomicCounter &fNRead;
0649       ROOT::Experimental::Detail::RNTupleAtomicCounter &fSzReadPayload;
0650       ROOT::Experimental::Detail::RNTupleAtomicCounter &fSzReadOverhead;
0651       ROOT::Experimental::Detail::RNTupleAtomicCounter &fSzUnzip;
0652       ROOT::Experimental::Detail::RNTupleAtomicCounter &fNClusterLoaded;
0653       ROOT::Experimental::Detail::RNTupleAtomicCounter &fNPageRead;
0654       ROOT::Experimental::Detail::RNTupleAtomicCounter &fNPageUnsealed;
0655       ROOT::Experimental::Detail::RNTupleAtomicCounter &fTimeWallRead;
0656       ROOT::Experimental::Detail::RNTupleAtomicCounter &fTimeWallUnzip;
0657       ROOT::Experimental::Detail::RNTupleTickCounter<ROOT::Experimental::Detail::RNTupleAtomicCounter> &fTimeCpuRead;
0658       ROOT::Experimental::Detail::RNTupleTickCounter<ROOT::Experimental::Detail::RNTupleAtomicCounter> &fTimeCpuUnzip;
0659       ROOT::Experimental::Detail::RNTupleCalcPerf &fBandwidthReadUncompressed;
0660       ROOT::Experimental::Detail::RNTupleCalcPerf &fBandwidthReadCompressed;
0661       ROOT::Experimental::Detail::RNTupleCalcPerf &fBandwidthUnzip;
0662       ROOT::Experimental::Detail::RNTupleCalcPerf &fFractionReadOverhead;
0663       ROOT::Experimental::Detail::RNTupleCalcPerf &fCompressionRatio;
0664    };
0665 
0666    /// Keeps track of the requested physical column IDs and their in-memory target type via a column element identifier.
0667    /// When using alias columns (projected fields), physical columns may be requested multiple times.
0668    class RActivePhysicalColumns {
0669    public:
0670       struct RColumnInfo {
0671          ROOT::Internal::RColumnElementBase::RIdentifier fElementId;
0672          std::size_t fRefCounter = 0;
0673       };
0674 
0675    private:
0676       /// Maps physical column IDs to all the requested in-memory representations.
0677       /// A pair of physical column ID and in-memory representation can be requested multiple times, which is
0678       /// indicated by the reference counter.
0679       /// We can only have a handful of possible in-memory representations for a given column,
0680       /// so it is fine to search them linearly.
0681       std::unordered_map<ROOT::DescriptorId_t, std::vector<RColumnInfo>> fColumnInfos;
0682 
0683    public:
0684       void Insert(ROOT::DescriptorId_t physicalColumnId, ROOT::Internal::RColumnElementBase::RIdentifier elementId);
0685       void Erase(ROOT::DescriptorId_t physicalColumnId, ROOT::Internal::RColumnElementBase::RIdentifier elementId);
0686       ROOT::Internal::RCluster::ColumnSet_t ToColumnSet() const;
0687       bool HasColumnInfos(ROOT::DescriptorId_t physicalColumnId) const
0688       {
0689          return fColumnInfos.count(physicalColumnId) > 0;
0690       }
0691       const std::vector<RColumnInfo> &GetColumnInfos(ROOT::DescriptorId_t physicalColumnId) const
0692       {
0693          return fColumnInfos.at(physicalColumnId);
0694       }
0695    };
0696 
0697    /// Summarizes cluster-level information that are necessary to load a certain page.
0698    /// Used by LoadPageImpl().
0699    struct RClusterInfo {
0700       ROOT::DescriptorId_t fClusterId = 0;
0701       /// Location of the page on disk
0702       ROOT::RClusterDescriptor::RPageInfoExtended fPageInfo;
0703       /// The first element number of the page's column in the given cluster
0704       std::uint64_t fColumnOffset = 0;
0705    };
0706 
0707    std::unique_ptr<RCounters> fCounters;
0708 
0709    ROOT::RNTupleReadOptions fOptions;
0710    /// The active columns are implicitly defined by the model fields or views
0711    RActivePhysicalColumns fActivePhysicalColumns;
0712    /// Pinned clusters and their $2 * (cluster bunch size) - 1$ successors will not be evicted from the cluster pool.
0713    /// Pages of pinned clusters won't be evicted from the page pool.
0714    std::unordered_set<ROOT::DescriptorId_t> fPinnedClusters;
0715 
0716    /// The cluster pool asynchronously preloads the next few clusters. Note that derived classes should call
0717    /// fClusterPool.StopBackgroundThread() in their destructor so that the I/O background thread does not call
0718    /// methods from the destructed derived class.
0719    ROOT::Internal::RClusterPool fClusterPool;
0720    /// Pages that are unzipped with IMT are staged into the page pool
0721    ROOT::Internal::RPagePool fPagePool;
0722 
0723    virtual void LoadStructureImpl() = 0;
0724    /// `LoadStructureImpl()` has been called before `AttachImpl()` is called
0725    virtual ROOT::RNTupleDescriptor AttachImpl(ROOT::Internal::RNTupleSerializer::EDescriptorDeserializeMode mode) = 0;
0726    /// Returns a new, unattached page source for the same data set
0727    virtual std::unique_ptr<RPageSource> CloneImpl() const = 0;
0728    // Only called if a task scheduler is set. No-op be default.
0729    virtual void UnzipClusterImpl(ROOT::Internal::RCluster *cluster);
0730    // Returns a page from storage if not found in the page pool. Should be able to handle zero page locators.
0731    virtual ROOT::Internal::RPageRef
0732    LoadPageImpl(ColumnHandle_t columnHandle, const RClusterInfo &clusterInfo, ROOT::NTupleSize_t idxInCluster) = 0;
0733 
0734    /// Prepare a page range read for the column set in `clusterKey`.  Specifically, pages referencing the
0735    /// `kTypePageZero` locator are filled in `pageZeroMap`; otherwise, `perPageFunc` is called for each page. This is
0736    /// commonly used as part of `LoadClusters()` in derived classes.
0737    void PrepareLoadCluster(
0738       const ROOT::Internal::RCluster::RKey &clusterKey, ROOT::Internal::ROnDiskPageMap &pageZeroMap,
0739       std::function<void(ROOT::DescriptorId_t, ROOT::NTupleSize_t, const ROOT::RClusterDescriptor::RPageInfo &)>
0740          perPageFunc);
0741 
0742    /// Enables the default set of metrics provided by RPageSource. `prefix` will be used as the prefix for
0743    /// the counters registered in the internal RNTupleMetrics object.
0744    /// A subclass using the default set of metrics is responsible for updating the counters
0745    /// appropriately, e.g. `fCounters->fNRead.Inc()`
0746    /// Alternatively, a subclass might provide its own RNTupleMetrics object by overriding the
0747    /// `GetMetrics()` member function.
0748    void EnableDefaultMetrics(const std::string &prefix);
0749 
0750    /// Note that the underlying lock is not recursive. See `GetSharedDescriptorGuard()` for further information.
0751    RExclDescriptorGuard GetExclDescriptorGuard() { return RExclDescriptorGuard(fDescriptor, fDescriptorLock); }
0752 
0753 public:
0754    RPageSource(std::string_view ntupleName, const ROOT::RNTupleReadOptions &fOptions);
0755    RPageSource(const RPageSource &) = delete;
0756    RPageSource &operator=(const RPageSource &) = delete;
0757    RPageSource(RPageSource &&) = delete;
0758    RPageSource &operator=(RPageSource &&) = delete;
0759    ~RPageSource() override;
0760    /// Guess the concrete derived page source from the file name (location)
0761    static std::unique_ptr<RPageSource> Create(std::string_view ntupleName, std::string_view location,
0762                                               const ROOT::RNTupleReadOptions &options = ROOT::RNTupleReadOptions());
0763    /// Open the same storage multiple time, e.g. for reading in multiple threads.
0764    /// If the source is already attached, the clone will be attached, too. The clone will use, however,
0765    /// it's own connection to the underlying storage (e.g., file descriptor, XRootD handle, etc.)
0766    std::unique_ptr<RPageSource> Clone() const;
0767 
0768    /// Helper for unstreaming a page. This is commonly used in derived, concrete page sources.  The implementation
0769    /// currently always makes a memory copy, even if the sealed page is uncompressed and in the final memory layout.
0770    /// The optimization of directly mapping pages is left to the concrete page source implementations.
0771    RResult<ROOT::Internal::RPage> static UnsealPage(const RSealedPage &sealedPage,
0772                                                     const ROOT::Internal::RColumnElementBase &element,
0773                                                     ROOT::Internal::RPageAllocator &pageAlloc);
0774 
0775    EPageStorageType GetType() final { return EPageStorageType::kSource; }
0776    const ROOT::RNTupleReadOptions &GetReadOptions() const { return fOptions; }
0777 
0778    /// Takes the read lock for the descriptor. Multiple threads can take the lock concurrently.
0779    /// The underlying `std::shared_mutex`, however, is neither read nor write recursive:
0780    /// within one thread, only one lock (shared or exclusive) must be acquired at the same time. This requires special
0781    /// care in sections protected by `GetSharedDescriptorGuard()` and `GetExclDescriptorGuard()` especially to avoid
0782    /// that the locks are acquired indirectly. As a general guideline, no other
0783    /// method of the page source should be called (directly or indirectly) in a guarded section.
0784    const RSharedDescriptorGuard GetSharedDescriptorGuard() const
0785    {
0786       return RSharedDescriptorGuard(fDescriptor, fDescriptorLock);
0787    }
0788 
0789    ColumnHandle_t AddColumn(ROOT::DescriptorId_t fieldId, ROOT::Internal::RColumn &column) override;
0790    void DropColumn(ColumnHandle_t columnHandle) override;
0791 
0792    /// Loads header and footer without decompressing or deserializing them. This can be used to asynchronously open
0793    /// a file in the background. The method is idempotent and it is called as a first step in `Attach()`.
0794    /// Pages sources may or may not make use of splitting loading and processing metadata.
0795    /// Therefore, `LoadStructure()` may do nothing and defer loading the metadata to `Attach()`.
0796    void LoadStructure();
0797    /// Open the physical storage container and deserialize header and footer
0798    void Attach(ROOT::Internal::RNTupleSerializer::EDescriptorDeserializeMode mode =
0799                   ROOT::Internal::RNTupleSerializer::EDescriptorDeserializeMode::kForReading);
0800    ROOT::NTupleSize_t GetNEntries();
0801    ROOT::NTupleSize_t GetNElements(ColumnHandle_t columnHandle);
0802 
0803    /// Promise to only read from the given entry range. If set, prevents the cluster pool from reading-ahead beyond
0804    /// the given range. The range needs to be within `[0, GetNEntries())`.
0805    void SetEntryRange(const REntryRange &range);
0806    REntryRange GetEntryRange() const { return fEntryRange; }
0807 
0808    /// Allocates and fills a page that contains the index-th element. The default implementation searches
0809    /// the page and calls LoadPageImpl(). Returns a default-constructed RPage for suppressed columns.
0810    virtual ROOT::Internal::RPageRef LoadPage(ColumnHandle_t columnHandle, ROOT::NTupleSize_t globalIndex);
0811    /// Another version of `LoadPage` that allows to specify cluster-relative indexes.
0812    /// Returns a default-constructed RPage for suppressed columns.
0813    virtual ROOT::Internal::RPageRef LoadPage(ColumnHandle_t columnHandle, RNTupleLocalIndex localIndex);
0814 
0815    /// Read the packed and compressed bytes of a page into the memory buffer provided by `sealedPage`. The sealed page
0816    /// can be used subsequently in a call to `RPageSink::CommitSealedPage`.
0817    /// The `fSize` and `fNElements` member of the sealedPage parameters are always set. If `sealedPage.fBuffer` is
0818    /// `nullptr`, no data will be copied but the returned size information can be used by the caller to allocate a large
0819    /// enough buffer and call `LoadSealedPage` again.
0820    virtual void
0821    LoadSealedPage(ROOT::DescriptorId_t physicalColumnId, RNTupleLocalIndex localIndex, RSealedPage &sealedPage) = 0;
0822 
0823    /// Populates all the pages of the given cluster ids and columns; it is possible that some columns do not
0824    /// contain any pages.  The page source may load more columns than the minimal necessary set from `columns`.
0825    /// To indicate which columns have been loaded, `LoadClusters()`` must mark them with `SetColumnAvailable()`.
0826    /// That includes the ones from the `columns` that don't have pages; otherwise subsequent requests
0827    /// for the cluster would assume an incomplete cluster and trigger loading again.
0828    /// `LoadClusters()` is typically called from the I/O thread of a cluster pool, i.e. the method runs
0829    /// concurrently to other methods of the page source.
0830    virtual std::vector<std::unique_ptr<ROOT::Internal::RCluster>>
0831    LoadClusters(std::span<ROOT::Internal::RCluster::RKey> clusterKeys) = 0;
0832 
0833    /// Parallel decompression and unpacking of the pages in the given cluster. The unzipped pages are supposed
0834    /// to be preloaded in a page pool attached to the source. The method is triggered by the cluster pool's
0835    /// unzip thread. It is an optional optimization, the method can safely do nothing. In particular, the
0836    /// actual implementation will only run if a task scheduler is set. In practice, a task scheduler is set
0837    /// if implicit multi-threading is turned on.
0838    void UnzipCluster(ROOT::Internal::RCluster *cluster);
0839 
0840    /// Instructs the cluster pool and page pool to consider the given cluster as active (should stay cached).
0841    void PinCluster(ROOT::DescriptorId_t clusterId) { fPinnedClusters.insert(clusterId); }
0842    /// Allows the given cluster to be evicted from the cluster pool and page pool.
0843    void UnpinCluster(ROOT::DescriptorId_t clusterId) { fPinnedClusters.erase(clusterId); }
0844    const std::unordered_set<ROOT::DescriptorId_t> &GetPinnedClusters() const { return fPinnedClusters; }
0845 
0846    // TODO(gparolini): for symmetry with SealPage(), we should either make this private or SealPage() public.
0847    RResult<ROOT::Internal::RPage>
0848    UnsealPage(const RSealedPage &sealedPage, const ROOT::Internal::RColumnElementBase &element);
0849 
0850    /// Builds the streamer info records from the descriptor's extra type info section. This is necessary when
0851    /// connecting streamer fields so that emulated classes can be read.
0852    void RegisterStreamerInfos();
0853 
0854    /// Forces the loading of ROOT StreamerInfo from the underlying file. This currently only has an effect for
0855    /// TFile-backed sources.
0856    virtual void LoadStreamerInfo() = 0;
0857 
0858    /// Creates a new PageSource using the same underlying file as this but referring to a different RNTuple,
0859    /// described by `anchorLink`.
0860    virtual std::unique_ptr<RPageSource> OpenWithDifferentAnchor(const ROOT::Internal::RNTupleLink &anchorLink,
0861                                                                 const ROOT::RNTupleReadOptions &options = {}) = 0;
0862 }; // class RPageSource
0863 
0864 } // namespace Internal
0865 } // namespace ROOT
0866 
0867 #endif