This is an automated email from the ASF dual-hosted git repository. bneradt pushed a commit to branch fix-aio-callback-lifetime in repository https://gitbox.apache.org/repos/asf/trafficserver.git
commit b7fca54a285bbbe05037414c9e9a9205c0507bb1 Author: bneradt <[email protected]> AuthorDate: Fri May 8 17:03:53 2026 -0500 Fix AIO callback completion lifetime Dirty cache recovery can complete an AIO operation by invoking a state machine that releases the temporary recovery state owning the callback. After the API AIO cleanup, the generic completion path read the callback again after invoking that continuation, which let ASan abort during startup recovery and restart Traffic Server in a loop. This snapshots the API-owned callback flag before dispatching the completion and uses that local value for the post-callback cleanup. This also adds a focused regression test for completion handlers that release the callback owner before AIOCallback::io_complete() returns. --- src/iocore/aio/AIO.cc | 5 +- src/iocore/aio/CMakeLists.txt | 4 ++ src/iocore/aio/unit_tests/test_AIOCallback.cc | 67 +++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 1 deletion(-) diff --git a/src/iocore/aio/AIO.cc b/src/iocore/aio/AIO.cc index f8d63d3f3b..98d84cdd93 100644 --- a/src/iocore/aio/AIO.cc +++ b/src/iocore/aio/AIO.cc @@ -85,6 +85,9 @@ AIOCallback::io_complete(int event, void *data) { (void)event; (void)data; + // The completion continuation can release the object that owns this callback. + bool const self_delete = from_api; + if (aio_err_callback && !ok()) { AIOCallback *err_op = new AIOCallback(); err_op->aiocb.aio_fildes = this->aiocb.aio_fildes; @@ -99,7 +102,7 @@ AIOCallback::io_complete(int event, void *data) if (!action.cancelled && action.continuation) { action.continuation->handleEvent(AIO_EVENT_DONE, this); } - if (from_api) { + if (self_delete) { delete this; } return EVENT_DONE; diff --git a/src/iocore/aio/CMakeLists.txt b/src/iocore/aio/CMakeLists.txt index 3d06b67ae1..8040a31118 100644 --- a/src/iocore/aio/CMakeLists.txt +++ b/src/iocore/aio/CMakeLists.txt @@ -33,6 +33,10 @@ if(BUILD_TESTING) COMMAND $<TARGET_FILE:test_AIO> WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/src/iocore/aio ) + + add_executable(test_AIOCallback unit_tests/test_AIOCallback.cc) + target_link_libraries(test_AIOCallback PRIVATE ts::aio Catch2::Catch2WithMain) + add_catch2_test(NAME test_AIOCallback COMMAND test_AIOCallback) endif() clang_tidy_check(aio) diff --git a/src/iocore/aio/unit_tests/test_AIOCallback.cc b/src/iocore/aio/unit_tests/test_AIOCallback.cc new file mode 100644 index 0000000000..0fcd131309 --- /dev/null +++ b/src/iocore/aio/unit_tests/test_AIOCallback.cc @@ -0,0 +1,67 @@ +/** @file + + Catch based unit tests for AIOCallback. + + @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. + */ + +#include <catch2/catch_test_macros.hpp> + +#include "iocore/aio/AIO.h" +#include "iocore/eventsystem/Event.h" + +namespace +{ + +struct AIOCompletionOwner : Continuation { + AIOCallback callback; + bool *completed = nullptr; + + explicit AIOCompletionOwner(bool &completion_flag) : Continuation(nullptr), completed(&completion_flag) + { + SET_HANDLER(&AIOCompletionOwner::handle_aio_complete); + + callback.action = this; + callback.aiocb.aio_nbytes = 0; + callback.aio_result = 0; + } + + int + handle_aio_complete(int event, void *data) + { + CHECK(event == AIO_EVENT_DONE); + CHECK(data == &callback); + + *completed = true; + delete this; + return EVENT_DONE; + } +}; + +} // namespace + +TEST_CASE("AIOCallback completion tolerates owner deletion", "[iocore][aio]") +{ + bool completed = false; + auto owner = new AIOCompletionOwner(completed); + auto callback = &owner->callback; + + CHECK(callback->io_complete(EVENT_NONE, nullptr) == EVENT_DONE); + CHECK(completed); +}
