Skip to content

Commit 74fdc47

Browse files
Pickle API: Avoid static Series hack, allow multiple Series (#1633)
* Defer iteration parsing * Add first attempt of createOwningCopy * Continue... * seems to work?? * Reenable the Python tests * Add pickling for Iteration and Series too * Add pickle support for Series and Iteration * Document new internal function
1 parent ff6cf66 commit 74fdc47

15 files changed

Lines changed: 166 additions & 20 deletions

File tree

include/openPMD/Iteration.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ class Iteration : public Attributable
131131
friend class WriteIterations;
132132
friend class SeriesIterator;
133133
friend class internal::AttributableData;
134+
template <typename T>
135+
friend T &internal::makeOwning(T &self, Series);
134136

135137
public:
136138
Iteration(Iteration const &) = default;
@@ -258,6 +260,11 @@ class Iteration : public Attributable
258260
return *m_iterationData;
259261
}
260262

263+
inline std::shared_ptr<Data_t> getShared()
264+
{
265+
return m_iterationData;
266+
}
267+
261268
inline void setData(std::shared_ptr<Data_t> data)
262269
{
263270
m_iterationData = std::move(data);

include/openPMD/ParticleSpecies.hpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ class ParticleSpecies : public Container<Record>
3535
friend class Container<ParticleSpecies>;
3636
friend class Container<Record>;
3737
friend class Iteration;
38+
template <typename T>
39+
friend T &internal::makeOwning(T &self, Series);
3840

3941
public:
4042
ParticlePatches particlePatches;
@@ -44,6 +46,13 @@ class ParticleSpecies : public Container<Record>
4446

4547
void read();
4648
void flush(std::string const &, internal::FlushParams const &) override;
49+
50+
using Data_t = Container<Record>::ContainerData;
51+
52+
inline std::shared_ptr<Data_t> getShared()
53+
{
54+
return m_containerData;
55+
}
4756
};
4857

4958
namespace traits

include/openPMD/RecordComponent.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "openPMD/auxiliary/ShareRaw.hpp"
2626
#include "openPMD/auxiliary/TypeTraits.hpp"
2727
#include "openPMD/auxiliary/UniquePtr.hpp"
28+
#include "openPMD/backend/Attributable.hpp"
2829
#include "openPMD/backend/BaseRecordComponent.hpp"
2930

3031
#include <array>
@@ -132,6 +133,8 @@ class RecordComponent : public BaseRecordComponent
132133
friend class DynamicMemoryView;
133134
friend class internal::RecordComponentData;
134135
friend class MeshRecordComponent;
136+
template <typename T>
137+
friend T &internal::makeOwning(T &self, Series);
135138

136139
public:
137140
enum class Allocation
@@ -523,6 +526,11 @@ OPENPMD_protected
523526
return *m_recordComponentData;
524527
}
525528

529+
inline std::shared_ptr<Data_t> getShared()
530+
{
531+
return m_recordComponentData;
532+
}
533+
526534
inline void setData(std::shared_ptr<internal::RecordComponentData> data)
527535
{
528536
m_recordComponentData = std::move(data);

include/openPMD/backend/Attributable.hpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,21 @@ namespace internal
124124
class BaseRecordData;
125125

126126
class RecordComponentData;
127+
128+
/*
129+
* Internal function to turn a handle into an owning handle that will keep
130+
* not only itself, but the entire Series alive. Works by hiding a copy of
131+
* the Series into the destructor lambda of the internal shared pointer. The
132+
* returned handle is entirely safe to use in just the same ways as a normal
133+
* handle, just the surrounding Series needs not be kept alive any more
134+
* since it is stored within the handle. By storing the Series in the
135+
* handle, not in the actual data, reference cycles are avoided.
136+
*
137+
* Instantiations for T exist for types RecordComponent,
138+
* MeshRecordComponent, Mesh, Record, ParticleSpecies, Iteration.
139+
*/
140+
template <typename T>
141+
T &makeOwning(T &self, Series);
127142
} // namespace internal
128143

