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
24namespace {
25
36
37/// An internal RPageSink that enables multiple RNTupleFillContext to write into a single common RPageSink.
38///
39/// The setup with two contexts looks as follows:
40///
41/// +------ owned by RNTupleFillContext ------+
42/// | |
43/// RPageSinkBuf --- forwards to ---> RPageSynchronizingSink ---+
44/// (and owns) |
45/// (via raw fInnerSink ptr) +-- RPageSink (usually a persistent sink)
46/// |
47/// RPageSinkBuf --- forwards to ---> RPageSynchronizingSink ---+
48/// | (and owns) |
49/// | |
50/// +------ owned by RNTupleFillContext ------+
51///
52/// The mutex used by the synchronizing sinks is owned by the RNTupleParallelWriter that also owns the original model,
53/// the "final" sink (usually a persistent sink) and keeps weak_ptr's of the contexts (to make sure they are destroyed
54/// before the writer is destructed).
55class RPageSynchronizingSink : public RPageSink {
56private:
57 /// The wrapped inner sink, not owned by this class.
58 RPageSink *fInnerSink;
59 std::mutex *fMutex;
60
61public:
62 explicit RPageSynchronizingSink(RPageSink &inner, std::mutex &mutex)
63 : RPageSink(inner.GetNTupleName(), inner.GetWriteOptions()), fInnerSink(&inner), fMutex(&mutex)
64 {
65 // Do not observe the sink's metrics: It will contain some counters for all threads, which is misleading for the
66 // users.
67 // fMetrics.ObserveMetrics(fSink->GetMetrics());
68 }
69 RPageSynchronizingSink(const RPageSynchronizingSink &) = delete;
70 RPageSynchronizingSink &operator=(const RPageSynchronizingSink &) = delete;
71
72 const RNTupleDescriptor &GetDescriptor() const final { return fInnerSink->GetDescriptor(); }
73
74 ColumnHandle_t AddColumn(DescriptorId_t, const RColumn &) final { return {}; }
75 void InitImpl(RNTupleModel &) final {}
76 void UpdateSchema(const RNTupleModelChangeset &, NTupleSize_t) final
77 {
78 throw RException(R__FAIL("UpdateSchema not supported via RPageSynchronizingSink"));
79 }
80 void UpdateExtraTypeInfo(const RExtraTypeInfoDescriptor &) final
81 {
82 throw RException(R__FAIL("UpdateExtraTypeInfo not supported via RPageSynchronizingSink"));
83 }
84
85 void CommitPage(ColumnHandle_t, const RPage &) final
86 {
87 throw RException(R__FAIL("should never commit single pages via RPageSynchronizingSink"));
88 }
89 void CommitSealedPage(DescriptorId_t, const RSealedPage &) final
90 {
91 throw RException(R__FAIL("should never commit sealed pages via RPageSynchronizingSink"));
92 }
93 void CommitSealedPageV(std::span<RPageStorage::RSealedPageGroup> ranges) final
94 {
95 fInnerSink->CommitSealedPageV(ranges);
96 }
97 std::uint64_t CommitCluster(NTupleSize_t nNewEntries) final { return fInnerSink->CommitCluster(nNewEntries); }
98 void CommitClusterGroup() final
99 {
100 throw RException(R__FAIL("should never commit cluster group via RPageSynchronizingSink"));
101 }
102 void CommitDatasetImpl() final
103 {
104 throw RException(R__FAIL("should never commit dataset via RPageSynchronizingSink"));
105 }
106
107 RPage ReservePage(ColumnHandle_t columnHandle, std::size_t nElements) final
108 {
109 return fInnerSink->ReservePage(columnHandle, nElements);
110 }
111 void ReleasePage(RPage &page) final { fInnerSink->ReleasePage(page); }
112
113 RSinkGuard GetSinkGuard() final { return RSinkGuard(fMutex); }
114};
115
116} // namespace
117
119 std::unique_ptr<Internal::RPageSink> sink)
120 : fSink(std::move(sink)), fModel(std::move(model)), fMetrics("RNTupleParallelWriter")
121{
122 fModel->Freeze();
123 fSink->Init(*fModel.get());
124 fMetrics.ObserveMetrics(fSink->GetMetrics());
125}
126
128{
129 for (const auto &context : fFillContexts) {
130 if (!context.expired()) {
131 R__LOG_ERROR(NTupleLog()) << "RNTupleFillContext has not been destructed";
132 return;
133 }
134 }
135
136 // Now commit all clusters as a cluster group and then the dataset.
137 try {
138 fSink->CommitClusterGroup();
139 fSink->CommitDataset();
140 } catch (const RException &err) {
141 R__LOG_ERROR(NTupleLog()) << "failure committing ntuple: " << err.GetError().GetReport();
142 }
143}
144
145std::unique_ptr<ROOT::Experimental::RNTupleParallelWriter>
146ROOT::Experimental::RNTupleParallelWriter::Recreate(std::unique_ptr<RNTupleModel> model, std::string_view ntupleName,
147 std::string_view storage, const RNTupleWriteOptions &options)
148{
149 if (!options.GetUseBufferedWrite()) {
150 throw RException(R__FAIL("parallel writing requires buffering"));
151 }
152
153 auto sink = Internal::RPagePersistentSink::Create(ntupleName, storage, options);
154 // Cannot use std::make_unique because the constructor of RNTupleParallelWriter is private.
155 return std::unique_ptr<RNTupleParallelWriter>(new RNTupleParallelWriter(std::move(model), std::move(sink)));
156}
157
158std::unique_ptr<ROOT::Experimental::RNTupleParallelWriter>
159ROOT::Experimental::RNTupleParallelWriter::Append(std::unique_ptr<RNTupleModel> model, std::string_view ntupleName,
160 TFile &file, const RNTupleWriteOptions &options)
161{
162 if (!options.GetUseBufferedWrite()) {
163 throw RException(R__FAIL("parallel writing requires buffering"));
164 }
165
166 auto sink = std::make_unique<Internal::RPageSinkFile>(ntupleName, file, options);
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::shared_ptr<ROOT::Experimental::RNTupleFillContext> ROOT::Experimental::RNTupleParallelWriter::CreateFillContext()
172{
173 std::lock_guard g(fMutex);
174
175 auto model = fModel->Clone();
176
177 // TODO: Think about honoring RNTupleWriteOptions::SetUseBufferedWrite(false); this requires synchronization on every
178 // call to CommitPage() *and* preparing multiple cluster descriptors in parallel!
179 auto sink = std::make_unique<Internal::RPageSinkBuf>(std::make_unique<RPageSynchronizingSink>(*fSink, fSinkMutex));
180
181 // Cannot use std::make_shared because the constructor of RNTupleFillContext is private. Also it would mean that the
182 // (direct) memory of all contexts stays around until the vector of weak_ptr's is cleared.
183 std::shared_ptr<RNTupleFillContext> context(new RNTupleFillContext(std::move(model), std::move(sink)));
184 fFillContexts.push_back(context);
185 return context;
186}
#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:43
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:41
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.
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