Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,7 @@ if(openPMD_HAVE_PYTHON)
src/binding/python/ParticleSpecies.cpp
src/binding/python/PatchRecord.cpp
src/binding/python/PatchRecordComponent.cpp
src/binding/python/Pickle.cpp
src/binding/python/Record.cpp
src/binding/python/RecordComponent.cpp
src/binding/python/MeshRecordComponent.cpp
Expand Down
4 changes: 2 additions & 2 deletions include/openPMD/Iteration.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,8 @@ class Iteration
friend class Container;
friend class Series;
friend class internal::AttributableData;
template <typename T>
friend T &internal::makeOwning(T &self, Series);
template <typename T, typename Series_type>
friend T &internal::makeOwning(T &self, Series_type);
friend class Writable;
friend class StatefulIterator;
friend class StatefulSnapshotsContainer;
Expand Down
4 changes: 2 additions & 2 deletions include/openPMD/ParticleSpecies.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ class ParticleSpecies
friend class Container<ParticleSpecies>;
friend class Container<Record>;
friend class Iteration;
template <typename T>
friend T &internal::makeOwning(T &self, Series);
template <typename T, typename Series_type>
friend T &internal::makeOwning(T &self, Series_type);
friend class internal::ScientificDefaults;
friend class Attributable;

Expand Down
4 changes: 2 additions & 2 deletions include/openPMD/RecordComponent.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ class RecordComponent
friend class DynamicMemoryView;
friend class internal::RecordComponentData;
friend class MeshRecordComponent;
template <typename T>
friend T &internal::makeOwning(T &self, Series);
template <typename T, typename Series_type>
friend T &internal::makeOwning(T &self, Series_type);
friend class internal::ScientificDefaults;
friend class Attributable;

Expand Down
9 changes: 9 additions & 0 deletions include/openPMD/Series.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,8 @@ class Series : public Attributable
friend class internal::SeriesData;
friend class internal::AttributableData;
friend class StatefulSnapshotsContainer;
template <typename T, typename Series_type>
friend T &internal::makeOwning(T &self, Series_type);

public:
explicit Series();
Expand Down Expand Up @@ -778,6 +780,8 @@ class Series : public Attributable
*/
void close();

[[nodiscard]] bool closed() const;

void visitHierarchy(HierarchyVisitor &v, bool recursive) override;

/**
Expand All @@ -804,6 +808,11 @@ OPENPMD_private
using Data_t = internal::SeriesData;
std::shared_ptr<Data_t> m_series = nullptr;

inline std::shared_ptr<Data_t> getShared()
{
return m_series;
}

inline Data_t &get()
{
if (m_series)
Expand Down
15 changes: 11 additions & 4 deletions include/openPMD/backend/Attributable.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,8 @@ namespace internal
* Instantiations for T exist for types RecordComponent,
* MeshRecordComponent, Mesh, Record, ParticleSpecies, Iteration.
*/
template <typename T>
T &makeOwning(T &self, Series);
template <typename T, typename Series_type>
T &makeOwning(T &self, Series_type);
} // namespace internal

namespace debug
Expand Down Expand Up @@ -241,8 +241,8 @@ class Attributable
friend class Writable;
friend class internal::RecordComponentData;
friend void debug::printDirty(Series const &);
template <typename T>
friend T &internal::makeOwning(T &self, Series);
template <typename T, typename Series_type>
friend T &internal::makeOwning(T &self, Series_type);
friend class StatefulSnapshotsContainer;
friend class internal::AttributableData;
friend class Snapshots;
Expand Down Expand Up @@ -457,6 +457,13 @@ class Attributable

[[nodiscard]] OpenpmdStandard openPMDStandard() const;

/** Returns the persistent immutable memory ID of the underlying Series.
*
* Useful when trying to determine which API handles refer to the same IO
* instance.
*/
[[nodiscard]] uintptr_t memoryID() const;

// clang-format off
OPENPMD_protected
// clang-format on
Expand Down
4 changes: 2 additions & 2 deletions include/openPMD/backend/BaseRecord.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,8 @@ class BaseRecord
friend class internal::BaseRecordData;
template <typename, typename, typename>
friend class internal::ScalarIterator;
template <typename T>
friend T &internal::makeOwning(T &self, Series);
template <typename T, typename Series_type>
friend T &internal::makeOwning(T &self, Series_type);
friend class internal::ScientificDefaults;

using Data_t =
Expand Down
97 changes: 56 additions & 41 deletions include/openPMD/binding/python/Pickle.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,55 @@

#include "Common.hpp"

#include <cstdint>
#include <exception>
#include <memory>
#include <shared_mutex>
#include <string>
#include <sys/types.h>
#include <tuple>
#include <vector>

