Skip to content

Commit

Permalink
RecordComponent: Properly handle uninitialized datasets (#1316)
Browse files Browse the repository at this point in the history
* Use a std::optional for BaseRecordComponentData::m_dataset

* Fix the bugs found by this check

Fix the examples, the unfinished_iteratoin_test and the Coretests

* Don't flush in JSONIOHandlerImpl destructor

It's not needed, and it avoids weird situations while recovering from an
error.

* Accept undefined dataset as long as no chunks are written yet

Just ignore and skip the component in that case
  • Loading branch information
franzpoeschel authored Apr 13, 2023
1 parent 4890388 commit 413b1c5
Show file tree
Hide file tree
Showing 11 changed files with 153 additions and 58 deletions.
5 changes: 3 additions & 2 deletions examples/7_extended_write_serial.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,9 @@ int main()
{{io::UnitDimension::M, 1}});
electrons["displacement"]["x"].setUnitSI(1e-6);
electrons.erase("displacement");
electrons["weighting"][io::RecordComponent::SCALAR].makeConstant(
1.e-5);
electrons["weighting"][io::RecordComponent::SCALAR]
.resetDataset({io::Datatype::FLOAT, {1}})
.makeConstant(1.e-5);
}

io::Mesh mesh = cur_it.meshes["lowRez_2D_field"];
Expand Down
4 changes: 3 additions & 1 deletion examples/7_extended_write_serial.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@
electrons["displacement"].unit_dimension = {Unit_Dimension.M: 1}
electrons["displacement"]["x"].unit_SI = 1.e-6
del electrons["displacement"]
electrons["weighting"][SCALAR].make_constant(1.e-5)
electrons["weighting"][SCALAR] \
.reset_dataset(Dataset(np.dtype("float32"), extent=[1])) \
.make_constant(1.e-5)

mesh = cur_it.meshes["lowRez_2D_field"]
mesh.axis_labels = ["x", "y"]
Expand Down
4 changes: 3 additions & 1 deletion examples/9_particle_write_serial.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@
# don't like it anymore? remove it with:
# del electrons["displacement"]

electrons["weighting"][SCALAR].make_constant(1.e-5)
electrons["weighting"][SCALAR] \
.reset_dataset(Dataset(np.dtype("float32"), extent=[1])) \
.make_constant(1.e-5)

particlePos_x = np.random.rand(234).astype(np.float32)
particlePos_y = np.random.rand(234).astype(np.float32)
Expand Down
8 changes: 7 additions & 1 deletion include/openPMD/RecordComponent.tpp
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,13 @@ RecordComponent::storeChunk(Offset o, Extent e, F &&createBuffer)
dCreate.name = rc.m_name;
dCreate.extent = getExtent();
dCreate.dtype = getDatatype();
dCreate.options = rc.m_dataset.options;
if (!rc.m_dataset.has_value())
{
throw error::WrongAPIUsage(
"[RecordComponent] Must specify dataset type and extent before "
"using storeChunk() (see RecordComponent::resetDataset()).");
}
dCreate.options = rc.m_dataset.value().options;
IOHandler()->enqueue(IOTask(this, dCreate));
}
Parameter<Operation::GET_BUFFER_VIEW> getBufferView;
Expand Down
4 changes: 3 additions & 1 deletion include/openPMD/backend/BaseRecordComponent.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
#include "openPMD/Error.hpp"
#include "openPMD/backend/Attributable.hpp"

#include <optional>

