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/18bool 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/18bool 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 |