From: Stephen Hemminger <stephen@networkplumber.org>
To: Konstantin Ananyev <konstantin.ananyev@huawei.com>
Cc: <dev@dpdk.org>, <hofors@lysator.liu.se>, <mb@smartsharesystems.com>
Subject: Re: [RFC] memtank: add memtank library
Date: Fri, 3 Jul 2026 09:30:23 -0700 [thread overview]
Message-ID: <20260703093023.703e545f@phoenix.local> (raw)
In-Reply-To: <20260610103918.96857-1-konstantin.ananyev@huawei.com>
On Wed, 10 Jun 2026 11:39:17 +0100
Konstantin Ananyev <konstantin.ananyev@huawei.com> wrote:
> Introduce memtank, highly customizable fixed sized object allocator
> for DPDK applications. It offers close to the mempool level performance
> on the fast path.
> Main difference with the mempool is the ability to grow/shrink at runtime
> with user provided grow/shirnk threshold values, plus some extra
> features for higher flexibility.
>
> Key properties:
>
> - relies on user to provide callbacks for actual memory reservations.
> User is free to choose whatever is most suitable way for his scenario,
> i.e: via malloc/rte_malloc/mmap/some custom memory allocator.
> - user defined constructor callback for newly allocated objects.
> - bulk alloc and free APIs.
> - different alloc/free policies (specified by user via flags parameter):
> * lightweight as possible, but can fail
> * more robust, but heavyweight - causes call to user-provided backing
> memory allocator.
> - backing memory grows/shrinks on demand, special API extensions
> to allow user control grow/shrink size, frequency and when/where it
> is going to happen (DP, CP, both, etc.).
> - ability to pre-allocate all objects at memtank creation time
> (mempool like behavior).
> - custom object size and alignment.
> - per object runtime statistics and sanity-checks (boundary violation,
> double free, etc.) can be enabled/disabled at memtank creation time.
>
> Known limitations (subject for further improvements):
>
> - scalability:
> after 8+ lcores conventional mempool (with FIFO) starts to outperform
> memtank (which uses LIFO inside).
> - mempool_cache integration is not part of the library and right now
> has to be implemented by used manually on top of memtank API.
>
> Envisioned usage scenarios within DPDK-based apps:
> various flow/session control structures (TCP PCB, CT, NAT sessions, etc.)
> that needs to be allocated/freed at the data-path.
> Also can be used by 'semi-fastpath' allocations:
> TBL-8 blocks for LPM, hash buckets, etc.
>
> Initial idea is inspired by Linux/Solaris SLAB allocators.
> Also re-used some ideas from my previous work for TLDK project:
> https://github.com/FDio/tldk
> Signed-off-by: Konstantin Ananyev <konstantin.ananyev@huawei.com>
> ---
Lots of good feedback from Fable AI review. Still needs more work.
Thanks for bringing memtank into DPDK proper - dynamic grow/shrink
fills a real gap between mempool and malloc for session-type
structures. The API shape holds up well against the new-library
criteria: handle-based create/destroy, single installed header,
RTE_EXPORT_EXPERIMENTAL_SYMBOL used correctly (26.11 for the next
merge window), and the three callbacks in rte_memtank_prm are the
point of the design rather than a framework smell.
That said, I applied the RFC on 26.07-rc2 and found real bugs.
Items 1-3 were verified at runtime with ASAN/UBSan harnesses
against the applied tree, item 4 with a build.
Errors:
1. Heap buffer overflow in rte_memtank_create() metadata sizing.
memtank_meta_size() reserves no headroom for aligning the user
allocator's return pointer, unlike memchunk_size() which adds
"algn - 1" for exactly this reason. create() then does:
mt = RTE_PTR_ALIGN_CEIL(p, alignof(typeof(*mt)));
which can advance p by up to 63 bytes into an allocation that only
has 48 bytes of slack (the gap between the offset of mtf.free[]
and sizeof(struct memtank_free)). Nothing documents an alignment
contract for prm->alloc(); with any allocator returning less than
16-byte-aligned memory (32-bit glibc malloc guarantees only 8),
put_free() writes the tail of the free[] cache past the end of
the allocation. Verified with ASAN and an 8-byte-aligned alloc
callback:
WRITE of size 7680 ...
#2 copy_objs lib/memtank/memtank.c:101
#3 put_free lib/memtank/memtank.c:131
#4 rte_memtank_free lib/memtank/memtank.c:627
located 0 bytes after 8520-byte region
Fix: mirror memchunk_size(),
sz = sizeof(*mt) + nb_free * sizeof(mt->mtf.free[0]);
sz = RTE_ALIGN_CEIL(sz + alignof(typeof(*mt)) - 1,
alignof(typeof(*mt)));
and document minimum alignment expectations for alloc().
2. Shrink is a no-op: the loop in shrink_chunk() never executes.
memtank.c:234-239:
k = 0;
n = 0;
for (i = 0; i != num && n != k; i += k) {
"n != k" is evaluated before the first iteration with both
variables zero, so the body never runs and the function always
returns 0. Consequently rte_memtank_shrink() and the
RTE_MTANK_FREE_SHRINK flag never release memory. Verified at
runtime: with all chunks on the FULL list and nb_free == max_free,
rte_memtank_shrink() returns 0 and the free() callback is never
invoked; memory is only released by destroy(). The condition
should presumably be "n == k" (continue while the previous batch
was fully satisfied, stop when _shrink_chunk() drains the FULL
list). Note this also means the stress test exercises none of the
shrink paths, despite reporting shrink statistics.
3. Misaligned struct memobj access when obj_align < 8.
check_param() accepts any power-of-two obj_align including 1 and
2, but the per-object trailer (two uint64_t red zones plus a
pointer) is placed at obj + obj_sz - sizeof(struct memobj), which
is only 8-byte aligned when the object stride is. With obj_align=2
and obj_size=5, UBSan traps on the first grow:
memtank.c:86: runtime error: store to misaligned address ...
for type 'struct memobj', which requires 8 byte alignment
Undefined behavior, and a fault on strict-alignment targets. Fix:
round obj_align up to alignof(struct memobj) inside create(), or
reject smaller values in check_param(). The shipped unit test uses
obj_align=2 and only avoids the trap because obj_size=0 keeps the
stride a multiple of 8.
4. Build failure: VLA in the stress test.
test_memtank_stress.c:425:
uint8_t buf[sz];
DPDK's default warning flags include -Wvla, so CI builds with
werror=true fail here. sz can also reach OBJ_SZ_MAX (1MB), which
is unfriendly to thread stacks regardless. Use a fixed-size buffer
with a bounded compare loop, or allocate once per worker.
Warnings:
5. mt->obj_size truncation for very large objects. memobj_size()
returns size_t but is stored into uint32_t (memtank.c:561). With
obj_size near UINT32_MAX the padded per-object size wraps, so
init_chunk() strides with a truncated value while chunk_size is
computed with the full one - corruption instead of EINVAL.
check_param() should bound obj_size.
6. Deprecated API in new test code: rte_atomic64_t/rte_atomic64_*
(test_memtank_stress.c:33-39) and rte_smp_rmb()/rte_smp_mb()
(:542, :616, :678, :686). New code should use
rte_atomic_*_explicit() with rte_memory_order_*. Related: wrk_cmd
(:127) is a plain uint32_t shared between the control lcore and
workers; it should be RTE_ATOMIC(uint32_t) with release store /
acquire load rather than bare access plus full barriers.
7. master/slave terminology throughout the stress test
(struct master_args, MASTER_FLAG_*, test_memtank_master, "launch
on all slaves"). DPDK uses main/worker; this was purged tree-wide
in 20.11 and must not be reintroduced.
8. __rte_cache_aligned placement. memtank.h:40,48,62 and
test_memtank_stress.c:97 use the old trailing form
"} __rte_cache_aligned;"; current convention is
"struct __rte_cache_aligned foo {" (MSVC-compatible position, see
rte_mempool.h). wrk_cmd applies the attribute to a variable - use
"static alignas(RTE_CACHE_LINE_SIZE) uint32_t" there.
9. meson.build declares deps += ['ring', 'telemetry'] but the
library uses neither (the ring is only in the test app, which has
its own dep entry). The empty extra_flags loop is dead boilerplate.
10. Callback contracts are undocumented. alloc/free/init can be
invoked concurrently from multiple lcores (any thread can trigger
grow or shrink), so they must be MT-safe. Worth stating in the
rte_memtank_prm doxygen, along with alignment expectations and
that alloc() need not zero memory.
11. Documentation needs a pass before leaving RFC:
- memtank_lib.rst uses markdown triple backticks (22 occurrences),
which RST renders literally; use ``inline literals``.
- The create/destroy example does not compile: "sruct", semicolons
instead of commas inside the designated initializer, and
"user_define_free" vs the defined "user_defined_free".
- Typos in the rst and header doxygen: "Same a s mempool", "Aled
public API", "ret_memtank_free", "rte_memetank_destroy",
"memntank", "pooll", "susbystem", "udnerlying", "thershold",
"maximimum", "statisitics", "insteance", "intitialize".
- rte_memtank.h:32 says the lists are "(USED, FREE)" but the
code's states are FULL and USED.
- misc.c:233 dump output has a stray comma: "[USED]={,".
- rte_memtank_chunk_free() doxygen for nb_obj says "Number of
objects to allocate".
12. Missing MAINTAINERS entry and release notes for a new library
(fine to defer for an RFC, needed for the real submission).
Info:
13. extern "C" opens before the #includes in rte_memtank.h;
current convention wraps only the declarations, after all includes
(see rte_mempool.h, rte_argparse.h). Include order is also
inverted (stdio.h after the rte_ headers).
14. RTE_MTANK_ALLOC_CHUNK is never tested by the implementation -
rte_memtank_alloc() gates the chunk path on "flags != 0", so any
future flag silently implies chunk allocation. Either test the
flag explicitly or document that any flag enables the chunk path.
prev parent reply other threads:[~2026-07-03 16:30 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-10 10:39 [RFC] memtank: add memtank library Konstantin Ananyev
2026-07-03 16:30 ` Stephen Hemminger [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260703093023.703e545f@phoenix.local \
--to=stephen@networkplumber.org \
--cc=dev@dpdk.org \
--cc=hofors@lysator.liu.se \
--cc=konstantin.ananyev@huawei.com \
--cc=mb@smartsharesystems.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox