Skip to content

Commit 11e3234

Browse files
committed
BP5: thread-safe context-bearing Get/PerformGets pipeline
NewGetContext returns a per-caller GetContext; Get(ctx, var, data, sel) and PerformGets(ctx) operate on independent queues, so concurrent reads on one engine no longer race. NewGetContext returns nullptr unless the engine is ReadRandomAccess on a reentrant Read transport (POSIX) — the default acts as a feature probe. Core: * core::GetContext polymorphic base. * Engine::NewGetContext (virtual, nullptr default), PerformGets(ctx), Get(ctx, ...) template, DoGetContextDeferred typeless virtual. * Same surface exposed through the CXX binding. BP5: * BP5GetContext (nested in BP5Deserializer) owns what was BP5Deserializer::PendingGetRequests. * QueueGet, GenerateReadRequests, FinalizeGet/Gets/DerivedGets gain ctx-bearing forms; legacy forms forward via m_DefaultGetContext. * BP5Reader overrides NewGetContext (guarded on ReadRandomAccess + cached m_HasReentrantReadTransport), PerformGets(ctx) (local only — remote throws), DoGetContextDeferred. XRootD plugin: * SubPool dual mode: first Open probes NewGetContext; if non-null, one engine is shared across concurrent users (in_use_count = refcount); otherwise legacy per-user exclusive borrow. * Single-get and batch handlers build per-request Selection inline (no Variable mutation) and use the ctx form when supported, with a single PerformGets(ctx) per batch for coalescing. Tests (testing/adios2/engine/bp/TestBPGetContextIsolation): * TwoIndependentContexts: PerformGets(ctxA) fills only ctxA, leaves ctxB at sentinel; context reusable; empty-queue PerformGets no-op. * ConcurrentGetsOnSameEngine: TSan-validated. * NonReentrantTransportRejected: fstream transport => NewGetContext returns nullptr.
1 parent a3f28e1 commit 11e3234

19 files changed

Lines changed: 781 additions & 118 deletions

File tree

bindings/CXX/adios2/cxx/Engine.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,18 @@ void Engine::PerformGets()
8989
m_Engine->PerformGets();
9090
}
9191

92+
std::unique_ptr<core::GetContext> Engine::NewGetContext()
93+
{
94+
helper::CheckForNullptr(m_Engine, "in call to Engine::NewGetContext");
95+
return m_Engine->NewGetContext();
96+
}
97+
98+
void Engine::PerformGets(core::GetContext &ctx)
99+
{
100+
helper::CheckForNullptr(m_Engine, "in call to Engine::PerformGets(GetContext&)");
101+
m_Engine->PerformGets(ctx);
102+
}
103+
92104
void Engine::LockWriterDefinitions()
93105
{
94106
helper::CheckForNullptr(m_Engine, "in call to Engine::LockWriterDefinitions");
@@ -334,6 +346,7 @@ Engine::AllStepsBlocksInfo(const VariableNT &variable) const
334346
template void Engine::Get<T>(const std::string &, std::vector<T> &, const Mode); \
335347
\
336348
template void Engine::Get<T>(Variable<T>, T *, const Selection &, const Mode); \
349+
template void Engine::Get<T>(core::GetContext &, Variable<T>, T *, const Selection &); \
337350
template void Engine::Get<T>(Variable<T>, std::vector<T> &, const Selection &, const Mode); \
338351
\
339352
template void Engine::Get<T>(Variable<T>, typename Variable<T>::Info & info, const Mode); \

bindings/CXX/adios2/cxx/Engine.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class Selection; // for selection-based Get()
2626
namespace core
2727
{
2828
class Engine; // private implementation
29+
class GetContext;
2930

3031
}
3132
/// \endcond
@@ -448,6 +449,15 @@ class Engine
448449
/** Perform all Get calls in Deferred mode up to this point */
449450
void PerformGets();
450451

452+
// Context-bearing Get / PerformGets — thread-safe pipeline (BP5 reader only).
453+
// Returns nullptr if the engine doesn't support concurrent contexts.
454+
std::unique_ptr<core::GetContext> NewGetContext();
455+
456+
template <class T>
457+
void Get(core::GetContext &ctx, Variable<T> variable, T *data, const Selection &selection);
458+
459+
void PerformGets(core::GetContext &ctx);
460+
451461
/**
452462
* Ends current step, by default calls PerformsPut/Get internally
453463
* For most engines, this is an MPI collective function.

bindings/CXX/adios2/cxx/Engine.tcc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,17 @@ void Engine::Get(Variable<T> variable, std::vector<T> &dataV, const Selection &s
305305
selection.GetCoreSelection(), launch);
306306
}
307307

308+
template <class T>
309+
void Engine::Get(core::GetContext &ctx, Variable<T> variable, T *data, const Selection &selection)
310+
{
311+
using IOType = typename TypeInfo<T>::IOType;
312+
adios2::helper::CheckForNullptr(m_Engine, "in call to Engine::Get(GetContext &, ...)");
313+
adios2::helper::CheckForNullptr(variable.m_Variable,
314+
"for variable in call to Engine::Get(GetContext &, ...)");
315+
m_Engine->Get(ctx, *variable.m_Variable, reinterpret_cast<IOType *>(data),
316+
selection.GetCoreSelection());
317+
}
318+
308319
} // end namespace adios2
309320

310321
#endif /* ADIOS2_BINDINGS_CXX_CXX_ENGINE_TCC_ */

docs/user_guide/source/components/engine.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,11 @@ PerformGets
472472

473473
Executes all pending ``Get`` calls in deferred mode.
474474

475+
A second overload, ``PerformGets(GetContext&)``, drains only the requests
476+
queued on the supplied context, supporting concurrent ``Get`` pipelines on
477+
a single engine. See `Thread-Safe Concurrent Reads with GetContext`_ in
478+
the Selection chapter.
479+
475480

476481
Engine usage example
477482
--------------------

docs/user_guide/source/components/selection.rst

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,46 @@ Selection-based ``Get()`` is currently supported by the **BP5** and **SST**
183183
(with BP5 marshalling) engines. Other engines will throw a runtime error if a
184184
``Selection`` is passed to ``Get()``.
185185

186+
Thread-Safe Concurrent Reads with GetContext
187+
--------------------------------------------
188+
189+
The Selection-based ``Get()`` shown above shares per-engine queue state with
190+
every other ``Get()`` call on the same engine, so concurrent calls from
191+
multiple threads are not safe. A second overload takes an explicit
192+
``GetContext`` so each thread (or each independent reader) can queue and drain
193+
``Get()`` requests on its own:
194+
195+
.. code-block:: c++
196+
197+
auto ctx = engine.NewGetContext();
198+
if (!ctx) {
199+
// Engine does not support concurrent contexts; fall back to the
200+
// single-threaded Selection-based Get above.
201+
}
202+
203+
auto sel = adios2::Selection::BoundingBox({0, 0}, {10, 20}).WithSteps(0, 1);
204+
engine.Get(*ctx, var, data, sel);
205+
engine.PerformGets(*ctx);
206+
207+
Each ``GetContext`` owns its own queue of pending requests, so
208+
``PerformGets(ctxA)`` drains only ``ctxA`` and leaves any requests queued on
209+
``ctxB`` untouched. A context is reusable after ``PerformGets``; the queue is
210+
cleared on the success path. Calls in ctx-form are always deferred — there is
211+
no ``Mode::Sync`` variant.
212+
213+
``NewGetContext()`` is a feature probe: it returns ``nullptr`` if the engine
214+
does not support concurrent contexts. Currently only the **BP5** reader
215+
supports them, and only when:
216+
217+
- the engine was opened in ``Mode::ReadRandomAccess`` (no step lifecycle to
218+
race on), and
219+
- the underlying file transport is reentrant for ``Read`` (the POSIX
220+
transport is; ``fstream`` and others are not).
221+
222+
The legacy ``Selection``-less ``Get()``/``PerformGets()`` API and the
223+
single-threaded Selection-based ``Get()`` are unchanged; the ctx-form is an
224+
additional API for callers that need concurrency.
225+
186226
Compatibility
187227
-------------
188228

docs/user_guide/source/introduction/whatsnew.rst

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,30 @@
22
What's new in Development
33
==============================
44

5+
Thread-Safe Get with GetContext
6+
-------------------------------
7+
8+
A new ``GetContext`` API extends the Selection-based ``Get()`` to support
9+
concurrent read pipelines on a single engine. Each caller allocates its own
10+
``GetContext`` via ``Engine::NewGetContext()`` and uses it as the first
11+
argument to ``Get()`` and ``PerformGets()``; per-context queues mean two
12+
threads can drive independent ``Get`` batches against the same open engine
13+
without racing.
14+
15+
.. code-block:: c++
16+
17+
auto ctx = engine.NewGetContext();
18+
if (ctx) {
19+
engine.Get(*ctx, var, data, adios2::Selection::BoundingBox(start, count));
20+
engine.PerformGets(*ctx);
21+
}
22+
23+
``NewGetContext()`` returns ``nullptr`` when the engine does not support
24+
concurrent contexts; callers feature-probe with the null check. Currently
25+
only the **BP5** reader supports them, and only when opened in
26+
``Mode::ReadRandomAccess`` with a reentrant file transport (POSIX). See the
27+
Selection chapter for full details.
28+
529
SST Mercury Data Plane
630
-----------------------
731

source/adios2/core/Engine.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,14 @@ void Engine::PerformPuts() { ThrowUp("PerformPuts"); }
8383
void Engine::PerformGets() { ThrowUp("PerformGets"); }
8484
void Engine::PerformDataWrite() { return; }
8585

86+
// Feature probe: null => engine doesn't support the ctx-form pipeline.
87+
std::unique_ptr<GetContext> Engine::NewGetContext() { return nullptr; }
88+
void Engine::PerformGets(GetContext &) { ThrowUp("PerformGets(GetContext&)"); }
89+
void Engine::DoGetContextDeferred(GetContext &, VariableBase &, void *, const Selection &)
90+
{
91+
ThrowUp("DoGetContextDeferred");
92+
}
93+
8694
void Engine::Close(const int transportIndex)
8795
{
8896
if (m_IsOpen)
@@ -375,6 +383,8 @@ std::vector<VariableStruct::BPInfo> Engine::BlocksInfoStruct(const VariableStruc
375383
template void Engine::Get<T>(Variable<T> &, T *, const Selection &, const Mode); \
376384
template void Engine::Get<T>(Variable<T> &, std::vector<T> &, const Selection &, const Mode); \
377385
\
386+
template void Engine::Get<T>(GetContext &, Variable<T> &, T *, const Selection &); \
387+
\
378388
template typename Variable<T>::BPInfo *Engine::Get<T>(Variable<T> &, const Mode); \
379389
template typename Variable<T>::BPInfo *Engine::Get<T>(const std::string &, const Mode); \
380390
\

source/adios2/core/Engine.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "adios2/common/ADIOSMacros.h"
2424
#include "adios2/common/ADIOSTypes.h"
2525
#include "adios2/common/Selection.h"
26+
#include "adios2/core/GetContext.h"
2627
#include "adios2/core/IO.h"
2728
#include "adios2/core/Variable.h"
2829
#include "adios2/core/VariableStruct.h"
@@ -387,6 +388,15 @@ class Engine
387388
void Get(Variable<T> &variable, std::vector<T> &dataV, const Selection &selection,
388389
const Mode launch = Mode::Deferred);
389390

391+
// Context-bearing thread-safe Get pipeline (BP5 only).
392+
// NewGetContext returns nullptr if the engine doesn't support it.
393+
virtual std::unique_ptr<GetContext> NewGetContext();
394+
395+
template <class T>
396+
void Get(GetContext &ctx, Variable<T> &variable, T *data, const Selection &selection);
397+
398+
virtual void PerformGets(GetContext &ctx);
399+
390400
/**
391401
* Reader application indicates that no more data will be read from the
392402
* current stream before advancing.
@@ -610,6 +620,8 @@ class Engine
610620
ADIOS2_FOREACH_STDTYPE_1ARG(declare_type)
611621
#undef declare_type
612622

623+
virtual void DoGetContextDeferred(GetContext &, VariableBase &, void *, const Selection &);
624+
613625
virtual void DoGetStructSync(VariableStruct &, void *);
614626
virtual void DoGetStructDeferred(VariableStruct &, void *);
615627

source/adios2/core/Engine.tcc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,14 @@ void Engine::Get(Variable<T> &variable, T *data, const Selection &selection, con
179179
}
180180
}
181181

182+
template <class T>
183+
void Engine::Get(GetContext &ctx, Variable<T> &variable, T *data, const Selection &selection)
184+
{
185+
CommonChecks(variable, data, {Mode::Read, Mode::ReadRandomAccess},
186+
"in call to Get(GetContext &, Variable, T*, Selection)");
187+
DoGetContextDeferred(ctx, variable, data, selection);
188+
}
189+
182190
template <class T>
183191
void Engine::Get(Variable<T> &variable, std::vector<T> &dataV, const Selection &selection,
184192
const Mode launch)

source/adios2/core/GetContext.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2026 Oak Ridge National Laboratory and Contributors
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#ifndef ADIOS2_CORE_GETCONTEXT_H_
8+
#define ADIOS2_CORE_GETCONTEXT_H_
9+
10+
namespace adios2
11+
{
12+
namespace core
13+
{
14+
15+
// Opaque per-caller state for thread-safe Get/PerformGets.
16+
class GetContext
17+
{
18+
public:
19+
virtual ~GetContext() = default;
20+
};
21+
22+
} // end namespace core
23+
} // end namespace adios2
24+
25+
#endif /* ADIOS2_CORE_GETCONTEXT_H_ */

0 commit comments

Comments
 (0)