Skip to content

Commit

Permalink
modify leak detection
Browse files Browse the repository at this point in the history
  • Loading branch information
petiaccja committed Dec 1, 2023
1 parent f07c413 commit b2313c4
Show file tree
Hide file tree
Showing 10 changed files with 112 additions and 71 deletions.
9 changes: 8 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.25)
project(async++)

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
option(ASYNCPP_BUILD_TESTS ON)
option(ENABLE_LLVM_COV OFF)
option(ENABLE_LLVM_ADDRESS_SANITIZER OFF)
option(ENABLE_LLVM_MEMORY_SANITIZER OFF)
Expand Down Expand Up @@ -35,6 +36,12 @@ if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
endif()
endif()

if (${ASYNCPP_BUILD_TESTS})
add_compile_definitions(ASYNCPP_BUILD_TESTS=1)
endif()

add_subdirectory(include/async++)
add_subdirectory(src)
add_subdirectory(test)
if (${ASYNCPP_BUILD_TESTS})
add_subdirectory(test)
endif()
37 changes: 37 additions & 0 deletions include/async++/promise.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

#include "awaitable.hpp"

#include <atomic>
#include <coroutine>
#include <utility>


namespace asyncpp {
Expand Down Expand Up @@ -91,4 +93,39 @@ struct result_promise<void> {
}
};


namespace impl {

class leak_checked_promise {
using snapshot_type = std::pair<intptr_t, intptr_t>;

public:
#ifdef ASYNCPP_BUILD_TESTS
leak_checked_promise() { num_alive.fetch_add(1, std::memory_order_relaxed); }
leak_checked_promise(const leak_checked_promise&) : leak_checked_promise() {}
leak_checked_promise(leak_checked_promise&&) : leak_checked_promise() {}
leak_checked_promise& operator=(const leak_checked_promise&) { return *this; }
leak_checked_promise& operator=(leak_checked_promise&&) { return *this; }
~leak_checked_promise() {
num_alive.fetch_sub(1, std::memory_order_relaxed);
version.fetch_add(1, std::memory_order_relaxed);
}
#endif

static snapshot_type snapshot() {
return { num_alive.load(std::memory_order_relaxed), version.load(std::memory_order_relaxed) };
}

static bool check(snapshot_type s) {
const auto current = snapshot();
return current.first == s.first && current.second > s.second;
}

private:
inline static std::atomic_intptr_t num_alive = 0;
inline static std::atomic_intptr_t version = 0;
};

} // namespace impl

} // namespace asyncpp
2 changes: 1 addition & 1 deletion include/async++/shared_task.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ namespace impl_shared_task {
};