129144
namespace debug
@@ -157,6 +172,8 @@ class Attributable
157172
friend class WriteIterations;
158173
friend class internal::RecordComponentData;
159174
friend void debug::printDirty(Series const &);
175+
template <typename T>
176+
friend T &internal::makeOwning(T &self, Series);
160177

161178
protected:
162179
// tag for internal constructor

include/openPMD/backend/BaseRecord.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,8 @@ class BaseRecord
237237
friend class internal::BaseRecordData;
238238
template <typename, typename, typename>
239239
friend class internal::ScalarIterator;
240+
template <typename T>
241+
friend T &internal::makeOwning(T &self, Series);
240242

241243
using Data_t =
242244
internal::BaseRecordData<T_elem, typename T_RecordComponent::Data_t>;
@@ -256,6 +258,11 @@ class BaseRecord
256258
return *m_baseRecordData;
257259
}
258260

261+
inline std::shared_ptr<Data_t> getShared()
262+
{
263+
return m_baseRecordData;
264+
}
265+
259266
BaseRecord();
260267

261268
protected:

include/openPMD/binding/python/Pickle.hpp

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,9 @@ add_pickle(pybind11::class_<T_Args...> &cl, T_SeriesAccessor &&seriesAccessor)
6767
std::vector<std::string> const group =
6868
t[1].cast<std::vector<std::string> >();
6969

70-
// Create a new openPMD Series and keep it alive.
71-
// This is a big hack for now, but it works for our use
72-
// case, which is spinning up remote serial read series
73-
// for DASK.
74-
static auto series = openPMD::Series(filename, Access::READ_ONLY);
75-
return seriesAccessor(series, group);
70+
openPMD::Series series(
71+
filename, Access::READ_ONLY, "defer_iteration_parsing = true");
72+
return seriesAccessor(std::move(series), group);
7673
}));
7774
}
7875
} // namespace openPMD

src/backend/Attributable.cpp

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@
2020
*/
2121
#include "openPMD/backend/Attributable.hpp"
2222
#include "openPMD/Iteration.hpp"
23+
#include "openPMD/ParticleSpecies.hpp"
24+
#include "openPMD/RecordComponent.hpp"
2325
#include "openPMD/Series.hpp"
2426
#include "openPMD/auxiliary/DerefDynamicCast.hpp"
2527
#include "openPMD/auxiliary/StringManip.hpp"
28+
#include "openPMD/backend/Attribute.hpp"
2629

2730
#include <algorithm>
2831
#include <complex>
@@ -505,4 +508,53 @@ void Attributable::linkHierarchy(Writable &w)
505508
writable().parent = &w;
506509
setDirty(true);
507510
}
511+
512+
namespace internal
513+
{
514+
template <typename T>
515+
T &makeOwning(T &self, Series s)
516+
{
517+
/*
518+
* `self` is a handle object such as RecordComponent or Mesh (see
519+
* instantiations below).
520+
* These objects don't normally keep alive the Series, i.e. as soon as
521+
* the Series is destroyed, the handle becomes invalid.
522+
* This function modifies the handle such that it actually keeps the
523+
* Series alive and behaves otherwise identically.
524+
* First, get the internal shared pointer of the handle.
525+
*/
526+
std::shared_ptr<typename T::Data_t> data_ptr = self.T::getShared();
527+
auto raw_ptr = data_ptr.get();
528+
/*
529+
* Now, create a new shared pointer pointing to the same address as the
530+
* actual pointer and replace the old internal shared pointer by the new
531+
* one.
532+
*/
533+
self.setData(std::shared_ptr<typename T::Data_t>{
534+
raw_ptr,
535+
/*
536+
* Here comes the main trick.
537+
* The new shared pointer stores (and thus keeps alive) two items
538+
* via lambda capture in its destructor:
539+
* 1. The old shared pointer.
540+
* 2. The Series.
541+
* It's important to notice that these two items are only stored
542+
* within the newly created handle, and not internally within the
543+
* actual openPMD object model. This means that no reference cycles
544+
* can occur.
545+
*/
546+
[s_lambda = std::move(s),
547+
data_ptr_lambda = std::move(data_ptr)](auto const *) {
548+
/* no-op, the lambda captures simply go out of scope */
549+
}});
550+
return self;
551+
}
552+
553+
template RecordComponent &makeOwning(RecordComponent &, Series);
554+
template MeshRecordComponent &makeOwning(MeshRecordComponent &, Series);
555+
template Mesh &makeOwning(Mesh &, Series);
556+
template Record &makeOwning(Record &, Series);
557+
template ParticleSpecies &makeOwning(ParticleSpecies &, Series);
558+
template Iteration &makeOwning(Iteration &, Series);
559+
} // namespace internal
508560
} // namespace openPMD

src/binding/python/Iteration.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "openPMD/backend/Attributable.hpp"
2424
#include "openPMD/binding/python/Common.hpp"
2525
#include "openPMD/binding/python/Container.H"
26+
#include "openPMD/binding/python/Pickle.hpp"
2627

2728
#include <ios>
2829
#include <sstream>
@@ -33,6 +34,13 @@ void init_Iteration(py::module &m)
3334
auto py_it_cont = declare_container<PyIterationContainer, Attributable>(
3435
m, "Iteration_Container");
3536

37+
// `clang-format on/off` doesn't help here.
38+
// Writing this without a macro would lead to a huge diff due to
39+
// clang-format.
40+
#define OPENPMD_AVOID_CLANG_FORMAT auto cl =
41+
OPENPMD_AVOID_CLANG_FORMAT
42+
#undef OPENPMD_AVOID_CLANG_FORMAT
43+
3644
py::class_<Iteration, Attributable>(m, "Iteration")
3745
.def(py::init<Iteration const &>())
3846

@@ -99,5 +107,12 @@ void init_Iteration(py::module &m)
99107
// garbage collection: return value must be freed before Iteration
100108
py::keep_alive<1, 0>());
101109

110+
add_pickle(
111+
cl, [](openPMD::Series series, std::vector<std::string> const &group) {
112+
uint64_t const n_it = std::stoull(group.at(1));
113+
auto &res = series.iterations[n_it];
114+
return internal::makeOwning(res, std::move(series));
115+
});
116+
102117
finalize_container<PyIterationContainer>(py_it_cont);
103118
}

src/binding/python/Mesh.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,10 @@ void init_Mesh(py::module &m)
115115
.def("set_grid_global_offset", &Mesh::setGridGlobalOffset)
116116
.def("set_grid_unit_SI", &Mesh::setGridUnitSI);
117117
add_pickle(
118-
cl, [](openPMD::Series &series, std::vector<std::string> const &group) {
118+
cl, [](openPMD::Series series, std::vector<std::string> const &group) {
119119
uint64_t const n_it = std::stoull(group.at(1));
120-
return series.iterations[n_it].meshes[group.at(3)];
120+
auto &res = series.iterations[n_it].open().meshes[group.at(3)];
121+
return internal::makeOwning(res, std::move(series));
121122
});
122123

123124
finalize_container<PyMeshContainer>(py_m_cont);

src/binding/python/MeshRecordComponent.cpp

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,15 @@ void init_MeshRecordComponent(py::module &m)
8282
"Relative position of the component on an element "
8383
"(node/cell/voxel) of the mesh");
8484
add_pickle(
85-
cl, [](openPMD::Series &series, std::vector<std::string> const &group) {
85+
cl, [](openPMD::Series series, std::vector<std::string> const &group) {
8686
uint64_t const n_it = std::stoull(group.at(1));
87-
return series.iterations[n_it]
88-
.meshes[group.at(3)]
89-
[group.size() < 5 ? MeshRecordComponent::SCALAR
90-
: group.at(4)];
87+
auto &res =
88+
series.iterations[n_it]
89+
.open()
90+
.meshes[group.at(3)]
91+
[group.size() < 5 ? MeshRecordComponent::SCALAR
92+
: group.at(4)];
93+
return internal::makeOwning(res, std::move(series));
9194
});
9295

9396
finalize_container<PyMeshRecordComponentContainer>(py_mrc_cnt);

0 commit comments

Comments
 (0)