// expose private and protected members for invasive testing
#ifndef OPENPMD_protected
#define OPENPMD_protected protected:
Expand All @@ -39,7 +41,7 @@ namespace internal
/**
* The type and extent of the dataset defined by this component.
*/
Dataset m_dataset{Datatype::UNDEFINED, {}};
std::optional<Dataset> m_dataset;
/**
* True if this is defined as a constant record component as specified
* in the openPMD standard.
Expand Down
18 changes: 1 addition & 17 deletions src/IO/JSON/JSONIOHandlerImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,23 +58,7 @@ JSONIOHandlerImpl::JSONIOHandlerImpl(AbstractIOHandler *handler)
: AbstractIOHandlerImpl(handler)
{}

JSONIOHandlerImpl::~JSONIOHandlerImpl()
{
// we must not throw in a destructor
try
{
flush();
}
catch (std::exception const &ex)
{
std::cerr << "[~JSONIOHandlerImpl] An error occurred: " << ex.what()
<< std::endl;
}
catch (...)
{
std::cerr << "[~JSONIOHandlerImpl] An error occurred." << std::endl;
}
}
JSONIOHandlerImpl::~JSONIOHandlerImpl() = default;

std::future<void> JSONIOHandlerImpl::flush()
{
Expand Down
73 changes: 56 additions & 17 deletions src/RecordComponent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ namespace internal
RecordComponent impl{
std::shared_ptr<RecordComponentData>{this, [](auto const *) {}}};
impl.setUnitSI(1);
impl.resetDataset(Dataset(Datatype::CHAR, {1}));
}
} // namespace internal

Expand Down Expand Up @@ -71,11 +70,17 @@ RecordComponent &RecordComponent::resetDataset(Dataset d)
auto &rc = get();
if (written())
{
if (!rc.m_dataset.has_value())
{
throw error::Internal(
"Internal control flow error: Written record component must "
"have defined datatype and extent.");
}
if (d.dtype == Datatype::UNDEFINED)
{
d.dtype = rc.m_dataset.dtype;
d.dtype = rc.m_dataset.value().dtype;
}
else if (d.dtype != rc.m_dataset.dtype)
else if (d.dtype != rc.m_dataset.value().dtype)
{
throw std::runtime_error(
"Cannot change the datatype of a dataset.");
Expand All @@ -99,7 +104,7 @@ RecordComponent &RecordComponent::resetDataset(Dataset d)
rc.m_isEmpty = false;
if (written())
{
rc.m_dataset.extend(std::move(d.extent));
rc.m_dataset.value().extend(std::move(d.extent));
}
else
{
Expand All @@ -112,12 +117,28 @@ RecordComponent &RecordComponent::resetDataset(Dataset d)

uint8_t RecordComponent::getDimensionality() const
{
return get().m_dataset.rank;
auto &rc = get();
if (rc.m_dataset.has_value())
{
return rc.m_dataset.value().rank;
}
else
{
return 1;
}
}

Extent RecordComponent::getExtent() const
{
return get().m_dataset.extent;
auto &rc = get();
if (rc.m_dataset.has_value())
{
return rc.m_dataset.value().extent;
}
else
{
return {1};
}
}

namespace detail
Expand Down Expand Up @@ -149,6 +170,12 @@ RecordComponent &RecordComponent::makeEmpty(Dataset d)
auto &rc = get();
if (written())
{
if (!rc.m_dataset.has_value())
{
throw error::Internal(
"Internal control flow error: Written record component must "
"have defined datatype and extent.");
}
if (!constant())
{
throw std::runtime_error(
Expand All @@ -158,30 +185,30 @@ RecordComponent &RecordComponent::makeEmpty(Dataset d)
}
if (d.dtype == Datatype::UNDEFINED)
{
d.dtype = rc.m_dataset.dtype;
d.dtype = rc.m_dataset.value().dtype;
}
else if (d.dtype != rc.m_dataset.dtype)
else if (d.dtype != rc.m_dataset.value().dtype)
{
throw std::runtime_error(
"Cannot change the datatype of a dataset.");
}
rc.m_dataset.extend(std::move(d.extent));
rc.m_dataset.value().extend(std::move(d.extent));
rc.m_hasBeenExtended = true;
}
else
{
rc.m_dataset = std::move(d);
}

if (rc.m_dataset.extent.size() == 0)
if (rc.m_dataset.value().extent.size() == 0)
throw std::runtime_error("Dataset extent must be at least 1D.");

rc.m_isEmpty = true;
dirty() = true;
if (!written())
{
switchType<detail::DefaultValue<RecordComponent> >(
rc.m_dataset.dtype, *this);
rc.m_dataset.value().dtype, *this);
}
return *this;
}
Expand Down Expand Up @@ -213,11 +240,23 @@ void RecordComponent::flush(
/*
* This catches when a user forgets to use resetDataset.
*/
if (rc.m_dataset.dtype == Datatype::UNDEFINED)
if (!rc.m_dataset.has_value())
{
throw error::WrongAPIUsage(
"[RecordComponent] Must set specific datatype (Use "
"resetDataset call).");
// The check for !written() is technically not needed, just
// defensive programming against internal bugs that go on us.
if (!written() && rc.m_chunks.empty())
{
// No data written yet, just accessed the object so far without
// doing anything
// Just do nothing and skip this record component.
return;
}
else
{
throw error::WrongAPIUsage(
"[RecordComponent] Must specify dataset type and extent "
"before flushing (see RecordComponent::resetDataset()).");
}
}
if (!written())
{
Expand All @@ -243,7 +282,7 @@ void RecordComponent::flush(
dCreate.name = name;
dCreate.extent = getExtent();
dCreate.dtype = getDatatype();
dCreate.options = rc.m_dataset.options;
dCreate.options = rc.m_dataset.value().options;
IOHandler()->enqueue(IOTask(this, dCreate));
}
}
Expand All @@ -262,7 +301,7 @@ void RecordComponent::flush(
else
{
Parameter<Operation::EXTEND_DATASET> pExtend;
pExtend.extent = rc.m_dataset.extent;
pExtend.extent = rc.m_dataset.value().extent;
IOHandler()->enqueue(IOTask(this, std::move(pExtend)));
rc.m_hasBeenExtended = false;
}
Expand Down
28 changes: 24 additions & 4 deletions src/backend/BaseRecordComponent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,29 @@ BaseRecordComponent &BaseRecordComponent::resetDatatype(Datatype d)
"A Records Datatype can not (yet) be changed after it has been "
"written.");

