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 <TDirectory.h>
25#include <TError.h>
26#include <TFile.h>
27
28namespace {
29
39
40/// An internal RPageSink that enables multiple RNTupleFillContext to write into a single common RPageSink.
41///
42/// The setup with two contexts looks as follows:
43///
44/// +------ owned by RNTupleFillContext ------+
45/// | |
46/// RPageSinkBuf --- forwards to ---> RPageSynchronizingSink ---+
47/// (and owns) |
48/// (via raw fInnerSink ptr) +-- RPageSink (usually a persistent sink)
49/// |
50/// RPageSinkBuf --- forwards to ---> RPageSynchronizingSink ---+
51/// | (and owns) |
52/// | |
53/// +------ owned by RNTupleFillContext ------+
54///
55/// The mutex used by the synchronizing sinks is owned by the RNTupleParallelWriter that also owns the original model,
56/// the "final" sink (usually a persistent sink) and keeps weak_ptr's of the contexts (to make sure they are destroyed
57/// before the writer is destructed).
58class RPageSynchronizingSink : public RPageSink {
59private:
60 /// The wrapped inner sink, not owned by this class.
61 RPageSink *fInnerSink;
62 std::mutex *fMutex;
63
64public:
65 explicit RPageSynchronizingSink(RPageSink &inner, std::mutex &mutex)
66 : RPageSink(inner.GetNTupleName(), inner.GetWriteOptions()), fInnerSink(&inner), fMutex(&mutex)
67 {
68 // Do not observe the sink's metrics: It will contain some counters for all threads, which is misleading for the
69 // users.
70 // fMetrics.ObserveMetrics(fSink->GetMetrics());
71 }
72 RPageSynchronizingSink(const RPageSynchronizingSink &) = delete;
73 RPageSynchronizingSink &operator=(const RPageSynchronizingSink &) = delete;
74
75 const RNTupleDescriptor &GetDescriptor() const final { return fInnerSink->GetDescriptor(); }
76
77 NTupleSize_t GetNEntries() const final { return fInnerSink->GetNEntries(); }
78
79 ColumnHandle_t AddColumn(DescriptorId_t, RColumn &) final { return {}; }
80 void InitImpl(RNTupleModel &) final {}
81 void UpdateSchema(const RNTupleModelChangeset &, NTupleSize_t) final
82 {
83 throw ROOT::RException(R__FAIL("UpdateSchema not supported via RPageSynchronizingSink"));
84 }
85 void UpdateExtraTypeInfo(const RExtraTypeInfoDescriptor &) final
86 {
87 throw ROOT::RException(R__FAIL("UpdateExtraTypeInfo not supported via RPageSynchronizingSink"));
88 }
89
90 void CommitSuppressedColumn(ColumnHandle_t handle) final { fInnerSink->CommitSuppressedColumn(handle); }
91 void CommitPage(ColumnHandle_t, const RPage &) final
92 {
93 throw ROOT::RException(R__FAIL("should never commit single pages via RPageSynchronizingSink"));
94 }
95 void CommitSealedPage(DescriptorId_t, const RSealedPage &) final
96 {
97 throw ROOT::RException(R__FAIL("should never commit sealed pages via RPageSynchronizingSink"));
98 }
99 void CommitSealedPageV(std::span<RPageStorage::RSealedPageGroup> ranges) final
100 {
101 fInnerSink->CommitSealedPageV(ranges);
102 }
103 std::uint64_t CommitCluster(NTupleSize_t nNewEntries) final { return fInnerSink->CommitCluster(nNewEntries); }
104 RStagedCluster StageCluster(NTupleSize_t nNewEntries) final { return fInnerSink->StageCluster(nNewEntries); }
105 void CommitStagedClusters(std::span<RStagedCluster> clusters) final { fInnerSink->CommitStagedClusters(clusters); }
106 void CommitClusterGroup() final
107 {
108 throw ROOT::RException(R__FAIL("should never commit cluster group via RPageSynchronizingSink"));
109 }
110 void CommitDatasetImpl() final
111 {
112 throw ROOT::RException(R__FAIL("should never commit dataset via RPageSynchronizingSink"));
113 }
114
115 RSinkGuard GetSinkGuard() final { return RSinkGuard(fMutex); }
116};
117
118} // namespace
119
121 std::unique_ptr<Internal::RPageSink> sink)
122 : fSink(std::move(sink)), fModel(std::move(model)), fMetrics("RNTupleParallelWriter")
123{
124 if (fModel->GetRegisteredSubfields().size() > 0) {
125 throw RException(R__FAIL("cannot create an RNTupleParallelWriter from a model with registered subfields"));
126 }
127 fModel->Freeze();
128 fSink->Init(*fModel.get());
129 fMetrics.ObserveMetrics(fSink->GetMetrics());
130}
131
133{
134 try {
135 CommitDataset();
136 } catch (const RException &err) {
137 R__LOG_ERROR(ROOT::Internal::NTupleLog()) << "failure committing ntuple: " << err.GetError().GetReport();
138 }
139}
140
142{
143 if (fModel->IsExpired())
144 return;
145
146 for (const auto &context : fFillContexts) {
147 if (!context.expired()) {
148 throw RException(R__FAIL("RNTupleFillContext has not been destructed"));
149 }
150 }
151
152 // Now commit all clusters as a cluster group and then the dataset.
153 fSink->CommitClusterGroup();
154 fSink->CommitDataset();
155 fModel->Expire();
156}
157
158std::unique_ptr<ROOT::Experimental::RNTupleParallelWriter>
159ROOT::Experimental::RNTupleParallelWriter::Recreate(std::unique_ptr<RNTupleModel> model, std::string_view ntupleName,
160 std::string_view storage, const ROOT::RNTupleWriteOptions &options)
161{
162 if (!options.GetUseBufferedWrite()) {
163 throw RException(R__FAIL("parallel writing requires buffering"));
164 }
165
167 // Cannot use std::make_unique because the constructor of RNTupleParallelWriter is private.
168 return std::unique_ptr<RNTupleParallelWriter>(new RNTupleParallelWriter(std::move(model), std::move(sink)));
169}
170
171std::unique_ptr<ROOT::Experimental::RNTupleParallelWriter>
172ROOT::Experimental::RNTupleParallelWriter::Append(std::unique_ptr<RNTupleModel> model, std::string_view ntupleName,
174{
175 auto file = fileOrDirectory.GetFile();
176 if (!file) {
177 throw RException(
178 R__FAIL("RNTupleParallelWriter only supports writing to a ROOT file. Cannot write into a directory "
179 "that is not backed by a file"));
180 }
181 if (!file->IsBinary()) {
182 throw RException(R__FAIL("RNTupleParallelWriter only supports writing to a ROOT file. Cannot write into " +
183 std::string(file->GetName())));
184 }
185 if (!options.GetUseBufferedWrite()) {
186 throw RException(R__FAIL("parallel writing requires buffering"));
187 }
188
189 auto sink = std::make_unique<Internal::RPageSinkFile>(ntupleName, fileOrDirectory, options);
190 // Cannot use std::make_unique because the constructor of RNTupleParallelWriter is private.
191 return std::unique_ptr<RNTupleParallelWriter>(new RNTupleParallelWriter(std::move(model), std::move(sink)));
192}
193
194std::shared_ptr<ROOT::Experimental::RNTupleFillContext> ROOT::Experimental::RNTupleParallelWriter::CreateFillContext()
195{
196 std::lock_guard g(fMutex);
197
198 auto model = fModel->Clone();
199 auto sink = std::make_unique<Internal::RPageSinkBuf>(std::make_unique<RPageSynchronizingSink>(*fSink, fSinkMutex));
200
201 // Cannot use std::make_shared because the constructor of RNTupleFillContext is private. Also it would mean that the
202 // (direct) memory of all contexts stays around until the vector of weak_ptr's is cleared.
203 std::shared_ptr<RNTupleFillContext> context(new RNTupleFillContext(std::move(model), std::move(sink)));
204 fFillContexts.push_back(context);
205 return context;
206}
#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:299
#define R__LOG_ERROR(...)
Definition RLogger.hxx:357
#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 ROOT::RNTupleWriteOptions &options=ROOT::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
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)
static std::unique_ptr< RNTupleParallelWriter > Recreate(std::unique_ptr< RNTupleModel > model, std::string_view ntupleName, std::string_view storage, const ROOT::RNTupleWriteOptions &options=ROOT::RNTupleWriteOptions())
Recreate a new file and return a writer to write an ntuple.
std::unique_ptr< RNTupleModel > fModel
The original RNTupleModel connected to fSink; needs to be destructed before it.
static std::unique_ptr< RNTupleParallelWriter > Append(std::unique_ptr< RNTupleModel > model, std::string_view ntupleName, TDirectory &fileOrDirectory, const ROOT::RNTupleWriteOptions &options=ROOT::RNTupleWriteOptions())
Append an ntuple to the existing file, which must not be accessed while data is filled into any creat...
std::unique_ptr< Internal::RPageSink > fSink
The final RPageSink that represents the synchronization point.
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.
Base class for all ROOT issued exceptions.
Definition RError.hxx:79
const RError & GetError() const
Definition RError.hxx:84
Common user-tunable settings for storing ntuples.
Describe directory structure in memory.
Definition TDirectory.h:45
ROOT::RLogChannel & NTupleLog()
Log channel for RNTuple diagnostics.
std::uint64_t DescriptorId_t
Distriniguishes elements of the same type within a descriptor, e.g. different fields.
std::uint64_t NTupleSize_t
Integer type long enough to hold the maximum number of entries in a column.
The incremental changes to a RNTupleModel