namespace openPMD
{
/*
* unpickled_series, as in "plural series"; this is a cache structure for series
* objects that have been unpickled. This cache structure fixes the issue
* described in https://github.com/openPMD/openPMD-api/issues/1919.
* Idea: One single Series instance may have multiple handles referencing it.
* When pickling and unpickling these references, the underlying Series must be
* restored once only, in order to keep the reference structure. Otherwise
* something like `data = E_x[:]; series.flush();` will not work, because `E_x`
* no longer references the same Series instance as `series`.
*
* For this, the pickle structure contains as first entry the internal
* (immutable) SharedAttributable pointer address of the `Series` object
* referenced by any handle. When unpickling, this is used to restore shared
* handles in accordance with their original reference structure. The pointers
* themselves are not restored (this would not be possible), but they are used
* as equivalence classes.
*/
struct unpickled_series
{
// Cache restored object by original Series ID (i.e. internal immutable
// pointer address). IDs are not restored equivalently, but this does not
// matter. They are necessary only for figuring out which handles point to
// the same objects.
// The cached Series objects are stored as weak_ptr, since they are memory
// managed by the Python side. The C++ side just needs to check if the
// weak_ptr is still valid when handing out a new reference. If not, reopen.
std::map<uintptr_t, std::weak_ptr<Series>> m_series_by_former_id;
std::shared_mutex m_mutex;
Comment on lines +58 to +68

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An idea:
Usually when we create series in Python, the Python interpreter owns the lifetime. Now after pickle, there is a C++ object owning the lifetime.

There might be a way to attach the extra data/counter/id to the Python object dynamically and reference it (or the series that is de-serialized first) between each other in Python.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The last commit tries sth like that


auto get(uintptr_t id, std::string const &filename)
-> std::shared_ptr<Series>;
};

/*
* Cache the Series per thread.
*/
extern unpickled_series cache;

/** Helper to Pickle Attributable Classes
*
* @tparam T_Args the types in pybind11::class_ - the first type will be pickled
Expand All @@ -48,62 +90,35 @@ add_pickle(pybind11::class_<T_Args...> &cl, T_SeriesAccessor &&seriesAccessor)
{
// helper: get first class in py::class_ - that's the type we pickle
using PickledClass =
typename std::tuple_element<0, std::tuple<T_Args...> >::type;
typename std::tuple_element<0, std::tuple<T_Args...>>::type;

cl.def(
py::pickle(
// __getstate__
[](const PickledClass &a) {
// Return a tuple that fully encodes the state of the object
Attributable::MyPath const myPath = a.myPath();
return py::make_tuple(myPath.filePath(), myPath.group);
// retrieve Series even though retrieveSeries is protected...
return py::make_tuple(
a.memoryID(), myPath.filePath(), myPath.group);
},

// __setstate__
[&seriesAccessor](py::tuple const &t) {
// our tuple has exactly two elements: filePath & group
if (t.size() != 2)
// Our tuple has exactly three elements: Series ID, filePath &
// group.
// Check the documentation of unpickled_series above for
// the reasoning behind Series ID.
if (t.size() != 3)
throw std::runtime_error("Invalid state!");

std::string const filename = t[0].cast<std::string>();
auto id = t[0].cast<uintptr_t>();
std::string const filename = t[1].cast<std::string>();
std::vector<std::string> const group =
t[1].cast<std::vector<std::string> >();

/*
* Cache the Series per thread.
*/
thread_local std::optional<openPMD::Series> series;
bool re_initialize = [&]() {
try
{
return !series.has_value() ||
!series->operator bool() ||
auxiliary::replace_all(
series->myPath().filePath(), "\\", "/") !=
auxiliary::replace_all(filename, "\\", "/");
}
/*
* Better safe than sorry, if anything goes wrong because
* the Series is in a weird state, just reinitialize it.
*/
catch (...)
{
return true;
}
}();
if (re_initialize)
{
/*
* Do NOT close the old Series, it might still be active in
* terms of handed-out handles.
*/
series = std::make_optional<Series>(
filename,
Access::READ_ONLY,
"defer_iteration_parsing = true");
}
t[2].cast<std::vector<std::string>>();

return seriesAccessor(*series, group);
auto series = cache.get(id, filename);
return seriesAccessor(std::move(series), group);
}));
}
} // namespace openPMD
14 changes: 14 additions & 0 deletions src/Series.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3564,6 +3564,20 @@ void Series::close()
m_attri.reset();
}

bool Series::closed() const
{
if (!operator bool())
{
return true;
}
auto &w = writable();
if (!w.IOHandler)
{
throw error::Internal("Series went into illegal state");
}
return !w.IOHandler->has_value();
}

void Series::visitHierarchy(HierarchyVisitor &v, bool recursive)
{
if (recursive)
Expand Down
25 changes: 17 additions & 8 deletions src/backend/Attributable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,11 @@ OpenpmdStandard Attributable::openPMDStandard() const
return IOHandler()->m_standard;
}

