GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 91.3% 63 / 0 / 69
Functions: 100.0% 10 / 0 / 10
Branches: 84.2% 32 / 0 / 38

src/internal/drain_backoff.hpp
Line Branch Exec Source
1 #ifndef DETOURMODKIT_INTERNAL_DRAIN_BACKOFF_HPP
2 #define DETOURMODKIT_INTERNAL_DRAIN_BACKOFF_HPP
3
4 /**
5 * @file internal/drain_backoff.hpp
6 * @brief Paces drains with yields followed by sleeps while another thread exits.
7 */
8
9 #include <atomic>
10 #include <chrono>
11 #include <cstdint>
12 #include <thread>
13 #include <utility>
14
15 namespace DetourModKit::detail
16 {
17 #if defined(DMK_ENABLE_TEST_SEAMS)
18 /// Exposes yield-tier pauses to the no-pause proofs.
19 extern std::atomic<std::uint64_t> g_drain_backoff_yields;
20
21 /// Counts every sleep-tier pause across all drains, so a proof can observe the escalation.
22 extern std::atomic<std::uint64_t> g_drain_backoff_sleeps;
23 #endif
24
25 /**
26 * @brief Paces one drain loop with a short yield burst followed by 1 ms sleeps.
27 * @details A drained thread can stay descheduled for a long time (a parked callback, a preempted entrant).
28 * Bare yields burn a core in that wait. The sleep tier limits the cost to one wake per millisecond.
29 * One instance belongs to each wait. It allocates nothing and takes no lock.
30 */
31 class DrainBackoff
32 {
33 public:
34 1958 void pause() noexcept
35 {
36
2/2
✓ Branch 2 → 3 taken 1280 times.
✓ Branch 2 → 7 taken 678 times.
1958 if (m_yields < YIELD_BURST)
37 {
38 1280 ++m_yields;
39 #if defined(DMK_ENABLE_TEST_SEAMS)
40 g_drain_backoff_yields.fetch_add(1, std::memory_order_relaxed);
41 #endif
42 1280 std::this_thread::yield();
43 1280 return;
44 }
45 #if defined(DMK_ENABLE_TEST_SEAMS)
46 g_drain_backoff_sleeps.fetch_add(1, std::memory_order_relaxed);
47 #endif
48 678 std::this_thread::sleep_for(std::chrono::milliseconds{1});
49 }
50
51 private:
52 static constexpr std::uint32_t YIELD_BURST = 64;
53 std::uint32_t m_yields{0};
54 };
55
56 /**
57 * @brief Waits with DrainBackoff pauses until @p count returns zero or @p deadline passes.
58 * @param count A no-throw callable that returns a count comparable with zero.
59 * @return true when the count reached zero. A false return never licenses reclamation: the caller must retain
60 * the undrained resource per [B-73].
61 */
62 template <class CountFn>
63 requires(noexcept(std::declval<CountFn &>()() != 0))
64 2681 [[nodiscard]] bool drain_until_zero(CountFn &&count, std::chrono::steady_clock::time_point deadline) noexcept
65 {
66 2681 DrainBackoff backoff;
67
16/18
bool DetourModKit::detail::drain_until_zero<(anonymous namespace)::DrainUntilZeroTest_ZeroCountReturnsTrueWithoutPausing_Test::TestBody()::{lambda()#1}>((anonymous namespace)::DrainUntilZeroTest_ZeroCountReturnsTrueWithoutPausing_Test::TestBody()::{lambda()#1}&&, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >):
✗ Branch 10 → 3 not taken.
✓ Branch 10 → 11 taken 1 time.
bool DetourModKit::detail::drain_until_zero<(anonymous namespace)::DrainUntilZeroTest_LongCountdownDrainsThroughTheSleepTier_Test::TestBody()::{lambda()#1}>((anonymous namespace)::DrainUntilZeroTest_LongCountdownDrainsThroughTheSleepTier_Test::TestBody()::{lambda()#1}&&, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >):
✓ Branch 10 → 3 taken 79 times.
✓ Branch 10 → 11 taken 1 time.
bool DetourModKit::detail::drain_until_zero<(anonymous namespace)::DrainUntilZeroTest_ExpiredDeadlineReportsFalseBeforeAnyPause_Test::TestBody()::{lambda()#1}>((anonymous namespace)::DrainUntilZeroTest_ExpiredDeadlineReportsFalseBeforeAnyPause_Test::TestBody()::{lambda()#1}&&, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >):
✓ Branch 10 → 3 taken 1 time.
✗ Branch 10 → 11 not taken.
bool DetourModKit::detail::drain_until_zero<DetourModKit::detail::run_down_mid_slot(DetourModKit::detail::MidAdapterSlot&)::{lambda()#1}>(DetourModKit::detail::run_down_mid_slot(DetourModKit::detail::MidAdapterSlot&)::{lambda()#1}&&, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >):
✓ Branch 10 → 3 taken 628 times.
✓ Branch 10 → 11 taken 217 times.
bool DetourModKit::detail::drain_until_zero<DetourModKit::detail::drain_mid_adapter_entries(DetourModKit::detail::MidAdapterSlot&)::{lambda()#1}>(DetourModKit::detail::drain_mid_adapter_entries(DetourModKit::detail::MidAdapterSlot&)::{lambda()#1}&&, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >):
✓ Branch 10 → 3 taken 129 times.
✓ Branch 10 → 11 taken 210 times.
bool DetourModKit::detail::drain_until_zero<DetourModKit::detail::await_staged_input_callbacks(std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >)::{lambda()#1}>(DetourModKit::detail::await_staged_input_callbacks(std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >)::{lambda()#1}&&, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >):
✓ Branch 10 → 3 taken 219 times.
✓ Branch 10 → 11 taken 139 times.
bool DetourModKit::detail::drain_until_zero<DetourModKit::(anonymous namespace)::drain_backend_route<std::variant<safetyhook::InlineHook, safetyhook::MidHook> >(std::variant<safetyhook::InlineHook, safetyhook::MidHook>&)::{lambda()#1}>(std::variant<safetyhook::InlineHook, safetyhook::MidHook>&&, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >):
✓ Branch 10 → 3 taken 452 times.
✓ Branch 10 → 11 taken 470 times.
bool DetourModKit::detail::drain_until_zero<DetourModKit::input::(anonymous namespace)::await_admission_commits(std::atomic<unsigned int>&, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >)::{lambda()#1}>(DetourModKit::input::(anonymous namespace)::await_admission_commits(std::atomic<unsigned int>&, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >)::{lambda()#1}&&, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >):
✓ Branch 10 → 3 taken 131 times.
✓ Branch 10 → 11 taken 142 times.
bool DetourModKit::detail::drain_until_zero<DetourModKit::memory::(anonymous namespace)::drain_admitted_readers()::{lambda()#1}>(DetourModKit::memory::(anonymous namespace)::drain_admitted_readers()::{lambda()#1}&&, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >):
✓ Branch 10 → 3 taken 130 times.
✓ Branch 10 → 11 taken 1493 times.
4442 while (count() != 0)
68 {
69
14/18
bool DetourModKit::detail::drain_until_zero<(anonymous namespace)::DrainUntilZeroTest_ZeroCountReturnsTrueWithoutPausing_Test::TestBody()::{lambda()#1}>((anonymous namespace)::DrainUntilZeroTest_ZeroCountReturnsTrueWithoutPausing_Test::TestBody()::{lambda()#1}&&, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >):
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 8 not taken.
bool DetourModKit::detail::drain_until_zero<(anonymous namespace)::DrainUntilZeroTest_LongCountdownDrainsThroughTheSleepTier_Test::TestBody()::{lambda()#1}>((anonymous namespace)::DrainUntilZeroTest_LongCountdownDrainsThroughTheSleepTier_Test::TestBody()::{lambda()#1}&&, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >):
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 79 times.
bool DetourModKit::detail::drain_until_zero<(anonymous namespace)::DrainUntilZeroTest_ExpiredDeadlineReportsFalseBeforeAnyPause_Test::TestBody()::{lambda()#1}>((anonymous namespace)::DrainUntilZeroTest_ExpiredDeadlineReportsFalseBeforeAnyPause_Test::TestBody()::{lambda()#1}&&, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >):
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 8 not taken.
bool DetourModKit::detail::drain_until_zero<DetourModKit::detail::run_down_mid_slot(DetourModKit::detail::MidAdapterSlot&)::{lambda()#1}>(DetourModKit::detail::run_down_mid_slot(DetourModKit::detail::MidAdapterSlot&)::{lambda()#1}&&, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >):
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 8 taken 627 times.
bool DetourModKit::detail::drain_until_zero<DetourModKit::detail::drain_mid_adapter_entries(DetourModKit::detail::MidAdapterSlot&)::{lambda()#1}>(DetourModKit::detail::drain_mid_adapter_entries(DetourModKit::detail::MidAdapterSlot&)::{lambda()#1}&&, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >):
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 8 taken 128 times.
bool DetourModKit::detail::drain_until_zero<DetourModKit::detail::await_staged_input_callbacks(std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >)::{lambda()#1}>(DetourModKit::detail::await_staged_input_callbacks(std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >)::{lambda()#1}&&, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >):
✓ Branch 6 → 7 taken 2 times.
✓ Branch 6 → 8 taken 217 times.
bool DetourModKit::detail::drain_until_zero<DetourModKit::(anonymous namespace)::drain_backend_route<std::variant<safetyhook::InlineHook, safetyhook::MidHook> >(std::variant<safetyhook::InlineHook, safetyhook::MidHook>&)::{lambda()#1}>(std::variant<safetyhook::InlineHook, safetyhook::MidHook>&&, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >):
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 8 taken 451 times.
bool DetourModKit::detail::drain_until_zero<DetourModKit::input::(anonymous namespace)::await_admission_commits(std::atomic<unsigned int>&, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >)::{lambda()#1}>(DetourModKit::input::(anonymous namespace)::await_admission_commits(std::atomic<unsigned int>&, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >)::{lambda()#1}&&, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >):
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 8 taken 130 times.
bool DetourModKit::detail::drain_until_zero<DetourModKit::memory::(anonymous namespace)::drain_admitted_readers()::{lambda()#1}>(DetourModKit::memory::(anonymous namespace)::drain_admitted_readers()::{lambda()#1}&&, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >):
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 8 taken 129 times.
1769 if (std::chrono::steady_clock::now() >= deadline)
70 {
71 8 return false;
72 }
73 1761 backoff.pause();
74 }
75 2673 return true;
76 }
77 } // namespace DetourModKit::detail
78
79 #endif // DETOURMODKIT_INTERNAL_DRAIN_BACKOFF_HPP
80