./
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
69 / 76
90.8%
Branches:
35 / 42
83.3%
Functions:
11 / 11
100.0%

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 4411 void pause() noexcept
35 {
36
2/2
✓ Branch 2 → 3 taken 3072 times.
✓ Branch 2 → 7 taken 1339 times.
4411 if (m_yields < YIELD_BURST)
37 {
38 3072 ++m_yields;
39 #if defined(DMK_ENABLE_TEST_SEAMS)
40 g_drain_backoff_yields.fetch_add(1, std::memory_order_relaxed);
41 #endif
42 3072 std::this_thread::yield();
43 3072 return;
44 }
45 #if defined(DMK_ENABLE_TEST_SEAMS)
46 g_drain_backoff_sleeps.fetch_add(1, std::memory_order_relaxed);
47 #endif
48 1339 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 31350 [[nodiscard]] bool drain_until_zero(CountFn &&count, std::chrono::steady_clock::time_point deadline) noexcept
65 {
66 31350 DrainBackoff backoff;
67
18/20
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::(anonymous namespace)::reset_inactive_xinput_hook(safetyhook::InlineHook&, std::atomic<unsigned long (*)(unsigned long, _XINPUT_STATE*)>&)::{lambda()#1}>(DetourModKit::detail::(anonymous namespace)::reset_inactive_xinput_hook(safetyhook::InlineHook&, std::atomic<unsigned long (*)(unsigned long, _XINPUT_STATE*)>&)::{lambda()#1}&&, std::chrono::time_point<std::chrono::_V2::steady_clock, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> > >):
✓ Branch 10 → 3 taken 195 times.
✓ Branch 10 → 11 taken 232 times.
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 627 times.
✓ Branch 10 → 11 taken 5247 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 285 times.
✓ Branch 10 → 11 taken 5231 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 1568 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 1093 times.
✓ Branch 10 → 11 taken 16000 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 132 times.
✓ Branch 10 → 11 taken 1572 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 129 times.
✓ Branch 10 → 11 taken 1484 times.
34096 while (count() != 0)
68 {
69
15/20
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::(anonymous namespace)::reset_inactive_xinput_hook(safetyhook::InlineHook&, std::atomic<unsigned long (*)(unsigned long, _XINPUT_STATE*)>&)::{lambda()#1}>(DetourModKit::detail::(anonymous namespace)::reset_inactive_xinput_hook(safetyhook::InlineHook&, std::atomic<unsigned long (*)(unsigned long, _XINPUT_STATE*)>&)::{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 195 times.
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 626 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 2 times.
✓ Branch 6 → 8 taken 283 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 6 times.
✓ Branch 6 → 8 taken 1087 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 131 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 128 times.
2760 if (std::chrono::steady_clock::now() >= deadline)
70 {
71 14 return false;
72 }
73 2746 backoff.pause();
74 }
75 31336 return true;
76 }
77 } // namespace DetourModKit::detail
78
79 #endif // DETOURMODKIT_INTERNAL_DRAIN_BACKOFF_HPP
80