Repository navigation
Introduce more flexible storeChunk() syntax, use to add ADIOS2 memory selection - #1620
Open
franzpoeschel wants to merge 92 commits into
Open
franzpoeschel wants to merge 92 commits into
franzpoeschel wants to merge 92 commits into
Conversation
franzpoeschel
force-pushed
the
adios2-memory-selection
branch
2 times, most recently
from
May 22, 2024 11:44
727cbbc to
ddcdfc9
Compare
ax3l
self-requested a review
May 31, 2024 17:50
franzpoeschel
force-pushed
the
adios2-memory-selection
branch
from
June 7, 2024 12:41
c05d535 to
ba7c582
Compare
franzpoeschel
force-pushed
the
adios2-memory-selection
branch
from
June 24, 2024 12:47
ba7c582 to
d7eb891
Compare
franzpoeschel
force-pushed
the
adios2-memory-selection
branch
2 times, most recently
from
June 25, 2024 13:56
332932f to
efb0876
Compare
franzpoeschel
force-pushed
the
adios2-memory-selection
branch
from
June 26, 2024 11:48
efb0876 to
39e3d71
Compare
franzpoeschel
force-pushed
the
adios2-memory-selection
branch
4 times, most recently
from
July 22, 2024 10:21
47d497b to
b2bc355
Compare
franzpoeschel
force-pushed
the
adios2-memory-selection
branch
from
July 23, 2024 14:05
b699520 to
3430b6d
Compare
franzpoeschel
force-pushed
the
adios2-memory-selection
branch
from
September 3, 2024 13:06
9b5acff to
bd7b378
Compare
franzpoeschel
force-pushed
the
adios2-memory-selection
branch
from
November 15, 2024 14:45
208c381 to
e51e635
Compare
franzpoeschel
force-pushed
the
adios2-memory-selection
branch
2 times, most recently
from
December 17, 2024 11:09
c8be94b to
8e455b4
Compare
franzpoeschel
force-pushed
the
adios2-memory-selection
branch
from
February 21, 2025 12:06
8e455b4 to
430fe3b
Compare
franzpoeschel
force-pushed
the
adios2-memory-selection
branch
from
March 26, 2025 14:33
430fe3b to
e98c840
Compare
franzpoeschel
force-pushed
the
adios2-memory-selection
branch
2 times, most recently
from
April 4, 2025 08:32
190674f to
e04f76d
Compare
franzpoeschel
force-pushed
the
adios2-memory-selection
branch
from
April 22, 2025 09:09
b99bb95 to
d05e029
Compare
Split the joined-dimension buffer-API regression test out of SerialIOTest.cpp into test/Files_SerialIO/joined_dim_buffer_api.cpp and register it in CMakeLists.txt, matching the convention of the other per-issue SerialIO test files.
A joined dimension's extent is only known once all writers have flushed and
the data is read back (close + reopen). During the write session the extent
is Dataset::JOINED_DIMENSION (max), so loading a chunk of a joined array is
not a meaningful operation: the total size is unknown and the streaming
engine cannot read the data back mid-write. After a reopen the extent is
concrete, joinedDimension() is nullopt, and the load goes through the normal
non-joined path (the case exercised by the joined_dim test).
The previous change made loadChunk_impl mirror the store path by accepting an
empty offset for joined arrays, which only validated the in-session case and
then queued a READ_DATASET for 2^64 elements. Reject joined arrays in
loadChunk_impl with a clear error instead; the store path (append) is
unchanged.
Update the joined_dim_buffer_api regression test: store with an empty {} and
a {0} offset still passes the frontend checks, while every buffer-based load
overload now throws error::WrongAPIUsage.
for more information, see https://pre-commit.ci
Co-authored-by: Axel Huebl <axel.huebl@plasma.ninja>
Rejecting a memory selection inside the backend's writeDataset() throws
while an IO task is being flushed. AbstractIOHandlerImpl::flush() reacts to
an exception in an IO task by clearing the whole IO queue and rethrowing
("Clearing IO queue and passing on the exception"), so every other pending
chunk and the file's root attributes are dropped. The subsequent flush()
and close() then report success, but the file can no longer be read back
(AttributeNotFound: openPMD) in HDF5 and JSON.
Whether a backend supports non-contiguous memory selections is known when
the chunk is enqueued, so check it in RecordComponent::storeChunk_impl()
and throw error::OperationUnsupportedInBackend there, before anything is
queued. Add AbstractIOHandler::supportsMemorySelection(), defaulting to
false and overridden to true only in ADIOS2IOHandler. The backend-side
throws stay as a safety net.
Add a regression test that stores a valid chunk, attempts a rejected
memory selection on another component, and verifies that the Series stays
usable and that the valid chunk and a root attribute survive the roundtrip
in HDF5 and JSON. Without the early check the test fails at flush time.
A memory selection could not be reset before ADIOS2 v2.10.1 (the reset capability was added upstream in v2.11.0 and backported to 2.10.1). On older versions a stale memory selection silently leaked into every following store operation of the same variable, producing wrong data or out-of-bounds reads. This affected later storeChunk() calls, later steps and the unique_ptr path. Instead of applying a workaround, report ADIOS2 as not supporting memory selections in that case via AbstractIOHandler::supportsMemorySelection(), so the frontend rejects them up front, and keep the backend-side check in verifyDataset() as a safety net. Both reuse the existing CanTheMemorySelectionBeReset trait. Remove the now-obsolete one-time warning and its bookkeeping. Adapt available_chunks_test and memory_selection_rejected_before_flush to use an equivalent contiguous buffer / expectation where memory selections are unsupported, and add a dedicated memory_selection_old_adios2 test. Pin the ADIOS2 v2.10 CI job to 2.10.0 to exercise the rejection path.
store() moves the buffer out of the (copyable, shared) configuration, and load() reads from it, but nothing prevented a second use of the same configuration or of a copy sharing the buffer. A second store() then enqueued an empty WriteBuffer and crashed when the backend dereferenced the moved-from std::any. Track consumption in a flag shared between copies of the configuration and report reuse as error::WrongAPIUsage.
DeferredComputation's move-assignment was defaulted, so assigning over a handle that still held a valid task silently overwrote (and dropped) that task. For the handles returned by store()/load() this means a data operation, and possibly a collective flush, would never run and no error would be reported. Execute the target's pending task before overwriting it, mirroring the destructor behavior. Add a regression test.
The legacy storeChunk() / loadChunk() overloads expand an offset of {0}
and an extent of {-1u} to the dataset dimensionality, i.e. they mean
"default / full selection". The chaining API took these values
literally, so migrating storeChunk(data, {0}, {-1u}) to
.offset({0}).extent({-1u}) changed the meaning and could fail with a
dimensionality error on multi-dimensional datasets.
Reset the stored offset/extent when the sentinel is passed, restoring the
legacy behavior, and document it on offset()/extent(). Add a regression
test.
Merge the two memory-selection tests into a single memory_selection.cpp and the two DeferredComputation tests into one future_test with sections. Rename container_buffer_size_1d_downsizes.cpp to container_buffer_size.cpp since it covers more than the 1-D downsize case.
franzpoeschel
force-pushed
the
adios2-memory-selection
branch
from
October 21, 2026 09:49
1f77b1e to
3169a99
Compare
This is an MPI hazard
- Document buffer lifetime and enqueue timing for the deferred load/store API, and align destructor/flush documentation with the no-execute-on-destruction behavior. - Reject joined-dimension loads before allocating a buffer of JOINED_DIMENSION elements. - Do not throw from the UniquePtrWithLambda deleter: deleting a unique_ptr is noexcept. Warn and skip deletion on disassociation.
franzpoeschel
force-pushed
the
adios2-memory-selection
branch
from
October 27, 2026 14:44
02aed2c to
e24785b
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
We currently don't support memory selections yet, e.g. when you have a 2D memory buffer but want to write only an arbitrary block from it. That block is then non-contiguous, but ADIOS2 has so-called memory selections to support this.
We currently don't have an API that would be able to expose this (except in Python where the native Python buffer protocol is powerful enough to express this; we currently throw an error when detecting this
strides in selection are inefficient, not implemented!). Since theloadChunk()/storeChunk()API is somewhat convoluted by now anyway, I didn't want to add even further overloads.New API Design
The new API is centered around
RecordComponent::prepareLoadStore(), which returns a configuration object that can be chained with various methods before executing the actual load/store operation.Methods return an
auxiliary::DeferredComputation<T>object that encapsulates the operation without immediately executing it. The computation is only performed whenget()(oroperator()()) is called on the returned object:Configuration Methods
The configuration chain supports:
offset(Offset)- Set the offset within the dataset (optional)extent(Extent)- Set the extent within the dataset (optional)memorySelection(MemorySelection)- Set memory selection for non-contiguous buffers (only available afterwithSharedPtr(),withRawPtr(), orwithContiguousContainer())unsafeNoAutomaticFlush(): The returnedDeferredComputationobject will be a no-op object. Buffers will still be returned, but they might not yet be written/read until manually flushing.Buffer Specification Methods
If not specifying a buffer, the following allocating load/store operations are available:
storeSpan<T>()- ReturnsDynamicMemoryView<T>(a span-like view)load<T>()- ReturnsDeferredComputation<std::shared_ptr<T>>loadVariant()- ReturnsDeferredComputation<variant>for type-erased loadingload<T>(policy)- Returnsstd::shared_ptr<T>>Alternatively, you may specify a buffer using one of:
withSharedPtr(std::shared_ptr<T>)- For shared pointer managed bufferswithUniquePtr(UniquePtrWithLambda<T>)- For unique pointer with custom deleterwithRawPtr(T*)- For raw pointer bufferswithContiguousContainer(Container&)- For contiguous containers (automatically deduces size if extent is not set)The following operations are only available after specifying a buffer. Template parameters are no longer needed as the type is given through the buffer type:
store()/load()- ReturnsDeferredComputation<void>(Load operations are only available after specifying a writable buffer, i.e. raw/shared pointer or contiguous container of non-const type)
ADIOS2 Memory Selection Support
This PR adds support for ADIOS2 memory selections, allowing writing from non-contiguous memory blocks:
Note: Memory selection support depends on ADIOS2 version. For ADIOS2 versions that cannot reset memory selections (PR ornladios/ADIOS2#4169), a warning is displayed. The API gracefully handles both cases.
Migration from Old API
The old
loadChunk()andstoreChunk()methods are now fully implemented using the new API internally, providing full backward compatibility while benefiting from the new implementation. Existing code continues to work without changes.Examples of Old to New Migration
Old:
New:
Old:
New:
E_y.prepareLoadStore() .offset({0, 5}) .extent({1, 5}) .withContiguousContainer(data) .store();Design Considerations & Open Questions
Memory Selection Limitations
prepareLoadStore())Files Changed
include/openPMD/LoadStoreChunk.hpp- New header with the main APIinclude/openPMD/LoadStoreChunk.tpp- Template implementationsinclude/openPMD/auxiliary/Future.hpp- NewDeferredComputationwrapperinclude/openPMD/Dataset.hpp- AddedMemorySelectionstructinclude/openPMD/RecordComponent.hpp- Integration of new APIsrc/LoadStoreChunk.cpp- Non-template implementationssrc/auxiliary/Future.cpp- DeferredComputation implementationTODO
.noop_future()option to disable future return types for cases where you don't need to track completionRelated
TODO: