Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RNTupleParallelWriter.cxx
Go to the documentation of this file.
1/// \file RNTupleParallelWriter.cxx
2/// \ingroup NTuple ROOT7
3/// \author Jonas Hahnfeld <jonas.hahnfeld@cern.ch>
4/// \date 2024-02-01
5/// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback
6/// is welcome!
7
8/*************************************************************************
9 * Copyright (C) 1995-2024, Rene Brun and Fons Rademakers. *
10 * All rights reserved. *
11 * *
12 * For the licensing terms see $ROOTSYS/LICENSE. *
13 * For the list of contributors see $ROOTSYS/README/CREDITS. *
14 *************************************************************************/
15
17
18#include <ROOT/RNTupleModel.hxx>
20#include <ROOT/RPageSinkBuf.hxx>
21#include <ROOT/RPageStorage.hxx>
23
24#include <TError.h>
25
26namespace {
27
38
39/// An internal RPageSink that enables multiple RNTupleFillContext to write into a single common RPageSink.
40///
41/// The setup with two contexts looks as follows:
42///
43/// +------ owned by RNTupleFillContext ------+
44/// | |
45/// RPageSinkBuf --- forwards to ---> RPageSynchronizingSink ---+
46/// (and owns) |
47/// (via raw fInnerSink ptr) +-- RPageSink (usually a persistent sink)
48/// |
49/// RPageSinkBuf --- forwards to ---> RPageSynchronizingSink ---+
50/// | (and owns) |
51/// | |
52/// +------ owned by RNTupleFillContext ------+
53///
54/// The mutex used by the synchronizing sinks is owned by the RNTupleParallelWriter that also owns the original model,
55/// the "final" sink (usually a persistent sink) and keeps weak_ptr's of the contexts (to make sure they are destroyed
56/// before the writer is destructed).
57class RPageSynchronizingSink : public RPageSink {
58private:
59 /// The wrapped inner sink, not owned by this class.
60 RPageSink *fInnerSink;
61 std::mutex *fMutex;
62
63public:
64 explicit RPageSynchronizingSink(RPageSink &inner, std::mutex &mutex)
65 : RPageSink(inner.GetNTupleName(), inner.GetWriteOptions()), fInnerSink(&inner), fMutex(&mutex)
66 {
67 // Do not observe the sink's metrics: It will contain some counters for all threads, which is misleading for the
68 // users.
69 // fMetrics.ObserveMetrics(fSink->GetMetrics());
70 }
71 RPageSynchronizingSink(const RPageSynchronizingSink &) = delete;
72 RPageSynchronizingSink &operator=(const RPageSynchronizingSink &) = delete;
73
74 const RNTupleDescriptor &GetDescriptor() const final { return fInnerSink->GetDescriptor(); }
75
76 ColumnHandle_t AddColumn(DescriptorId_t, RColumn &) final { return {}; }
77 void InitImpl(RNTupleModel &) final {}
78 void UpdateSchema(const RNTupleModelChangeset &, NTupleSize_t) final
79 {
80 throw RException(R__FAIL("UpdateSchema not supported via RPageSynchronizingSink"));
81 }
82 void UpdateExtraTypeInfo(const RExtraTypeInfoDescriptor &) final
83 {
84 throw RException(R__FAIL("UpdateExtraTypeInfo not supported via RPageSynchronizingSink"));
85 }
86
87 void CommitSuppressedColumn(ColumnHandle_t handle) final { fInnerSink->CommitSuppressedColumn(handle); }
88 void CommitPage(ColumnHandle_t, const RPage &) final
89 {
90 throw RException(R__FAIL("should never commit single pages via RPageSynchronizingSink"));
91 }
92 void CommitSealedPage(DescriptorId_t, const RSealedPage &) final
93 {
94 throw RException(R__FAIL("should never commit sealed pages via RPageSynchronizingSink"));
95 }
96 void CommitSealedPageV(std::span<RPageStorage::RSealedPageGroup> ranges) final
97 {
98 fInnerSink->CommitSealedPageV(ranges);
99 }
100 std::uint64_t CommitCluster(NTupleSize_t nNewEntries) final { return fInnerSink->CommitCluster(nNewEntries); }
101 RStagedCluster StageCluster(NTupleSize_t nNewEntries) final { return fInnerSink->StageCluster(nNewEntries); }
102 void CommitStagedClusters(std::span<RStagedCluster> clusters) final { fInnerSink->CommitStagedClusters(clusters); }
103 void CommitClusterGroup() final
104 {
105 throw RException(R__FAIL("should never commit cluster group via RPageSynchronizingSink"));
106 }
107 void CommitDatasetImpl() final
108 {
109 throw RException(R__FAIL("should never commit dataset via RPageSynchronizingSink"));
110 }
111
112 RSinkGuard GetSinkGuard() final { return RSinkGuard(fMutex); }
113};
114
115} // namespace
116
118 std::unique_ptr<Internal::RPageSink> sink)
119 : fSink(std::move(sink)), fModel(std::move(model)), fMetrics("RNTupleParallelWriter")
120{
121 if (fModel->GetRegisteredSubfields().size() > 0) {
122 throw RException(R__FAIL("cannot create an RNTupleWriter from a model with registered subfields"));
123 }
124 fModel->Freeze();
125 fSink->Init(*fModel.get());
126 fMetrics.ObserveMetrics(fSink->GetMetrics());
127}
128
130{
131 try {
132 CommitDataset();
133 } catch (const RException &err) {
134 R__LOG_ERROR(NTupleLog()) << "failure committing ntuple: " << err.GetError().GetReport();
135 }
136}
137
139{
140 if (fModel->IsExpired())
141 return;
142
143 for (const auto &context : fFillContexts) {
144 if (!context.expired()) {
145 throw RException(R__FAIL("RNTupleFillContext has not been destructed"));
146 }
147 }
148
149 // Now commit all clusters as a cluster group and then the dataset.
150 fSink->CommitClusterGroup();
151 fSink->CommitDataset();
152 fModel->Expire();
153}
154
155std::unique_ptr<ROOT::Experimental::RNTupleParallelWriter>
156ROOT::Experimental::RNTupleParallelWriter::Recreate(std::unique_ptr<RNTupleModel> model, std::string_view ntupleName,
157 std::string_view storage, const RNTupleWriteOptions &options)
158{
159 if (!options.GetUseBufferedWrite()) {
160 throw RException(R__FAIL("parallel writing requires buffering"));
161 }
162
163 auto sink = Internal::RPagePersistentSink::Create(ntupleName, storage, options);
164 // Cannot use std::make_unique because the constructor of RNTupleParallelWriter is private.
165 return std::unique_ptr<RNTupleParallelWriter>(new RNTupleParallelWriter(std::move(model), std::move(sink)));
166}
167
168std::unique_ptr<ROOT::Experimental::RNTupleParallelWriter>
169ROOT::Experimental::RNTupleParallelWriter::Append(std::unique_ptr<RNTupleModel> model, std::string_view ntupleName,
170 TFile &file, const RNTupleWriteOptions &options)
171{
172 if (!options.GetUseBufferedWrite()) {
173 throw RException(R__FAIL("parallel writing requires buffering"));
174 }
175
176 auto sink = std::make_unique<Internal::RPageSinkFile>(ntupleName, file, options);
177 // Cannot use std::make_unique because the constructor of RNTupleParallelWriter is private.
178 return std::unique_ptr<RNTupleParallelWriter>(new RNTupleParallelWriter(std::move(model), std::move(sink)));
179}
180
181std::shared_ptr<ROOT::Experimental::RNTupleFillContext> ROOT::Experimental::RNTupleParallelWriter::CreateFillContext()
182{
183 std::lock_guard g(fMutex);
184
185 auto model = fModel->Clone();
186
187 // TODO: Think about honoring RNTupleWriteOptions::SetUseBufferedWrite(false); this requires synchronization on every
188 // call to CommitPage() *and* preparing multiple cluster descriptors in parallel!
189 auto sink = std::make_unique<Internal::RPageSinkBuf>(std::make_unique<RPageSynchronizingSink>(*fSink, fSinkMutex));
190
191 // Cannot use std::make_shared because the constructor of RNTupleFillContext is private. Also it would mean that the
192 // (direct) memory of all contexts stays around until the vector of weak_ptr's is cleared.
193 std::shared_ptr<RNTupleFillContext> context(new RNTupleFillContext(std::move(model), std::move(sink)));
194 fFillContexts.push_back(context);
195 return context;
196}
#define R__FAIL(msg)
Short-hand to return an RResult<T> in an error state; the RError is implicitly converted into RResult...
Definition RError.hxx:290
#define R__LOG_ERROR(...)
Definition RLogger.hxx:362
#define g(i)
Definition RSha256.hxx:105
Binding & operator=(OUT(*fun)(void))
void ObserveMetrics(RNTupleMetrics &observee)
A column is a storage-backed array of a simple, fixed-size type, from which pages can be mapped into ...
Definition RColumn.hxx:40
static std::unique_ptr< RPageSink > Create(std::string_view ntupleName, std::string_view location, const RNTupleWriteOptions &options=RNTupleWriteOptions())
Guess the concrete derived page source from the location.
Abstract interface to write data into an ntuple.
A page is a slice of a column that is mapped into memory.
Definition RPage.hxx:47
std::string GetReport() const
Format a dignostics report, e.g. for an exception message.
Definition RError.cxx:25
Base class for all ROOT issued exceptions.
Definition RError.hxx:78
const RError & GetError() const
Definition RError.hxx:82
Field specific extra type information from the header / extenstion header.
The on-storage meta-data of an ntuple.
A context for filling entries (data) into clusters of an RNTuple.
The RNTupleModel encapulates the schema of an ntuple.
A writer to fill an RNTuple from multiple contexts.
RNTupleParallelWriter(std::unique_ptr< RNTupleModel > model, std::unique_ptr< Internal::RPageSink > sink)
std::unique_ptr< RNTupleModel > fModel
The original RNTupleModel connected to fSink; needs to be destructed before it.
std::unique_ptr< Internal::RPageSink > fSink
The final RPageSink that represents the synchronization point.
static std::unique_ptr< RNTupleParallelWriter > Recreate(std::unique_ptr< RNTupleModel > model, std::string_view ntupleName, std::string_view storage, const RNTupleWriteOptions &options=RNTupleWriteOptions())
Recreate a new file and return a writer to write an ntuple.
std::shared_ptr< RNTupleFillContext > CreateFillContext()
Create a new RNTupleFillContext that can be used to fill entries and prepare clusters in parallel.
void CommitDataset()
Automatically called by the destructor.
static std::unique_ptr< RNTupleParallelWriter > Append(std::unique_ptr< RNTupleModel > model, std::string_view ntupleName, TFile &file, const RNTupleWriteOptions &options=RNTupleWriteOptions())
Append an ntuple to the existing file, which must not be accessed while data is filled into any creat...
Common user-tunable settings for storing ntuples.
A ROOT file is an on-disk file, usually with extension .root, that stores objects in a file-system-li...
Definition TFile.h:53
RLogChannel & NTupleLog()
Log channel for RNTuple diagnostics.
std::uint64_t NTupleSize_t
Integer type long enough to hold the maximum number of entries in a column.
std::uint64_t DescriptorId_t
Distriniguishes elements of the same type within a descriptor, e.g. different fields.
The incremental changes to a RNTupleModel