Skip to content

Commit 10fdeba

Browse files
committed
refactor(provider): atomic singleton + ScopedProviderOverride
TrustProvider* g_installed_provider was a raw pointer global — no atomicity, no ownership, no RAII restore. Parallel test runners and any future multi-worker runtime could tear the pointer while another thread is mid-install. Fix: - Back the singleton with std::atomic<TrustProvider*>. runtime_provider() does an acquire-load, install_provider_for_ testing() does an acq_rel exchange and now returns the previous pointer so callers can restore. - Add ScopedProviderOverride: RAII class that installs on construction and restores the prior pointer on destruction. Non-copyable, non-movable; preferred over manual install calls because tests become exception-safe and composable. Backwards-compat: tests that still call install_provider_for_ testing() directly work unchanged (they just ignore the return value). ScopedProviderOverride becomes the recommended pattern for any new call site. Tests 1124/1124 green.
1 parent 4dd0c30 commit 10fdeba

2 files changed

Lines changed: 40 additions & 7 deletions

File tree

runtime/include/provider.hpp

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -276,10 +276,38 @@ class LocalEmbeddedProvider final : public TrustProvider {
276276
std::string instance_pseudonym_{"local-embedded-default"};
277277
};
278278

279-
// TrustProvider singleton used by the runtime dispatch path. Tests may
280-
// swap in an alternate provider via install_provider_for_testing().
279+
// TrustProvider singleton used by the runtime dispatch path. The
280+
// backing pointer is std::atomic so concurrent callers (multi-threaded
281+
// tests, future multi-worker runtime) see a consistent provider and
282+
// can't tear the pointer while another thread is mid-install.
281283
TrustProvider& runtime_provider() noexcept;
282-
void install_provider_for_testing(TrustProvider* provider) noexcept;
284+
285+
// Swap in an alternate provider (tests only). Returns the previous
286+
// installed pointer so tests can restore state on teardown. A
287+
// nullptr argument restores the default LocalEmbeddedProvider.
288+
TrustProvider* install_provider_for_testing(TrustProvider* provider) noexcept;
289+
290+
// RAII scope guard that installs `provider` on construction and
291+
// restores the previous installed pointer on destruction. Preferred
292+
// over manual install_provider_for_testing() calls because it makes
293+
// tests exception-safe and composable.
294+
class ScopedProviderOverride {
295+
public:
296+
[[nodiscard]] explicit ScopedProviderOverride(TrustProvider* provider) noexcept
297+
: previous_(install_provider_for_testing(provider)) {}
298+
299+
~ScopedProviderOverride() noexcept {
300+
(void)install_provider_for_testing(previous_);
301+
}
302+
303+
ScopedProviderOverride(const ScopedProviderOverride&) = delete;
304+
ScopedProviderOverride& operator=(const ScopedProviderOverride&) = delete;
305+
ScopedProviderOverride(ScopedProviderOverride&&) = delete;
306+
ScopedProviderOverride& operator=(ScopedProviderOverride&&) = delete;
307+
308+
private:
309+
TrustProvider* previous_;
310+
};
283311

284312
} // namespace VMPilot::Runtime::Provider
285313

runtime/src/provider/local_embedded.cpp

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "provider.hpp"
22

33
#include <algorithm>
4+
#include <atomic>
45
#include <cstring>
56

67
#include "cbor/strict.hpp"
@@ -223,7 +224,10 @@ bool evidence_binds_to_context(const ProviderEvidence& ev,
223224
ev.policy_requirement_hash == ctx.policy_requirement_hash;
224225
}
225226

226-
TrustProvider* g_installed_provider = nullptr;
227+
// std::atomic so parallel test runners (and any future multi-worker
228+
// runtime) observe a consistent provider pointer and cannot tear
229+
// while another thread is mid-install.
230+
std::atomic<TrustProvider*> g_installed_provider{nullptr};
227231

228232
LocalEmbeddedProvider& default_provider() noexcept {
229233
static LocalEmbeddedProvider instance;
@@ -526,12 +530,13 @@ LocalEmbeddedProvider::bind_artifact(
526530
// ─── Singleton access ───────────────────────────────────────────────────
527531

528532
TrustProvider& runtime_provider() noexcept {
529-
if (g_installed_provider != nullptr) return *g_installed_provider;
533+
auto* p = g_installed_provider.load(std::memory_order_acquire);
534+
if (p != nullptr) return *p;
530535
return default_provider();
531536
}
532537

533-
void install_provider_for_testing(TrustProvider* provider) noexcept {
534-
g_installed_provider = provider;
538+
TrustProvider* install_provider_for_testing(TrustProvider* provider) noexcept {
539+
return g_installed_provider.exchange(provider, std::memory_order_acq_rel);
535540
}
536541

537542
} // namespace VMPilot::Runtime::Provider

0 commit comments

Comments
 (0)