Skip to content

Commit da538c8

Browse files
author
Alan M. Carroll
committed
Add named objects to context.
1 parent eb4d5e4 commit da538c8

File tree

5 files changed

+133
-97
lines changed

5 files changed

+133
-97
lines changed

doc/dev/memory-management.en.rst

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,38 +11,31 @@
1111
Memory Mangement
1212
****************
1313

14-
While most elements do not require additional memory, this is not always the case. If elements in a
15-
constellation need to share
14+
While most elements do not require additional memory, this is not always the case. If additional memory is needed to
15+
perform a task, this should be allocated in |TxB| memory that has a transaction based lifetime for faster allocation and
16+
automated cleanup. If elements in a constellation need to share data, the data must be placed in |TxB| managed memory to
17+
avoid corrouption or crashes on configuration reload.
1618

17-
18-
|TxB| provides
19-
a number of mechanisms for allocating memory efficiently in the context of handling transactions.
20-
21-
The primary reason to use |TxB| memory instead of :code:`malloc` is memory lifetime management.
22-
Using the internal mechanisms memory that has exactly the lifetime of a configuration or a
23-
transaction is straightforward. In many cases the code can simply allocate without concern of leaks.
24-
In addition this restriction also makes the allocation much faster than with :code:`malloc`.
25-
26-
Another reason is avoiding race conditions and collisions. Because configurations can be dynamically
27-
reloaded, pointers to configuration local memory that are stored statically can go bad or become
28-
corrupted during reload as the pointers are moved while a configuration is still in use. Use of the
29-
internal mechanisms ties the memory to a particular configuration or transaction context, avoiding
30-
this problem.
19+
|TxB| provides a number of mechanisms for allocating memory efficiently for configuration and transaction
20+
lifetimes.
3121

3222
=====================
3323
Configuration Storage
3424
=====================
3525

3626
The root of memomry management is the configuration instance, represented by an instance of
37-
:txb:`Config`. While allocating memory for its own uses, directives can request memmory in the
38-
configuration to be reserved for that directive. This is per directive class, not per directive
39-
instance. Access to the memory is via the inherited pointer :txb:`Directive::_rtti`.
40-
41-
When the directive is defined with :txb:`Config::define` there is an options structure of type
42-
:txb:`Directive::Options` that can specify the amount of reserved storage in bytes in the
43-
:code:`_cfg_store_required` member. If the directive is used, the specified amount of storage is
44-
reserved. It is accessed by :code:`_rtti->_cfg_store`. This is of type :code:`MemSpan<void>` and
45-
specifies the reserved memory.
27+
:txb:`Config`. This contains a memory arena with the same lifetime as the configuration and is used to store
28+
configuration related data. Elements can also allocate memory in this arena. Such memory has two useful
29+
properties.
30+
31+
* The memory lifetime is the same as the configuration instance.
32+
* The memory is accessible from any element.
33+
34+
Raw configuration memory can be allocated as described later but this leaves the problem of locating that memory
35+
again, with the self referential problem of having to know the location of the memory in the configuration to
36+
find the location. The method :txb:`Config::obtain_named_object` provides a bootstrapping object in configuration.
37+
This finds or creates an instance of a specific type in configuration memory and associates it with a name. The
38+
method :txb:`Config::named_object` can be used later to find that object.
4639

4740
The most challenging aspect is finding configuration allocated memory later. If the configuration
4841
based memory is used per directive instance, this is not a problem - a span can be stored in the

plugin/include/txn_box/Config.h

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -117,15 +117,17 @@ class Config
117117
~ActiveCaptureScope();
118118
};
119119
friend ActiveCaptureScope;
120-
ActiveCaptureScope
121-
capture_scope(unsigned count, unsigned line_no)
122-
{
123-
ActiveCaptureScope scope(*this);
124-
_active_capture._count = count;
125-
_active_capture._line = line_no;
126-
_active_capture._ref_p = false;
127-
return scope;
128-
}
120+
121+
/** Preserve the current capture group state.
122+
*
123+
* @param count Number of capture groups in the new state.
124+
* @param line_no Configuration line number that required the change.
125+
* @return A cached scope object.
126+
*
127+
* The current state is preserved in the returned object which, when destructed,
128+
* restores the previous state.
129+
*/
130+
ActiveCaptureScope capture_scope(unsigned count, unsigned line_no);
129131

