Skip to main content

Module injection

Module injection 

Source
Expand description

v7.38 P0 元机制 A — injection_points framework.

§What this gives the test suite

Pepper the engine / storage hot paths with

crate::injection_point!("aggregate_spill_trigger", &group_count);

and tests can do

engine.execute("SELECT spg_injection_attach('aggregate_spill_trigger', 'wait')")?;
// … spawn worker that hits the point …
engine.execute("SELECT spg_injection_wakeup('aggregate_spill_trigger')")?;

to drive deterministic timing without sleep + probability. The framework also supports error:<msg> (panic-cum-typed-error at the site) and notice:<msg> (record + tally, never block). Multiple attaches to the same point clobber the previous one — same as PG’s injection_points_attach.

§Production safety

With the crate feature injection-points off (the default release configuration) injection_point!() expands to the no-op

{ let _ = ($($payload,)*); }

so the payload exprs still type-check (catching drift between site and tests) but no runtime trace ever happens. Zero __trigger symbol, zero thread-local touch, zero Mutex taken. The two unit tests at the bottom of this file verify the no-op invariant by sampling core::mem::size_of shadow markers and inspecting the generated symbol table (see zero_cost_release_macro_expands_empty and the cargo asm recipe documented next to it).

The SQL-facing spg_injection_* builtin functions follow the same gating: with the feature off they return EvalError::TypeMismatch { detail: "injection-points feature not enabled in this build" } so a release SPG cannot be coerced into deadlocking even if an attacker manages to call them.

§Why thread-local not global

Two engines share a process during integration tests (e.g. the permutation runner spins up embedded + server_simple engines in parallel). A single global registry would let test A inject into engine B; cross-contamination defeats the whole point. So:

  • Each crate::Engine owns an Arc<InjectionStore>.
  • Before any execute_* entry point we push the engine’s store onto a thread-local stack via [Engine::enter_injection_scope].
  • __trigger looks up the current store — None is silently tolerated so dropping into the framework from a non-engine code path is safe.

Drop of InjectionGuard restores the previous store, so nested scopes (engine-within-engine for exec_select_with_meta_views style rewrites) compose.

Structs§

InjectionGuard
RAII guard that does nothing when the feature is off.
InjectionStore
Zero-sized placeholder so Engine::injection_store survives the #[derive(Default)] derivation when the feature is off. Arc<InjectionStore> would still allocate; ()-sized InjectionStore plus a wrapping core::marker::PhantomData keeps the field at zero bytes.

Constants§

REGISTERED_POINTS
Stable identifiers for every injection_point!() site SPG ships.

Functions§

enter_scope
new_guard