Copilot commented on code in PR #13170:
URL: https://github.com/apache/trafficserver/pull/13170#discussion_r3276626643
##########
src/tscore/ink_queue.cc:
##########
@@ -517,117 +578,136 @@ 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;
- }
+ head_p *next_ptr = reinterpret_cast<head_p *>(reinterpret_cast<unsigned
char *>(TO_PTR(FREELIST_POINTER(item))) + l->offset);
+ SET_FREELIST_POINTER_VERSION(next, FREELIST_POINTER(*next_ptr),
FREELIST_VERSION(item) + 1);
+ result = l->head.compare_exchange_weak(item, next);
+ } while (result == false);
+
+ void *ret = TO_PTR(FREELIST_POINTER(item));
+ head_p *ret_ = reinterpret_cast<head_p *>(reinterpret_cast<unsigned char
*>(ret) + l->offset);
+ SET_FREELIST_POINTER_VERSION(*ret_, nullptr, 0);
+ 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) {
+ head_p *e_ = to_head_p(e, l->offset);
+ void *n = TO_PTR(FREELIST_POINTER(*e_));
+ SET_FREELIST_POINTER_VERSION(*e_, n, FREELIST_VERSION(*e_));
+ 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);
- head_p head;
- head_p item_pair;
- int result = 0;
- void *h = nullptr;
+ ink_release_assert(is_addr_aligned(item, alignof(head_p)));
+
+ head_p head;
+ head_p item_pair;
+ head_p *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)
head_p{};
+ SET_FREELIST_POINTER_VERSION(*recovered_item, FREELIST_POINTER(head), 0);
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);
Review Comment:
`ink_atomiclist_push` constructs and writes a `head_p` into the item at
`offset` (`new (... + offset) head_p{}`), which assumes the link storage is
`sizeof(head_p)` and aligned for `head_p`. Many existing list node types
provide only a pointer-sized `next` field (e.g. `SLink<C>::next`), so this can
corrupt memory when `head_p` is wider than a pointer (128-bit CAS) and is still
UB due to type/lifetime mismatch even when sizes match. Consider storing only a
`void*` next pointer in the node and reserving `head_p` for the atomic
head/version.
##########
src/tscore/unit_tests/test_ink_queue.cc:
##########
@@ -0,0 +1,221 @@
+/** @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")
+ {
+ InkFreeList *f{ink_freelist_create("test#1", sizeof(std::int32_t), 1,
alignof(std::int32_t))};
+ 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;
+ head_p next;
Review Comment:
This test defines the atomiclist link field as `head_p next;`, but most
production uses of `InkAtomicList` (e.g. via `AtomicSLL` / `SLink<C>::next`)
use a pointer-typed `next` field. With the current implementation treating the
node link storage as `head_p`, these tests won't catch the
incompatibility/memory corruption risk on 128-bit `head_p` platforms. Please
add coverage using a node type whose `next` member is a pointer (or a real
`SLink`/`Link` member) to match existing call sites.
##########
src/tscore/ink_queue.cc:
##########
@@ -517,117 +578,136 @@ 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;
- }
+ head_p *next_ptr = reinterpret_cast<head_p *>(reinterpret_cast<unsigned
char *>(TO_PTR(FREELIST_POINTER(item))) + l->offset);
+ SET_FREELIST_POINTER_VERSION(next, FREELIST_POINTER(*next_ptr),
FREELIST_VERSION(item) + 1);
+ result = l->head.compare_exchange_weak(item, next);
+ } while (result == false);
+
+ void *ret = TO_PTR(FREELIST_POINTER(item));
+ head_p *ret_ = reinterpret_cast<head_p *>(reinterpret_cast<unsigned char
*>(ret) + l->offset);
+ SET_FREELIST_POINTER_VERSION(*ret_, nullptr, 0);
+ return ret;
Review Comment:
These atomiclist operations treat the per-node "next" field as storage for a
`head_p` object (e.g. `new (... + offset) head_p{}` and
`reinterpret_cast<head_p*>`), but existing users pass offsets to pointer fields
such as `SLink<C>::next` / `Link<C>::next` (see `include/tscore/List.h`) and
`Event::link.next` (used by `ProtectedQueue`). This violates C++ object
model/strict-aliasing even when `head_p` is 64-bit, and it will overwrite
adjacent memory when `TS_HAS_128BIT_CAS` makes `head_p` 128-bit (16 bytes)
while the link field remains an 8-byte pointer. Please either (a) keep node
link fields as pointer types and only store a pointer there (versioning stays
in the list head), or (b) change the API contract to require `head_p`-typed
link storage and update all call sites/struct layouts accordingly.
##########
src/tscore/unit_tests/test_ink_queue.cc:
##########
@@ -0,0 +1,221 @@
+/** @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")
+ {
+ InkFreeList *f{ink_freelist_create("test#1", sizeof(std::int32_t), 1,
alignof(std::int32_t))};
+ void *addr{ink_freelist_new(f)};
Review Comment:
Inside the first `SECTION`, `InkFreeList *f{...}` is redeclared and shadows
the `f` created at the start of the `TEST_CASE`. Because Catch2 re-runs the
outer scope for each section, this results in an extra freelist allocation in
that section that is never used (and never reclaimable), which can skew the
benchmark/leak behavior. Please reuse the existing `f` from the test scope
instead of creating a second one here.
--
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]