130132
/** Local extractor table.
131133
*
@@ -389,15 +391,13 @@ class Config
389391
* This is used when a directive needs to be available under an alternative name. All of the arguments
390392
* are pulled from standard class members except the key (directive name).
391393
*/
392-
template <typename D>
393-
static swoc::Errata
394-
define(swoc::TextView name);
394+
template <typename D> static swoc::Errata define(swoc::TextView name);
395395

396396
/** Allocate storage in @a this.
397397
*
398398
* @param n Size in bytes.
399399
* @param align Memory alignment.
400-
* @return The allocated span.
400+
* @return The allocated memory.
401401
*/
402402
swoc::MemSpan<void> allocate_cfg_storage(size_t n, size_t align = 1);
403403

plugin/include/txn_box/Context.h

Lines changed: 85 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
#include <swoc/MemArena.h>
1717
#include <swoc/Errata.h>
18+
#include <swoc/IntrusiveHashMap.h>
1819

1920
#include "txn_box/common.h"
2021
#include "txn_box/Rxp.h"
@@ -37,12 +38,6 @@ class Context
3738
{
3839
using self_type = Context;
3940
friend class Config;
40-
/// Cleanup functor for an inverted arena.
41-
/// @internal Because the arena is inverted, calling the destructor will clean up everything.
42-
/// For this reason @c delete must @b not be called (that will double free).
43-
struct ArenaDestructor {
44-
void operator()(swoc::MemArena *arena);
45-
};
4641

4742
public:
4843
/// Construct based a specific configuration.
@@ -147,7 +142,32 @@ class Context
147142
*
148143
* @see mark_for_cleanup
149144
*/
150-
template <typename T> swoc::MemSpan<T> alloc_span(unsigned count);
145+
template <typename T> swoc::MemSpan<T> alloc_span(unsigned count, size_t align = 1);
146+
147+
/** Find or allocate an instance of @a T in configuration storage.
148+
*
149+
* @tparam T Type of object.
150+
* @tparam Args Arguments to @a T constructor.
151+
* @param name Name of object.
152+
* @return A pointer to an initialized instance of @a T in configuration storage.
153+
*
154+
* Looks for the named object and if found returns it. If the name is not found, a @a T is
155+
* allocated and constructed with the arguments after @a name forwarded.
156+
*
157+
* @note This should only be called during configuration loading.
158+
*/
159+
template < typename T, typename ... Args > T * obtain_named_object(swoc::TextView name, Args && ... args);
160+
161+
/** Find named object.
162+
*
163+
* @tparam T Expected type of object.
164+
* @param name Name of the object.
165+
* @return A pointer to the object, or @c nullptr if not found.
166+
*
167+
* @note The caller is presumed to know that @a T is correct, no checks are done. This is purely a convenience to
168+
* avoid excessive casting.
169+
*/
170+
template < typename T > T * named_object(swoc::TextView name);
151171

152172
/** Require @a n bytes of transient buffer.
153173
*
@@ -515,6 +535,13 @@ class Context
515535
swoc::MemSpan<void> _storage;
516536
};
517537

538+
/// Cleanup functor for an inverted arena.
539+
/// @internal Because the arena is inverted, calling the destructor will clean up everything.
540+
/// For this reason @c delete must @b not be called (that will double free).
541+
struct ArenaDestructor {
542+
void operator()(swoc::MemArena *arena);
543+
};
544+
518545
/// Transaction local storage.
519546
/// This is a pointer so that the arena can be inverted to minimize allocations.
520547
std::unique_ptr<swoc::MemArena, ArenaDestructor> _arena;
@@ -567,20 +594,9 @@ class Context
567594
/// Linkage for @c IntrusiveHashMap.
568595
struct Linkage : public swoc::IntrusiveLinkage<self_type, &self_type::_next, &self_type::_prev> {
569596
static swoc::TextView
570-
key_of(self_type *self)
571-
{
572-
return self->_name;
573-
}
574-
static auto
575-
hash_of(swoc::TextView const &text) -> decltype(Hash_Func(text))
576-
{
577-
return Hash_Func(text);
578-
}
579-
static bool
580-
equal(swoc::TextView const &lhs, swoc::TextView const &rhs)
581-
{
582-
return lhs == rhs;
583-
}
597+
key_of(self_type *self) { return self->_name; }
598+
static auto hash_of(swoc::TextView const &text) -> decltype(Hash_Func(text)){ return Hash_Func(text); }
599+
static bool equal(swoc::TextView const &lhs, swoc::TextView const &rhs) { return lhs == rhs; }
584600
};
585601

586602
TxnVar(swoc::TextView const &name, Feature const &value) : _name(name), _value(value) {}
@@ -589,6 +605,30 @@ class Context
589605
using TxnVariables = swoc::IntrusiveHashMap<TxnVar::Linkage>;
590606
TxnVariables _txn_vars; ///< Variables for the transaction.
591607

608+
/// Internal named object local to the context.
609+
struct NamedObject {
610+
using self_type = NamedObject; ///< Self reference type.
611+
static constexpr std::hash<std::string_view> Hash_Func{};
612+
613+
swoc::TextView _name; ///< Name of variable.
614+
swoc::MemSpan<void> _span; ///< Object memroy.
615+
self_type *_next = nullptr; ///< Intrusive link.
616+
self_type *_prev = nullptr; ///< Intrusive link.
617+
618+
/// Linkage for @c IntrusiveHashMap.
619+
struct Linkage : public swoc::IntrusiveLinkage<self_type, &self_type::_next, &self_type::_prev> {
620+
static swoc::TextView
621+
key_of(self_type *self) { return self->_name; }
622+
static auto hash_of(swoc::TextView const &text) -> decltype(Hash_Func(text)){ return Hash_Func(text); }
623+
static bool equal(swoc::TextView const &lhs, swoc::TextView const &rhs) { return lhs == rhs; }
624+
};
625+
626+
NamedObject(swoc::TextView const &name, swoc::MemSpan<void> span) : _name(name), _span(span) {}
627+
};
628+
629+
using NamedObjects = swoc::IntrusiveHashMap<NamedObject::Linkage>;
630+
NamedObjects _named_objects; ///< Conext local data objects.
631+
592632
/// Flag for continuing invoking directives.
593633
bool _terminal_p = false;
594634

@@ -639,10 +679,10 @@ Context::mark_for_cleanup(T* ptr, void (*cleaner)(T*))
639679

640680
template <typename T>
641681
swoc::MemSpan<T>
642-
Context::alloc_span(unsigned int count)
682+
Context::alloc_span(unsigned count, size_t align)
643683
{
644684
this->commit_transient();
645-
return _arena->alloc(sizeof(T) * count).rebind<T>();
685+
return _arena->alloc(sizeof(T) * count, align).rebind<T>();
646686
}
647687

648688
inline swoc::MemSpan<void>
@@ -754,3 +794,25 @@ Context::inbound_ssn()
754794
{
755795
return _txn.inbound_ssn();
756796
}
797+
798+
template <typename T, typename... Args>
799+
T *
800+
Context::obtain_named_object(swoc::TextView name, Args&&... args)
801+
{
802+
auto spot = _named_objects.find(name);
803+
if (spot != _named_objects.end()) {
804+
return static_cast<T*>(spot->_span.data());
805+
}
806+
807+
auto span = this->alloc_span<T>(1, alignof(T));
808+
_named_objects.insert(this->make<NamedObject>(name, span));
809+
return new (span.data()) T(std::forward<Args>(args)...);
810+
}
811+
812+
template <typename T>
813+
T *
814+
Context::named_object(swoc::TextView name)
815+
{
816+
auto spot = _named_objects.find(name);
817+
return spot != _named_objects.end() ? static_cast<T*>(spot->_span.data()) : nullptr;
818+
}

plugin/src/Config.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -813,6 +813,16 @@ Config::feature_scope(ActiveType const &ex_type)
813813
return scope;
814814
}
815815

816+
Config::ActiveCaptureScope
817+
Config::capture_scope(unsigned int count, unsigned int line_no)
818+
{
819+
ActiveCaptureScope scope(*this);
820+
_active_capture._count = count;
821+
_active_capture._line = line_no;
822+
_active_capture._ref_p = false;
823+
return scope;
824+
}
825+
816826
Config::ActiveFeatureScope::~ActiveFeatureScope()
817827
{
818828
if (_cfg) {

plugin/src/Machinery.cc

Lines changed: 7 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2626,11 +2626,6 @@ class Do_proxy_reply : public Directive
26262626
using self_type = Do_proxy_reply; ///< Self reference type.
26272627
using super_type = Directive; ///< Parent type.
26282628

2629-
/// Per configuration storage.
2630-
struct CfgInfo {
2631-
ReservedSpan _ctx_span; ///< Reserved span for @c CtxInfo.
2632-
};
2633-
26342629
/// Per context information.
26352630
/// This is what is stored in the span @c CfgInfo::_ctx_span
26362631
struct CtxInfo {
@@ -2643,7 +2638,7 @@ class Do_proxy_reply : public Directive
26432638
inline static const std::string REASON_KEY = "reason"; ///< Key for reason value.
26442639
inline static const std::string BODY_KEY = "body"; ///< Key for body.
26452640

2646-
static const HookMask HOOKS; ///< Valid hooks for directive.
2641+
static inline const HookMask HOOKS{MaskFor({Hook::CREQ, Hook::PRE_REMAP, Hook::REMAP})}; ///< Valid hooks for directive.
26472642

26482643
/// Need to do fixups on a later hook.
26492644
static constexpr Hook FIXUP_HOOK = Hook::PRSP;
@@ -2662,9 +2657,7 @@ class Do_proxy_reply : public Directive
26622657
static Rv<Handle> load(Config &cfg, CfgStaticData const *, YAML::Node drtv_node, swoc::TextView const &name,
26632658
swoc::TextView const &arg, YAML::Node key_value);
26642659

2665-
/// Configuration level initialization.
2666-
static Errata cfg_init(Config &cfg, CfgStaticData const *rtti);
2667-
2660+
static Errata cfg_init(Config &cfg, CfgStaticData const *);
26682661
protected:
26692662
using index_type = FeatureGroup::index_type;
26702663

@@ -2683,25 +2676,17 @@ class Do_proxy_reply : public Directive
26832676
Errata fixup(Context &ctx);
26842677
};
26852678

2686-
const HookMask Do_proxy_reply::HOOKS{MaskFor({Hook::CREQ, Hook::PRE_REMAP, Hook::REMAP})};
2687-
26882679
Errata
26892680
Do_proxy_reply::cfg_init(Config &cfg, CfgStaticData const *)
26902681
{
2691-
auto cfg_info = cfg.obtain_named_object<CfgInfo>(KEY);
2692-
// Only one proxy_reply can be effective per transaction, therefore shared state per context is best.
2693-
cfg_info->_ctx_span = cfg.reserve_ctx_storage(sizeof(CtxInfo));
26942682
cfg.reserve_slot(FIXUP_HOOK); // needed to fix up "Location" field in proxy response.
26952683
return {};
26962684
}
26972685

26982686
Errata
26992687
Do_proxy_reply::invoke(Context &ctx)
27002688
{
2701-
auto cfg_info = ctx.cfg().named_object<CfgInfo>(KEY);
2702-
if (!cfg_info) { return {}; } // Indicates shutdown is ongoing.
2703-
2704-
auto ctx_info = ctx.initialized_storage_for<CtxInfo>(cfg_info->_ctx_span).data();
2689+
auto ctx_info = ctx.obtain_named_object<CtxInfo>(KEY);
27052690

27062691
// Is a fix up hook required to set the reason correctly?
27072692
bool need_hook_p = false;
@@ -2742,11 +2727,9 @@ Do_proxy_reply::invoke(Context &ctx)
27422727
Errata
27432728
Do_proxy_reply::fixup(Context &ctx)
27442729
{
2745-
if ( auto cfg_info = ctx.cfg().named_object<CfgInfo>(KEY) ; cfg_info ) {
2746-
auto ctx_info = ctx.storage_for(cfg_info->_ctx_span).rebind<CtxInfo>().data();
2747-
auto hdr{ctx.proxy_rsp_hdr()};
2748-
// Set the reason.
2730+
if ( auto ctx_info = ctx.named_object<CtxInfo>(KEY) ; ctx_info ) {
27492731
if (!ctx_info->_reason.empty()) {
2732+
auto hdr{ctx.proxy_rsp_hdr()};
27502733
hdr.reason_set(ctx_info->_reason);
27512734
}
27522735
}
@@ -2846,11 +2829,6 @@ class Do_redirect : public Directive
28462829
using self_type = Do_redirect; ///< Self reference type.
28472830
using super_type = Directive; ///< Parent type.
28482831

2849-
/// Per configuration storage.
2850-
struct CfgInfo {
2851-
ReservedSpan _ctx_span; ///< Reserved span for @c CtxInfo.
2852-
};
2853-
28542832
/// Per context information, used for fix up on proxy response hook.
28552833
/// -- doc Do_redirect::CtxInfo
28562834
struct CtxInfo {
@@ -2913,8 +2891,6 @@ const HookMask Do_redirect::HOOKS{MaskFor(Hook::PRE_REMAP, Hook::REMAP)};
29132891
Errata
29142892
Do_redirect::cfg_init(Config &cfg, CfgStaticData const *)
29152893
{
2916-
auto cfg_info = cfg.obtain_named_object<CfgInfo>(KEY);
2917-
cfg_info->_ctx_span = cfg.reserve_ctx_storage(sizeof(CtxInfo));
29182894
cfg.reserve_slot(FIXUP_HOOK); // needed to fix up "Location" field in proxy response.
29192895
return {};
29202896
}
@@ -2923,11 +2899,7 @@ Do_redirect::cfg_init(Config &cfg, CfgStaticData const *)
29232899
Errata
29242900
Do_redirect::invoke(Context &ctx)
29252901
{
2926-
auto cfg_info = ctx.cfg().named_object<CfgInfo>(KEY);
2927-
if (!cfg_info) {
2928-
return {}; // shutdown in progress and config data is already gone.
2929-
}
2930-
auto ctx_info = ctx.initialized_storage_for<CtxInfo>(cfg_info->_ctx_span).data();
2902+
auto ctx_info = ctx.obtain_named_object<CtxInfo>(KEY);
29312903

29322904
// If the Location view is empty, it hasn't been set and therefore the clean up hook
29332905
// hasn't been set either, so need to do that.
@@ -2976,8 +2948,7 @@ Do_redirect::invoke(Context &ctx)
29762948
Errata
29772949
Do_redirect::fixup(Context &ctx)
29782950
{
2979-
if ( auto cfg_info = ctx.cfg().named_object<CfgInfo>(KEY) ; cfg_info ) {
2980-
auto ctx_info = ctx.storage_for(cfg_info->_ctx_span).rebind<CtxInfo>().data();
2951+
if ( auto ctx_info = ctx.named_object<CtxInfo>(KEY) ; ctx_info ) {
29812952
auto hdr{ctx.proxy_rsp_hdr()};
29822953

29832954
auto field{hdr.field_obtain(ts::HTTP_FIELD_LOCATION)};

0 commit comments

Comments
 (0)