uintptr_t Attributable::memoryID() const
{
return reinterpret_cast<uintptr_t>(&retrieveSeries().Attributable::get());
}

template <bool flush_entire_series>
void Attributable::seriesFlush_impl(internal::FlushParams const &flushParams)
{
Expand Down Expand Up @@ -625,8 +630,8 @@ void Attributable::linkHierarchy(Writable &w)

namespace internal
{
template <typename T>
T &makeOwning(T &self, Series s)
template <typename T, typename Series_type>
T &makeOwning(T &self, Series_type s)
{
/*
* `self` is a handle object such as RecordComponent or Mesh (see
Expand Down Expand Up @@ -665,11 +670,15 @@ namespace internal
return self;
}

template RecordComponent &makeOwning(RecordComponent &, Series);
template MeshRecordComponent &makeOwning(MeshRecordComponent &, Series);
template Mesh &makeOwning(Mesh &, Series);
template Record &makeOwning(Record &, Series);
template ParticleSpecies &makeOwning(ParticleSpecies &, Series);
template Iteration &makeOwning(Iteration &, Series);
template Series &makeOwning(Series &, std::shared_ptr<Series>);
template RecordComponent &
makeOwning(RecordComponent &, std::shared_ptr<Series>);
template MeshRecordComponent &
makeOwning(MeshRecordComponent &, std::shared_ptr<Series>);
template Mesh &makeOwning(Mesh &, std::shared_ptr<Series>);
template Record &makeOwning(Record &, std::shared_ptr<Series>);
template ParticleSpecies &
makeOwning(ParticleSpecies &, std::shared_ptr<Series>);
template Iteration &makeOwning(Iteration &, std::shared_ptr<Series>);
} // namespace internal
} // namespace openPMD
5 changes: 4 additions & 1 deletion src/binding/python/Attributable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -657,7 +657,10 @@ void init_Attributable(py::module &m)
.def(
"populate_missing_metadata",
&Attributable::populateMissingMetadata,
py::arg("recursive"));
py::arg("recursive"))
.def_property_readonly("memory_id", [](Attributable const &attr) {
return attr.memoryID();
});

py::bind_vector<PyAttributeKeys>(m, "Attribute_Keys");
}
6 changes: 4 additions & 2 deletions src/binding/python/Iteration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,11 @@ void init_Iteration(py::module &m)
py::keep_alive<0, 1>()));

add_pickle(
cl, [](openPMD::Series series, std::vector<std::string> const &group) {
cl,
[](std::shared_ptr<openPMD::Series> series,
std::vector<std::string> const &group) {
uint64_t const n_it = std::stoull(group.at(1));
auto res = series.iterations[n_it];
auto res = series->iterations[n_it];
return internal::makeOwning(res, std::move(series));
});

Expand Down
6 changes: 4 additions & 2 deletions src/binding/python/Mesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,11 @@ Ref.: https://github.com/openPMD/openPMD-standard/pull/193)"[1])
"set_grid_unit_SI",
py::overload_cast<double>(&Mesh::setGridUnitSI));
add_pickle(
cl, [](openPMD::Series series, std::vector<std::string> const &group) {
cl,
[](std::shared_ptr<openPMD::Series> series,
std::vector<std::string> const &group) {
uint64_t const n_it = std::stoull(group.at(1));
auto res = series.iterations[n_it].open().meshes[group.at(3)];
auto res = series->iterations[n_it].open().meshes[group.at(3)];
return internal::makeOwning(res, std::move(series));
});

Expand Down
6 changes: 4 additions & 2 deletions src/binding/python/MeshRecordComponent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,12 @@ void init_MeshRecordComponent(py::module &m)
"Relative position of the component on an element "
"(node/cell/voxel) of the mesh");
add_pickle(
cl, [](openPMD::Series series, std::vector<std::string> const &group) {
cl,
[](std::shared_ptr<openPMD::Series> series,
std::vector<std::string> const &group) {
uint64_t const n_it = std::stoull(group.at(1));
auto res =
series.iterations[n_it]
series->iterations[n_it]
.open()
.meshes[group.at(3)]
[group.size() < 5 ? MeshRecordComponent::SCALAR
Expand Down
6 changes: 4 additions & 2 deletions src/binding/python/ParticleSpecies.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,12 @@ void init_ParticleSpecies(py::module &m)
// garbage collection: return value must be freed before Series
py::keep_alive<0, 1>()));
add_pickle(
cl, [](openPMD::Series series, std::vector<std::string> const &group) {
cl,
[](std::shared_ptr<openPMD::Series> series,
std::vector<std::string> const &group) {
uint64_t const n_it = std::stoull(group.at(1));
ParticleSpecies res =
series.iterations[n_it].open().particles[group.at(3)];
series->iterations[n_it].open().particles[group.at(3)];
return internal::makeOwning(res, std::move(series));
});

Expand Down
Loading
Loading