src/internal/config_reload_lifecycle.hpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #ifndef DETOURMODKIT_INTERNAL_CONFIG_RELOAD_LIFECYCLE_HPP | ||
| 2 | #define DETOURMODKIT_INTERNAL_CONFIG_RELOAD_LIFECYCLE_HPP | ||
| 3 | |||
| 4 | /** | ||
| 5 | * @file internal/config_reload_lifecycle.hpp | ||
| 6 | * @brief Reload-pass and lifecycle-gate vocabulary owned by src/internal/config_reload.cpp. | ||
| 7 | * @details The data-plane pass (config.cpp) and the watcher control plane (config_watch.cpp) reach the pass lock and | ||
| 8 | * the background-reload lifecycle gate through this seam. The state itself lives in one TU. | ||
| 9 | */ | ||
| 10 | |||
| 11 | #include <cstdint> | ||
| 12 | #include <mutex> | ||
| 13 | |||
| 14 | namespace DetourModKit::config::detail | ||
| 15 | { | ||
| 16 | /** | ||
| 17 | * @class ReloadApplyLock | ||
| 18 | * @brief Guards a reload pass and fails fast on same-thread re-entry. | ||
| 19 | * @details Serializes an entire load()/reload() pass: read, content-hash decision, and deferred-setter | ||
| 20 | * application. Setters run after the config mutex is released, so this separate lock prevents stale pass | ||
| 21 | * reorder. Acquire this lock FIRST, then the config mutex. Lock order against the watcher control mutex | ||
| 22 | * (config_watch.cpp): a pass-lock holder can take the watcher mutex (load()'s re-point does), but a | ||
| 23 | * watcher-mutex holder must not acquire this lock. The drain predicate reads only the atomic in-flight | ||
| 24 | * count under the watcher mutex. A refused same-thread re-entry stays disengaged, which lets the caller | ||
| 25 | * report failure without a wait. | ||
| 26 | */ | ||
| 27 | class ReloadApplyLock | ||
| 28 | { | ||
| 29 | public: | ||
| 30 | ReloadApplyLock(); | ||
| 31 | ~ReloadApplyLock() noexcept; | ||
| 32 | |||
| 33 | ReloadApplyLock(const ReloadApplyLock &) = delete; | ||
| 34 | ReloadApplyLock &operator=(const ReloadApplyLock &) = delete; | ||
| 35 | |||
| 36 | /// Reports true for an acquired pass lock and false for a refused same-thread re-entry. | ||
| 37 | 257 | [[nodiscard]] bool engaged() const noexcept { return m_engaged; } | |
| 38 | |||
| 39 | /// Releases the pass lock before load() performs a stale-watcher join. The operation is idempotent. | ||
| 40 | void unlock() noexcept; | ||
| 41 | |||
| 42 | private: | ||
| 43 | std::unique_lock<std::mutex> m_lock; | ||
| 44 | bool m_engaged{false}; | ||
| 45 | }; | ||
| 46 | |||
| 47 | /// Reports whether the current thread owns the reload pass lock (it runs inside a bound setter). | ||
| 48 | [[nodiscard]] bool reload_apply_lock_held_by_current_thread() noexcept; | ||
| 49 | |||
| 50 | /** | ||
| 51 | * @class BackgroundReloadGuard | ||
| 52 | * @brief Controls entry to a background reload pass from a watcher callback or hotkey servicer. | ||
| 53 | * @details The captured lifecycle epoch must match an enabled lifecycle state before and after admission. | ||
| 54 | * Otherwise, the pass stops before it can call consumer code. While engaged, it holds the in-flight | ||
| 55 | * count for the whole pass. The check, increment, and recheck pair with the drain latch store and count | ||
| 56 | * load. A pass that unload misses also fails to engage. | ||
| 57 | */ | ||
| 58 | class BackgroundReloadGuard | ||
| 59 | { | ||
| 60 | public: | ||
| 61 | explicit BackgroundReloadGuard(std::uint64_t expected_epoch) noexcept; | ||
| 62 | ~BackgroundReloadGuard() noexcept; | ||
| 63 | |||
| 64 | BackgroundReloadGuard(const BackgroundReloadGuard &) = delete; | ||
| 65 | BackgroundReloadGuard &operator=(const BackgroundReloadGuard &) = delete; | ||
| 66 | |||
| 67 | /// Returns true when reloads are armed for this lifecycle and this pass can run consumer code. | ||
| 68 | 29 | [[nodiscard]] bool engaged() const noexcept { return m_engaged; } | |
| 69 | |||
| 70 | /// Reports whether an admitted pass still belongs to its enabled lifecycle. | ||
| 71 | [[nodiscard]] bool current() const noexcept; | ||
| 72 | |||
| 73 | private: | ||
| 74 | [[nodiscard]] bool lifecycle_current() const noexcept; | ||
| 75 | |||
| 76 | std::uint64_t m_expected_epoch{0}; | ||
| 77 | bool m_engaged{false}; | ||
| 78 | }; | ||
| 79 | |||
| 80 | /// Returns the current reload lifecycle epoch with the unload latch masked off. | ||
| 81 | [[nodiscard]] std::uint64_t current_reload_lifecycle_epoch() noexcept; | ||
| 82 | |||
| 83 | /// Reports whether the background-reload unload latch is set. | ||
| 84 | [[nodiscard]] bool background_reloads_disabled() noexcept; | ||
| 85 | } // namespace DetourModKit::config::detail | ||
| 86 | |||
| 87 | #endif // DETOURMODKIT_INTERNAL_CONFIG_RELOAD_LIFECYCLE_HPP | ||
| 88 |