Copilot commented on code in PR #13170:
URL: https://github.com/apache/trafficserver/pull/13170#discussion_r3276943044
##########
src/tscore/ink_queue.cc:
##########
@@ -200,15 +223,21 @@ ink_freelist_create(const char *name, uint32_t type_size,
uint32_t chunk_size, u
return f;
}
-#define ADDRESS_OF_NEXT(x, offset) ((void **)((char *)x + offset))
+static void **
+to_voidp_p(void *x, std::uint64_t offset)
+{
+ unsigned char *addr{reinterpret_cast<unsigned char *>(x) + offset};
+ ink_assert(is_addr_aligned(x, alignof(void **)));
Review Comment:
to_voidp_p computes addr = x + offset but asserts alignment of x rather than
addr, so it can return an unaligned void** when offset is not suitably aligned.
The assertion should validate the computed addr meets the alignment required
for void* (and typically should use alignof(void*) rather than alignof(void**)).
##########
src/tscore/ink_queue.cc:
##########
@@ -517,76 +577,96 @@ ink_freelists_dump(FILE *f)
void
ink_atomiclist_init(InkAtomicList *l, const char *name, uint32_t
offset_to_next)
{
+ // The pointers we push onto the atomiclist will also need to be aligned. If
+ // the offset is not aligned, then it is not possible for the caller to
+ // determine a consistent, safe alignment for the object the head_p objects
+ // are subobjects of.
+ ink_release_assert(offset_to_next % alignof(head_p) == 0);
+
l->name = name;
l->offset = offset_to_next;
- SET_FREELIST_POINTER_VERSION(l->head, FROM_PTR(0), 0);
+ head_p empty_head;
+ SET_FREELIST_POINTER_VERSION(empty_head, FROM_PTR(nullptr), 0);
+ l->head.store(empty_head);
}
void *
ink_atomiclist_pop(InkAtomicList *l)
{
head_p item;
head_p next;
- int result = 0;
+ bool result = 0;
+
+ std::lock_guard guard{l->m};
+
+ item = l->head.load();
do {
- INK_QUEUE_LD(item, l->head);
if (TO_PTR(FREELIST_POINTER(item)) == nullptr) {
return nullptr;
}
- SET_FREELIST_POINTER_VERSION(next,
*ADDRESS_OF_NEXT(TO_PTR(FREELIST_POINTER(item)), l->offset),
FREELIST_VERSION(item) + 1);
- result = ink_atomic_cas(&l->head.data, item.data, next.data);
- } while (result == 0);
- {
- void *ret = TO_PTR(FREELIST_POINTER(item));
- *ADDRESS_OF_NEXT(ret, l->offset) = nullptr;
- return ret;
- }
+ void **next_ptr = reinterpret_cast<void **>(reinterpret_cast<unsigned char
*>(TO_PTR(FREELIST_POINTER(item))) + l->offset);
+ SET_FREELIST_POINTER_VERSION(next, *next_ptr, FREELIST_VERSION(item) + 1);
+ result = l->head.compare_exchange_weak(item, next);
+ } while (result == false);
+
+ void *ret = TO_PTR(FREELIST_POINTER(item));
+ void **ret_ = reinterpret_cast<void **>(reinterpret_cast<unsigned char
*>(ret) + l->offset);
+ *ret_ = nullptr;
+ return ret;
}
void *
ink_atomiclist_popall(InkAtomicList *l)
{
head_p item;
head_p next;
- int result = 0;
+ bool result = false;
+
+ item = l->head.load();
do {
- INK_QUEUE_LD(item, l->head);
if (TO_PTR(FREELIST_POINTER(item)) == nullptr) {
return nullptr;
}
SET_FREELIST_POINTER_VERSION(next, FROM_PTR(nullptr),
FREELIST_VERSION(item) + 1);
- result = ink_atomic_cas(&l->head.data, item.data, next.data);
- } while (result == 0);
- {
- void *ret = TO_PTR(FREELIST_POINTER(item));
- void *e = ret;
- /* fixup forward pointers */
- while (e) {
- void *n = TO_PTR(*ADDRESS_OF_NEXT(e, l->offset));
- *ADDRESS_OF_NEXT(e, l->offset) = n;
- e = n;
- }
- return ret;
+ result = l->head.compare_exchange_weak(item, next);
+ } while (result == false);
+
+ void *ret = TO_PTR(FREELIST_POINTER(item));
+ void *e = ret;
+ /* fixup forward pointers */
+ while (e) {
+ void **e_ = to_voidp_p(e, l->offset);
+ void *n = TO_PTR(*e_);
+ *e_ = n;
+ e = n;
}
+
+ ink_assert(is_addr_aligned(ret, alignof(head_p)));
Review Comment:
ink_atomiclist_popall asserts ret is aligned to alignof(head_p). The
returned pointer is an item allocated by the caller, and only needs to satisfy
the caller's object alignment (and the next-pointer field alignment). If head_p
becomes 16-byte aligned (TS_HAS_128BIT_CAS), this assertion can fire even when
ret is correctly void*-aligned. The alignment check should be against the
actual requirement (typically alignof(void*) and/or the list's expected node
alignment), not alignof(head_p).
##########
src/tscore/ink_queue.cc:
##########
@@ -517,76 +577,96 @@ ink_freelists_dump(FILE *f)
void
ink_atomiclist_init(InkAtomicList *l, const char *name, uint32_t
offset_to_next)
{
+ // The pointers we push onto the atomiclist will also need to be aligned. If
+ // the offset is not aligned, then it is not possible for the caller to
+ // determine a consistent, safe alignment for the object the head_p objects
+ // are subobjects of.
+ ink_release_assert(offset_to_next % alignof(head_p) == 0);
+
l->name = name;
l->offset = offset_to_next;
- SET_FREELIST_POINTER_VERSION(l->head, FROM_PTR(0), 0);
+ head_p empty_head;
+ SET_FREELIST_POINTER_VERSION(empty_head, FROM_PTR(nullptr), 0);
+ l->head.store(empty_head);
}
void *
ink_atomiclist_pop(InkAtomicList *l)
{
head_p item;
head_p next;
- int result = 0;
+ bool result = 0;
+
+ std::lock_guard guard{l->m};
+
+ item = l->head.load();
do {
- INK_QUEUE_LD(item, l->head);
if (TO_PTR(FREELIST_POINTER(item)) == nullptr) {
return nullptr;
}
- SET_FREELIST_POINTER_VERSION(next,
*ADDRESS_OF_NEXT(TO_PTR(FREELIST_POINTER(item)), l->offset),
FREELIST_VERSION(item) + 1);
- result = ink_atomic_cas(&l->head.data, item.data, next.data);
- } while (result == 0);
- {
- void *ret = TO_PTR(FREELIST_POINTER(item));
- *ADDRESS_OF_NEXT(ret, l->offset) = nullptr;
- return ret;
- }
+ void **next_ptr = reinterpret_cast<void **>(reinterpret_cast<unsigned char
*>(TO_PTR(FREELIST_POINTER(item))) + l->offset);
+ SET_FREELIST_POINTER_VERSION(next, *next_ptr, FREELIST_VERSION(item) + 1);
+ result = l->head.compare_exchange_weak(item, next);
+ } while (result == false);
+
+ void *ret = TO_PTR(FREELIST_POINTER(item));
+ void **ret_ = reinterpret_cast<void **>(reinterpret_cast<unsigned char
*>(ret) + l->offset);
+ *ret_ = nullptr;
+ return ret;
}
void *
ink_atomiclist_popall(InkAtomicList *l)
{
head_p item;
head_p next;
- int result = 0;
+ bool result = false;
+
+ item = l->head.load();
do {
- INK_QUEUE_LD(item, l->head);
if (TO_PTR(FREELIST_POINTER(item)) == nullptr) {
return nullptr;
}
SET_FREELIST_POINTER_VERSION(next, FROM_PTR(nullptr),
FREELIST_VERSION(item) + 1);
- result = ink_atomic_cas(&l->head.data, item.data, next.data);
- } while (result == 0);
- {
- void *ret = TO_PTR(FREELIST_POINTER(item));
- void *e = ret;
- /* fixup forward pointers */
- while (e) {
- void *n = TO_PTR(*ADDRESS_OF_NEXT(e, l->offset));
- *ADDRESS_OF_NEXT(e, l->offset) = n;
- e = n;
- }
- return ret;
+ result = l->head.compare_exchange_weak(item, next);
+ } while (result == false);
Review Comment:
ink_atomiclist_popall is not protected by the new InkAtomicList mutex,
unlike ink_atomiclist_pop. If the intent is to provide mutual exclusion among
pop operations to avoid UB (per PR description), popall should also take the
mutex; otherwise concurrent pop/popall can still race on the head node's next
pointer reads.
##########
src/tscore/unit_tests/test_ink_queue.cc:
##########
@@ -0,0 +1,220 @@
+/** @file
+
+ Freelist and Atomiclist tests
+
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+#define CATCH_CONFIG_ENABLE_BENCHMARKING
+#include <catch2/catch_all.hpp>
+
+#include <tscore/ink_queue.h>
+
+#include <atomic>
+#include <cstddef>
+#include <cstdint>
+#include <thread>
+#include <vector>
+
+TEST_CASE("Freelist", "[freelist]")
+{
+ // There is no error reporting for this routine. The allocation
+ // is assumed to never fail.
+ InkFreeList *f{ink_freelist_create("test#1", sizeof(std::int32_t), 1,
alignof(std::int32_t))};
+
+ SECTION("Freelist allocates aligned pointers")
+ {
+ void *addr{ink_freelist_new(f)};
+
+ CHECK(!(reinterpret_cast<std::uintptr_t>(addr) & (alignof(std::int32_t) -
1)));
+
+ ink_freelist_free(f, addr);
+ }
+
+ SECTION("Two new freelist allocations")
+ {
+ std::int32_t *a{new (ink_freelist_new(f)) std::int32_t{1}};
+ std::int32_t *b{new (ink_freelist_new(f)) std::int32_t{2}};
+
+ CHECK(((*a == 1) && (*b == 2)));
+
+ ink_freelist_free(f, a);
+ ink_freelist_free(f, b);
+ }
+
+ SECTION("Freelist stack behavior")
+ {
+ void *a{ink_freelist_new(f)};
+ void *b{ink_freelist_new(f)};
+
+ ink_freelist_free(f, a);
+ ink_freelist_free(f, b);
+
+ void *x{ink_freelist_new(f)};
+ void *y{ink_freelist_new(f)};
+
+ CHECK((a == y && b == x));
+
+ ink_freelist_free(f, a);
+ ink_freelist_free(f, b);
+ }
+}
+
+TEST_CASE("Empty atomic list", "[atomiclist]")
+{
+ InkAtomicList l;
+ ink_atomiclist_init(&l, "test#1", 0);
+
+ CHECK(nullptr == ink_atomiclist_pop(&l));
+}
+
+TEST_CASE("Popall from empty atomic list", "[atomiclist]")
+{
+ InkAtomicList l;
+ ink_atomiclist_init(&l, "test#1", 0);
+
+ CHECK(nullptr == ink_atomiclist_popall(&l));
+}
+
+TEST_CASE("Atomic list", "[atomiclist]")
+{
+ struct test_type {
+ std::int32_t i;
+ void *next;
+ };
+
+ InkAtomicList l;
+ ink_atomiclist_init(&l, "test#1", offsetof(test_type, next));
+
+ // We allocate memory this way to ensure proper alignment for atomiclist.
+ InkFreeList *f{ink_freelist_create("test#1", sizeof(test_type), 1,
alignof(test_type))};
+
+ SECTION("Atomic list becomes empty after push and pop")
+ {
+ ink_atomiclist_push(&l, ink_freelist_new(f));
+ void *a{ink_atomiclist_pop(&l)};
+
+ CHECK(nullptr == ink_atomiclist_pop(&l));
+
+ ink_freelist_free(f, a);
+ }
+
+ SECTION("Atomic list stack behavior")
+ {
+ void *a{ink_freelist_new(f)};
+ void *b{ink_freelist_new(f)};
+
+ ink_atomiclist_push(&l, a);
+ ink_atomiclist_push(&l, b);
+
+ void *x{ink_atomiclist_pop(&l)};
+ void *y{ink_atomiclist_pop(&l)};
+
+ CHECK((a == y && b == x));
+
+ ink_freelist_free(f, a);
+ ink_freelist_free(f, b);
+ }
+
+ SECTION("Atomic list remove head")
+ {
+ void *a{ink_freelist_new(f)};
+ void *b{ink_freelist_new(f)};
+
+ ink_atomiclist_push(&l, a);
+ ink_atomiclist_push(&l, b);
+
+ CHECK(b == ink_atomiclist_remove(&l, b));
+
+ ink_freelist_free(f, a);
+ ink_freelist_free(f, b);
+ }
+
+ SECTION("Atomic list remove tail")
+ {
+ void *a{ink_freelist_new(f)};
+ void *b{ink_freelist_new(f)};
+
+ ink_atomiclist_push(&l, a);
+ ink_atomiclist_push(&l, b);
+
+ CHECK(a == ink_atomiclist_remove(&l, a));
+
+ ink_freelist_free(f, a);
+ ink_freelist_free(f, b);
+ }
+
+ SECTION("Atomic list becomes empty after popall")
+ {
+ void *a{ink_freelist_new(f)};
+ void *b{ink_freelist_new(f)};
+
+ ink_atomiclist_push(&l, a);
+ ink_atomiclist_push(&l, b);
+
+ ink_atomiclist_popall(&l);
+ CHECK(nullptr == ink_atomiclist_popall(&l));
+
+ ink_freelist_free(f, a);
+ ink_freelist_free(f, b);
+ }
+
+ SECTION("Atomic list popall behavior")
+ {
+ void *a{ink_freelist_new(f)};
+ void *b{ink_freelist_new(f)};
+
+ ink_atomiclist_push(&l, a);
+ ink_atomiclist_push(&l, b);
+
+ void *head{ink_atomiclist_popall(&l)};
+ void **head_{reinterpret_cast<void **>(reinterpret_cast<unsigned char
*>(head) + l.offset)};
+ void *tail{FREELIST_POINTER(*head_)};
+
+ CHECK(((head == b) && (tail == a)));
+
+ ink_freelist_free(f, a);
+ ink_freelist_free(f, b);
+ }
+}
+
+TEST_CASE("Freelist benchmarks", "[freelist][bench]")
+{
+ BENCHMARK_ADVANCED("Single threaded alloc")(Catch::Benchmark::Chronometer
meter)
+ {
+ InkFreeList *f{ink_freelist_create("test#1", sizeof(std::int32_t), 4,
alignof(std::int32_t))};
+
+ ink_freelist_new(f);
+
+ meter.measure([&]() { return ink_freelist_new(f); });
+ };
+
+ BENCHMARK_ADVANCED("Single threaded free")(Catch::Benchmark::Chronometer
meter)
+ {
+ InkFreeList *f{ink_freelist_create("test#1", sizeof(std::int32_t), 4,
alignof(std::int32_t))};
+
+ std::vector<void *> ptrs;
+ ptrs.resize(meter.runs());
+ for (auto &x : ptrs) {
+ x = ink_freelist_new(f);
+ }
+
+ meter.measure([&](int i) { return ink_freelist_free(f, ptrs[i]); });
+ };
+}
Review Comment:
The new `TEST_CASE("Freelist benchmarks", ...)` uses Catch2
BENCHMARK_ADVANCED. Catch2 runs benchmarks by default unless the test runner is
invoked with `--skip-benchmarks`; `test_tscore` is currently registered without
that flag, so this will add significant runtime to the regular unit-test suite.
Consider gating these benchmarks behind an opt-in build flag, moving them to a
dedicated microbench target, or updating the test invocation to skip benchmarks
in CI/unit-test runs.
##########
include/tscore/ink_queue.h:
##########
@@ -213,9 +236,10 @@ void ink_freelists_snap_baseline();
struct InkAtomicList {
InkAtomicList() {}
- head_p head{};
- const char *name = nullptr;
- uint32_t offset = 0;
+ std::mutex m;
+ std::atomic<head_p> head{};
+ const char *name = nullptr;
+ uint32_t offset = 0;
};
Review Comment:
InkAtomicList::head is now std::atomic<head_p>, but there are existing
non-macro call sites that access `al.head` as if it were a plain head_p (e.g.,
AtomicSLL::head() in include/tscore/List.h currently does
`FREELIST_POINTER(al.head)`). This type change requires updating those call
sites to use `.head.load(...)` (or providing a helper accessor) or the build
will fail due to passing std::atomic to FREELIST_POINTER/TO_PTR.
##########
src/tscore/ink_queue.cc:
##########
@@ -517,76 +577,96 @@ ink_freelists_dump(FILE *f)
void
ink_atomiclist_init(InkAtomicList *l, const char *name, uint32_t
offset_to_next)
{
+ // The pointers we push onto the atomiclist will also need to be aligned. If
+ // the offset is not aligned, then it is not possible for the caller to
+ // determine a consistent, safe alignment for the object the head_p objects
+ // are subobjects of.
+ ink_release_assert(offset_to_next % alignof(head_p) == 0);
+
l->name = name;
l->offset = offset_to_next;
- SET_FREELIST_POINTER_VERSION(l->head, FROM_PTR(0), 0);
+ head_p empty_head;
+ SET_FREELIST_POINTER_VERSION(empty_head, FROM_PTR(nullptr), 0);
+ l->head.store(empty_head);
Review Comment:
ink_atomiclist_init now asserts offset_to_next % alignof(head_p) == 0, but
the offset is used to access a void* next field, not a head_p. On platforms
where head_p has stricter alignment (e.g., 16-byte when TS_HAS_128BIT_CAS),
this will spuriously abort for common structures where the next pointer is only
void*-aligned (e.g., ProtectedQueue/Event and AtomicSLL). Consider validating
against alignof(void*) (and/or validate the alignment of item+offset at push
time) instead.
##########
ci/asan_leak_suppression/unit_tests.txt:
##########
@@ -2,3 +2,8 @@
# not destroyed because it holds a reference to a stack-allocated
# TestRefCountObj whose free() override calls exit(1).
leak:test_http_hdr_print_and_copy_aux
+
+# on 64-bit architectures, freelist pointers are stored in a special bitwise
+# format, so LSan cannot find link pointers. It will erroneously determine
+# that the list tail is not reachable.
+leak:freelist_new
Review Comment:
LSan suppression `leak:freelist_new` is very broad: any real leak
originating from freelist allocations in unit tests will also be suppressed. If
possible, narrow the suppression (e.g., suppress the specific test binary/stack
frame that is a known false positive, or suppress the specific
allocator/backing allocation function) so leak detection remains useful for
other freelist-backed allocations.
##########
src/tscore/ink_queue.cc:
##########
@@ -517,76 +577,96 @@ ink_freelists_dump(FILE *f)
void
ink_atomiclist_init(InkAtomicList *l, const char *name, uint32_t
offset_to_next)
{
+ // The pointers we push onto the atomiclist will also need to be aligned. If
+ // the offset is not aligned, then it is not possible for the caller to
+ // determine a consistent, safe alignment for the object the head_p objects
+ // are subobjects of.
+ ink_release_assert(offset_to_next % alignof(head_p) == 0);
+
l->name = name;
l->offset = offset_to_next;
- SET_FREELIST_POINTER_VERSION(l->head, FROM_PTR(0), 0);
+ head_p empty_head;
+ SET_FREELIST_POINTER_VERSION(empty_head, FROM_PTR(nullptr), 0);
+ l->head.store(empty_head);
}
void *
ink_atomiclist_pop(InkAtomicList *l)
{
head_p item;
head_p next;
- int result = 0;
+ bool result = 0;
+
+ std::lock_guard guard{l->m};
+
+ item = l->head.load();
do {
- INK_QUEUE_LD(item, l->head);
if (TO_PTR(FREELIST_POINTER(item)) == nullptr) {
return nullptr;
}
- SET_FREELIST_POINTER_VERSION(next,
*ADDRESS_OF_NEXT(TO_PTR(FREELIST_POINTER(item)), l->offset),
FREELIST_VERSION(item) + 1);
- result = ink_atomic_cas(&l->head.data, item.data, next.data);
- } while (result == 0);
- {
- void *ret = TO_PTR(FREELIST_POINTER(item));
- *ADDRESS_OF_NEXT(ret, l->offset) = nullptr;
- return ret;
- }
+ void **next_ptr = reinterpret_cast<void **>(reinterpret_cast<unsigned char
*>(TO_PTR(FREELIST_POINTER(item))) + l->offset);
+ SET_FREELIST_POINTER_VERSION(next, *next_ptr, FREELIST_VERSION(item) + 1);
+ result = l->head.compare_exchange_weak(item, next);
+ } while (result == false);
+
+ void *ret = TO_PTR(FREELIST_POINTER(item));
+ void **ret_ = reinterpret_cast<void **>(reinterpret_cast<unsigned char
*>(ret) + l->offset);
+ *ret_ = nullptr;
+ return ret;
}
void *
ink_atomiclist_popall(InkAtomicList *l)
{
head_p item;
head_p next;
- int result = 0;
+ bool result = false;
+
+ item = l->head.load();
do {
- INK_QUEUE_LD(item, l->head);
if (TO_PTR(FREELIST_POINTER(item)) == nullptr) {
return nullptr;
}
SET_FREELIST_POINTER_VERSION(next, FROM_PTR(nullptr),
FREELIST_VERSION(item) + 1);
- result = ink_atomic_cas(&l->head.data, item.data, next.data);
- } while (result == 0);
- {
- void *ret = TO_PTR(FREELIST_POINTER(item));
- void *e = ret;
- /* fixup forward pointers */
- while (e) {
- void *n = TO_PTR(*ADDRESS_OF_NEXT(e, l->offset));
- *ADDRESS_OF_NEXT(e, l->offset) = n;
- e = n;
- }
- return ret;
+ result = l->head.compare_exchange_weak(item, next);
+ } while (result == false);
+
+ void *ret = TO_PTR(FREELIST_POINTER(item));
+ void *e = ret;
+ /* fixup forward pointers */
+ while (e) {
+ void **e_ = to_voidp_p(e, l->offset);
+ void *n = TO_PTR(*e_);
+ *e_ = n;
+ e = n;
}
+
+ ink_assert(is_addr_aligned(ret, alignof(head_p)));
+
+ return ret;
}
void *
ink_atomiclist_push(InkAtomicList *l, void *item)
{
- void **adr_of_next = ADDRESS_OF_NEXT(item, l->offset);
+ ink_release_assert(is_addr_aligned(item, alignof(void *)));
+
head_p head;
head_p item_pair;
- int result = 0;
+ void **recovered_item;
+ bool result = false;
void *h = nullptr;
+
+ head = l->head.load();
do {
- INK_QUEUE_LD(head, l->head);
- h = FREELIST_POINTER(head);
- *adr_of_next = h;
+ h = FREELIST_POINTER(head);
ink_assert(item != TO_PTR(h));
+
+ recovered_item = new (reinterpret_cast<unsigned char *>(item) +
l->offset) void *{};
+ *recovered_item = FREELIST_POINTER(head);
SET_FREELIST_POINTER_VERSION(item_pair, FROM_PTR(item),
FREELIST_VERSION(head));
- INK_MEMORY_BARRIER;
- result = ink_atomic_cas(&l->head.data, head.data, item_pair.data);
- } while (result == 0);
+ result = l->head.compare_exchange_weak(head, item_pair);
+ } while (result == false);
Review Comment:
ink_atomiclist_push does not take the new InkAtomicList mutex, but the PR
description states atomiclist_push was locked to provide mutual exclusion.
Either add the lock (if required for the UB fix) or update the
description/approach; as-is the locking strategy is inconsistent (pop is
locked, popall/push are not).
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]