get().m_dataset.dtype = d;
auto &rc = get();
if (rc.m_dataset.has_value())
{
rc.m_dataset.value().dtype = d;
}
else
{
rc.m_dataset = Dataset{d, {1}};
}
return *this;
}

Datatype BaseRecordComponent::getDatatype() const
{
return get().m_dataset.dtype;
auto &rc = get();
if (rc.m_dataset.has_value())
{
return rc.m_dataset.value().dtype;
}
else
{
return Datatype::UNDEFINED;
}
}

bool BaseRecordComponent::constant() const
Expand All @@ -54,8 +70,12 @@ ChunkTable BaseRecordComponent::availableChunks()
auto &rc = get();
if (rc.m_isConstant)
{
Offset offset(rc.m_dataset.extent.size(), 0);
return ChunkTable{{std::move(offset), rc.m_dataset.extent}};
if (!rc.m_dataset.has_value())
{
return ChunkTable{};
}
Offset offset(rc.m_dataset.value().extent.size(), 0);
return ChunkTable{{std::move(offset), rc.m_dataset.value().extent}};
}
containingIteration().open();
Parameter<Operation::AVAILABLE_CHUNKS> param;
Expand Down
31 changes: 29 additions & 2 deletions src/backend/PatchRecordComponent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,15 @@ uint8_t PatchRecordComponent::getDimensionality() const

Extent PatchRecordComponent::getExtent() const
{
return get().m_dataset.extent;
auto &rc = get();
if (rc.m_dataset.has_value())
{
return rc.m_dataset.value().extent;
}
else
{
return {1};
}
}

PatchRecordComponent::PatchRecordComponent() : BaseRecordComponent{nullptr}
Expand All @@ -94,13 +102,32 @@ void PatchRecordComponent::flush(
}
else
{
if (!rc.m_dataset.has_value())
{
// The check for !written() is technically not needed, just
// defensive programming against internal bugs that go on us.
if (!written() && rc.m_chunks.empty())
{
// No data written yet, just accessed the object so far without
// doing anything
// Just do nothing and skip this record component.
return;
}
else
{
throw error::WrongAPIUsage(
"[PatchRecordComponent] Must specify dataset type and "
"extent before flushing (see "
"RecordComponent::resetDataset()).");
}
}
if (!written())
{
Parameter<Operation::CREATE_DATASET> dCreate;
dCreate.name = name;
dCreate.extent = getExtent();
dCreate.dtype = getDatatype();
dCreate.options = rc.m_dataset.options;
dCreate.options = rc.m_dataset.value().options;
IOHandler()->enqueue(IOTask(this, dCreate));
}

Expand Down
Loading

0 comments on commit 413b1c5

Please sign in to comment.