Skip to content

Commit 477137c

Browse files
committed
[ntuple] automatic evolution in and out std::atomic
Allow for std::atomic<T> --> T (or compatible) automatic conversion and vice versa.
1 parent 4c3335e commit 477137c

File tree

7 files changed

+76
-6
lines changed

7 files changed

+76
-6
lines changed

tree/ntuple/inc/ROOT/RNTupleDescriptor.hxx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ public:
153153
/// The dictionary does not need to be available for this method.
154154
/// Needs the full descriptor to look up sub fields.
155155
bool IsCustomEnum(const RNTupleDescriptor &desc) const;
156+
bool IsStdAtomic() const;
156157
};
157158

158159
// clang-format off

tree/ntuple/src/RField.cxx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1165,10 +1165,12 @@ std::unique_ptr<ROOT::RFieldBase> ROOT::RAtomicField::CloneImpl(std::string_view
11651165

11661166
void ROOT::RAtomicField::ReconcileOnDiskField(const RNTupleDescriptor &desc)
11671167
{
1168-
static const std::vector<std::string> prefixes = {"std::atomic<"};
1169-
1170-
EnsureMatchingOnDiskField(desc, kDiffTypeName).ThrowOnError();
1171-
EnsureMatchingTypePrefix(desc, prefixes).ThrowOnError();
1168+
const auto &fieldDesc = desc.GetFieldDescriptor(GetOnDiskId());
1169+
if (fieldDesc.GetTypeName().rfind("std::atomic<", 0) == 0) {
1170+
EnsureMatchingOnDiskField(desc, kDiffTypeName).ThrowOnError();
1171+
} else {
1172+
fSubfields[0]->SetOnDiskId(GetOnDiskId());
1173+
}
11721174
}
11731175

11741176
std::vector<ROOT::RFieldBase::RValue> ROOT::RAtomicField::SplitValue(const RValue &value) const

tree/ntuple/src/RFieldBase.cxx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -952,6 +952,18 @@ void ROOT::RFieldBase::ConnectPageSource(ROOT::Internal::RPageSource &pageSource
952952
if (!fDescription.empty())
953953
throw RException(R__FAIL("setting description only valid when connecting to a page sink"));
954954

955+
if (!fIsArtificial) {
956+
R__ASSERT(fOnDiskId != kInvalidDescriptorId);
957+
// Handle moving from on-disk std::atomic<T> to (compatible of) T in memory centrally because otherwise
958+
// we would need to handle it in each and every ReconcileOnDiskField()
959+
// Note that we have to do this before calling BeforeConnectPageSource(), which already may compare the field
960+
// to its on-disk description.
961+
const auto &desc = pageSource.GetSharedDescriptorGuard().GetRef();
962+
if (!dynamic_cast<RAtomicField *>(this) && desc.GetFieldDescriptor(GetOnDiskId()).IsStdAtomic()) {
963+
SetOnDiskId(desc.GetFieldDescriptor(GetOnDiskId()).GetLinkIds()[0]);
964+
}
965+
}
966+
955967
auto substitute = BeforeConnectPageSource(pageSource);
956968
if (substitute) {
957969
const RFieldBase *itr = this;
@@ -974,8 +986,8 @@ void ROOT::RFieldBase::ConnectPageSource(ROOT::Internal::RPageSource &pageSource
974986
}
975987

976988
if (!fIsArtificial) {
977-
R__ASSERT(fOnDiskId != kInvalidDescriptorId);
978-
ReconcileOnDiskField(pageSource.GetSharedDescriptorGuard().GetRef());
989+
const auto &desc = pageSource.GetSharedDescriptorGuard().GetRef();
990+
ReconcileOnDiskField(desc);
979991
}
980992

981993
for (auto &f : fSubfields) {

tree/ntuple/src/RNTupleDescriptor.cxx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,13 @@ bool ROOT::RFieldDescriptor::IsCustomEnum(const RNTupleDescriptor &desc) const
187187
desc.GetFieldDescriptor(subFieldId).GetTypeName()) != std::end(gIntTypeNames);
188188
}
189189

190+
bool ROOT::RFieldDescriptor::IsStdAtomic() const
191+
{
192+
if (fStructure != ROOT::ENTupleStructure::kPlain)
193+
return false;
194+
return (fTypeName.rfind("std::atomic<", 0) == 0);
195+
}
196+
190197
////////////////////////////////////////////////////////////////////////////////
191198

192199
bool ROOT::RColumnDescriptor::operator==(const RColumnDescriptor &other) const

tree/ntuple/test/CustomStruct.hxx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ enum class CustomEnumUInt32 : unsigned int {};
3636
enum class CustomEnumInt64 : long int {};
3737
enum class CustomEnumUInt64 : unsigned long int {};
3838

39+
// Used for std::atomic tests as an example of a class that is not lock-free.
40+
struct CustomAtomicNotLockFree {
41+
int a[100];
42+
};
43+
3944
struct CustomStruct {
4045
template <typename T>
4146
using MyVec = std::vector<T>;

tree/ntuple/test/CustomStructLinkDef.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
#pragma link C++ enum CustomEnumInt64;
1414
#pragma link C++ enum CustomEnumUInt64;
1515

16+
#pragma link C++ class CustomAtomicNotLockFree+;
17+
1618
#pragma link C++ class CustomStruct+;
1719
#pragma link C++ class DerivedA+;
1820
#pragma link C++ class DerivedA2+;

tree/ntuple/test/ntuple_evolution_type.cxx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,47 @@ TEST(RNTupleEvolution, Enum)
234234
EXPECT_EQ(kRenamedCustomEnumVal, ve3(0));
235235
}
236236

237+
TEST(RNTupleEvolution, CheckAtomic)
238+
{
239+
// TODO(jblomer): enable test with CustomAtomicNotLockFree once linking of libatomic is sorted out.
240+
241+
FileRaii fileGuard("test_ntuple_evolution_check_atomic.root");
242+
{
243+
auto model = ROOT::RNTupleModel::Create();
244+
auto atomicInt = model->MakeField<std::atomic<std::int32_t>>("atomicInt");
245+
auto regularInt = model->MakeField<std::int32_t>("regularInt");
246+
auto writer = ROOT::RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath());
247+
248+
*atomicInt = 7;
249+
*regularInt = 13;
250+
writer->Fill();
251+
}
252+
253+
auto reader = RNTupleReader::Open("ntpl", fileGuard.GetPath());
254+
255+
auto v1 = reader->GetView<std::atomic<std::int64_t>>("atomicInt");
256+
auto v2 = reader->GetView<std::atomic<std::int64_t>>("regularInt");
257+
auto v3 = reader->GetView<std::int64_t>("atomicInt");
258+
259+
try {
260+
reader->GetView<std::atomic<std::byte>>("atomicInt");
261+
FAIL() << "automatic evolution into an invalid atomic inner type should fail";
262+
} catch (const ROOT::RException &err) {
263+
EXPECT_THAT(err.what(), testing::HasSubstr("incompatible with on-disk field"));
264+
}
265+
266+
try {
267+
reader->GetView<CustomAtomicNotLockFree>("atomicInt");
268+
FAIL() << "automatic evolution into an invalid non-atomic inner type should fail";
269+
} catch (const ROOT::RException &err) {
270+
EXPECT_THAT(err.what(), testing::HasSubstr("incompatible type name for field"));
271+
}
272+
273+
EXPECT_EQ(7, v1(0));
274+
EXPECT_EQ(13, v2(0));
275+
EXPECT_EQ(7, v3(0));
276+
}
277+
237278
TEST(RNTupleEvolution, ArrayAsRVec)
238279
{
239280
FileRaii fileGuard("test_ntuple_evolution_array_as_rvec.root");

0 commit comments

Comments
 (0)