template <class T>
struct promise : result_promise<T>, resumable_promise, schedulable_promise {
struct promise : result_promise<T>, resumable_promise, schedulable_promise, impl::leak_checked_promise {
struct final_awaitable {
constexpr bool await_ready() const noexcept { return false; }
void await_suspend(std::coroutine_handle<promise> handle) noexcept {
Expand Down
2 changes: 1 addition & 1 deletion include/async++/stream.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class stream;
namespace impl_stream {

template <class T>
struct promise : resumable_promise, schedulable_promise {
struct promise : resumable_promise, schedulable_promise, impl::leak_checked_promise {
struct yield_awaitable {
constexpr bool await_ready() const noexcept {
return false;
Expand Down
2 changes: 1 addition & 1 deletion include/async++/task.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class task;
namespace impl_task {

template <class T>
struct promise : result_promise<T>, resumable_promise, schedulable_promise {
struct promise : result_promise<T>, resumable_promise, schedulable_promise, impl::leak_checked_promise {
struct final_awaitable {
constexpr bool await_ready() const noexcept { return false; }
void await_suspend(std::coroutine_handle<promise> handle) const noexcept {
Expand Down
56 changes: 37 additions & 19 deletions test/helper_interleaving.hpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
#pragma once

#include "helper_leak_tester.hpp"
#include "helper_schedulers.hpp"

#include <async++/interleaving/runner.hpp>
#include <async++/interleaving/sequence_point.hpp>

#include <concepts>

#include <catch2/catch_test_macros.hpp>

using namespace asyncpp;
Expand All @@ -24,17 +25,17 @@ auto run_dependent_tasks(MainTask main_task,
main_result_t main_result;
};

const auto create_fixture = [&] {
auto create_fixture = [=, main_args = std::move(main_args), sub_args = std::move(sub_args)] {
auto fixture_ = std::make_shared<fixture>();
auto sub_result = launch(std::apply(sub_task, sub_args), fixture_->sub_sched);
auto all_main_args = std::tuple_cat(std::forward_as_tuple(std::move(sub_result)), main_args);
fixture_->main_result = launch(std::apply(main_task, std::move(all_main_args)), fixture_->main_sched);
return fixture_;
};
const auto sub_thread = [](std::shared_ptr<fixture> fixture_) {
auto sub_thread = [](std::shared_ptr<fixture> fixture_) {
fixture_->sub_sched.resume();
};
const auto main_thread = [](std::shared_ptr<fixture> fixture_) {
auto main_thread = [](std::shared_ptr<fixture> fixture_) {
fixture_->main_sched.resume();
if (!fixture_->main_result.ready()) {
INTERLEAVED_ACQUIRE(fixture_->main_sched.wait());
Expand All @@ -43,9 +44,11 @@ auto run_dependent_tasks(MainTask main_task,
join(fixture_->main_result);
};

return interleaving::run_all(std::function(create_fixture),
std::vector{ std::function(main_thread), std::function(sub_thread) },
{ "$main", "$sub" });
return [create_fixture = std::move(create_fixture), sub_thread = std::move(sub_thread), main_thread = std::move(main_thread)] {
return interleaving::run_all(std::function(create_fixture),
std::vector{ std::function(main_thread), std::function(sub_thread) },
{ "$main", "$sub" });
};
}


Expand All @@ -58,33 +61,48 @@ auto run_abandoned_task(MainTask main_task,
main_result_t main_result;
};

const auto create_fixture = [&] {
auto create_fixture = [main_task, main_args = std::move(main_args)] {
auto fixture_ = std::make_shared<fixture>();
fixture_->main_result = launch(std::apply(main_task, std::move(main_args)), fixture_->main_sched);
return fixture_;
};
const auto exec_thread = [](std::shared_ptr<fixture> fixture_) {
auto exec_thread = [](std::shared_ptr<fixture> fixture_) {
fixture_->main_sched.resume();
};
const auto abandon_thread = [](std::shared_ptr<fixture> fixture_) {
auto abandon_thread = [](std::shared_ptr<fixture> fixture_) {
fixture_->main_result = {};
};

return interleaving::run_all(std::function(create_fixture),
std::vector{ std::function(abandon_thread), std::function(exec_thread) },
{ "$abandon", "$exec" });
return [create_fixture = std::move(create_fixture), abandon_thread = std::move(abandon_thread), exec_thread = std::move(exec_thread)] {
return interleaving::run_all(std::function(create_fixture),
std::vector{ std::function(abandon_thread), std::function(exec_thread) },
{ "$abandon", "$exec" });
};
}


template <class InterleavingGen>
void evaluate_interleavings(InterleavingGen interleaving_gen, std::optional<leak_tester> tester = {}) {
template <std::invocable<> InterleavingGenFunc>
void evaluate_interleavings(InterleavingGenFunc interleaving_gen_func) {
size_t count = 0;
auto before = impl::leak_checked_promise::snapshot();
for (const auto& interleaving : interleaving_gen_func()) {
INFO(count << "\n"
<< (interleaving::interleaving_printer{ interleaving, true }));
REQUIRE(impl::leak_checked_promise::check(before));
auto before = impl::leak_checked_promise::snapshot();
++count;
}
REQUIRE(count >= 3);
}


template <std::ranges::range<> InterleavingGen>
void evaluate_interleavings(InterleavingGen interleaving_gen) {
size_t count = 0;
for (const auto& interleaving : interleaving_gen) {
INFO(count << "\n"
<< (interleaving::interleaving_printer{ interleaving, true }));
++count;
INFO((interleaving::interleaving_printer{ interleaving, true }));
if (tester) {
REQUIRE(tester.value());
}
}
REQUIRE(count >= 3);
}
15 changes: 0 additions & 15 deletions test/helper_leak_tester.hpp

This file was deleted.

22 changes: 10 additions & 12 deletions test/test_shared_task.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,36 +11,34 @@ using namespace asyncpp;


TEST_CASE("Shared task: interleaving co_await", "[Shared task]") {
static const auto sub_task = [](leak_tester tester) -> shared_task<int> {
static const auto sub_task = []() -> shared_task<int> {
co_return 3;
};
static const auto main_task = [](shared_task<int> tsk) -> shared_task<int> {
tsk.launch();
co_return co_await tsk;
};

leak_tester tester;
auto interleavings = run_dependent_tasks(main_task, sub_task, std::tuple{}, std::tuple{ tester });
evaluate_interleavings(std::move(interleavings), std::move(tester));
auto interleavings = run_dependent_tasks(main_task, sub_task, std::tuple{}, std::tuple{});
evaluate_interleavings(std::move(interleavings));
}


TEST_CASE("Shared task: interleaving abandon", "[Shared task]") {
static const auto abandoned_task = [](leak_tester value) -> shared_task<int> { co_return 3; };
static const auto abandoned_task = []() -> shared_task<int> { co_return 3; };

leak_tester tester;
auto interleavings = run_abandoned_task(abandoned_task, std::tuple{ tester });
evaluate_interleavings(std::move(interleavings), std::move(tester));
auto interleavings = run_abandoned_task(abandoned_task, std::tuple{});
evaluate_interleavings(std::move(interleavings));
}


TEST_CASE("Shared task: abandon (not started)", "[Shared task]") {
static const auto coro = [](leak_tester tester) -> shared_task<void> {
static const auto coro = []() -> shared_task<void> {
co_return;
};
leak_tester tester;
static_cast<void>(coro(tester));
REQUIRE(tester);
const auto before = impl::leak_checked_promise::snapshot();
static_cast<void>(coro());
REQUIRE(impl::leak_checked_promise::check(before));
}


Expand Down
16 changes: 7 additions & 9 deletions test/test_stream.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#include "helper_leak_tester.hpp"

#include <async++/join.hpp>
#include <async++/stream.hpp>

Expand All @@ -12,22 +10,22 @@ using namespace asyncpp;


TEST_CASE("Stream: destroy", "[Task]") {
static const auto coro = [](leak_tester value) -> stream<int> { co_yield 0; };
static const auto coro = []() -> stream<int> { co_yield 0; };

SECTION("no execution") {
leak_tester tester;
const auto before = impl::leak_checked_promise::snapshot();
{
auto s = coro(tester);
auto s = coro();
}
REQUIRE(tester);
REQUIRE(impl::leak_checked_promise::check(before));
}
SECTION("synced") {
leak_tester tester;
const auto before = impl::leak_checked_promise::snapshot();
{
auto s = coro(tester);
auto s = coro();
join(s);
}
REQUIRE(tester);
REQUIRE(impl::leak_checked_promise::check(before));
}
}

Expand Down
22 changes: 10 additions & 12 deletions test/test_task.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,36 +11,34 @@ using namespace asyncpp;


TEST_CASE("Task: interleaving co_await", "[Task]") {
static const auto sub_task = [](leak_tester tester) -> task<int> {
static const auto sub_task = []() -> task<int> {
co_return 3;
};
static const auto main_task = [](task<int> tsk) -> task<int> {
tsk.launch();
co_return co_await tsk;
};

leak_tester tester;
auto interleavings = run_dependent_tasks(main_task, sub_task, std::tuple{}, std::tuple{ tester });
evaluate_interleavings(std::move(interleavings), std::move(tester));
auto interleavings = run_dependent_tasks(main_task, sub_task, std::tuple{}, std::tuple{});
evaluate_interleavings(std::move(interleavings));
}


TEST_CASE("Task: interleaving abandon", "[Task]") {
static const auto abandoned_task = [](leak_tester value) -> task<int> { co_return 3; };
static const auto abandoned_task = []() -> task<int> { co_return 3; };

leak_tester tester;
auto interleavings = run_abandoned_task(abandoned_task, std::tuple{ tester });
evaluate_interleavings(std::move(interleavings), std::move(tester));
auto interleavings = run_abandoned_task(abandoned_task, std::tuple{});
evaluate_interleavings(std::move(interleavings));
}


TEST_CASE("Task: abandon (not started)", "[Shared task]") {
static const auto coro = [](leak_tester tester) -> task<void> {
static const auto coro = []() -> task<void> {
co_return;
};
leak_tester tester;
static_cast<void>(coro(tester));
REQUIRE(tester);
const auto before = impl::leak_checked_promise::snapshot();
static_cast<void>(coro());
REQUIRE(impl::leak_checked_promise::check(before));
}


Expand Down

0 comments on commit b2313c4

Please sign in to comment.