Follow-up hardening pass (post-3.2.0)
Context
PR #74 (chore: bump version to 3.2.0) ships two noexcept destructor paths that intentionally leak objects into a static container to avoid loader-lock / static-destruction-order hazards:
Logger::shutdown_internal — appends to a static vector under loader lock.
~ConfigWatcher (loader-lock detach branch) — uses s_leaked_impls.emplace_back(std::move(impl_)) inside the noexcept destructor.
Both are currently safe in practice (the branch fires at most once per instance during DLL unload, where an OOM-induced terminate is indistinguishable from OS teardown), but std::vector::emplace_back can in principle throw bad_alloc, which under a noexcept destructor would call std::terminate.
Goal
Align both paths with the HookManager pattern introduced in PR #71:
- Use
new (std::nothrow) + release() instead of emplace_back so the noexcept contract is honestly honoured even under OOM.
- Keep the approach consistent across all three components.
Why deferred
The HookManager pattern was forced by a different constraint (unordered_map holding move-only safetyhook::VmtHook values interacting with MSVC's noexcept destructor check). That constraint does not apply to std::vector<std::unique_ptr<Impl>>, so the change is low-risk but should be done as a single consistent pass covering both Logger and ConfigWatcher rather than piecemeal.
References
Follow-up hardening pass (post-3.2.0)
Context
PR #74 (chore: bump version to 3.2.0) ships two
noexceptdestructor paths that intentionally leak objects into a static container to avoid loader-lock / static-destruction-order hazards:Logger::shutdown_internal— appends to a static vector under loader lock.~ConfigWatcher(loader-lock detach branch) — usess_leaked_impls.emplace_back(std::move(impl_))inside thenoexceptdestructor.Both are currently safe in practice (the branch fires at most once per instance during DLL unload, where an OOM-induced
terminateis indistinguishable from OS teardown), butstd::vector::emplace_backcan in principle throwbad_alloc, which under anoexceptdestructor would callstd::terminate.Goal
Align both paths with the
HookManagerpattern introduced in PR #71:new (std::nothrow)+release()instead ofemplace_backso thenoexceptcontract is honestly honoured even under OOM.Why deferred
The
HookManagerpattern was forced by a different constraint (unordered_mapholding move-onlysafetyhook::VmtHookvalues interacting with MSVC'snoexceptdestructor check). That constraint does not apply tostd::vector<std::unique_ptr<Impl>>, so the change is low-risk but should be done as a single consistent pass covering bothLoggerandConfigWatcherrather than piecemeal.References