wasmerio/wasmer · critical

Metering::transform_module_info: Attempting to use a `Meteri

Error message

Metering::transform_module_info: Attempting to use a `Metering` middleware from multiple modules.

What it means

The Metering middleware stores per-module state (a global index for remaining points) inside itself. transform_module_info detects that this state was already set, meaning the same Metering instance is being applied to a second module, which would corrupt cost accounting. It panics to enforce one-Metering-per-module usage.

Source

Thrown at lib/middlewares/src/metering.rs:169

impl<F: Fn(&Operator) -> u64 + Send + Sync + 'static> ModuleMiddleware for Metering<F> {
    /// Generates a `FunctionMiddleware` for a given function.
    fn generate_function_middleware<'a>(
        &self,
        _: LocalFunctionIndex,
    ) -> Box<dyn FunctionMiddleware<'a> + 'a> {
        Box::new(FunctionMetering {
            cost_function: self.cost_function.clone(),
            global_indexes: self.global_indexes.lock().unwrap().clone().unwrap(),
            accumulated_cost: 0,
        })
    }

    /// Transforms a `ModuleInfo` struct in-place. This is called before application on functions begins.
    fn transform_module_info(&self, module_info: &mut ModuleInfo) -> Result<(), MiddlewareError> {
        let mut global_indexes = self.global_indexes.lock().unwrap();

        if global_indexes.is_some() {
            panic!(
                "Metering::transform_module_info: Attempting to use a `Metering` middleware from multiple modules."
            );
        }

        // Append a global for remaining points and initialize it.
        let remaining_points_global_index = module_info
            .globals
            .push(GlobalType::new(Type::I64, Mutability::Var));

        module_info
            .global_initializers
            .push(GlobalInit::I64Const(self.initial_limit as i64));

        module_info.exports.insert(
            "wasmer_metering_remaining_points".to_string(),
            ExportIndex::Global(remaining_points_global_index),
        );

View on GitHub (pinned to 8c4b9ee9d3)

Solutions

  1. Create a fresh Metering instance for every module you compile (inside the per-module loop).
  2. If you share middleware, wrap Metering construction in a closure/factory instead of sharing the instance.
  3. Check whether the shared object is an Arc<Metering> and clone semantics silently reuse the same global state; replace with per-use construction.

Example fix

// before
let metering = Arc::new(Metering::new(cost_fn, 10_000));
for wasm in modules {
    Module::new_with_middleware(&engine, &wasm, vec![metering.clone()])?;
}
// after
for wasm in modules {
    let metering = Arc::new(Metering::new(cost_fn, 10_000)); // fresh per module
    Module::new_with_middleware(&engine, &wasm, vec![metering])?;
}
Defensive patterns

Strategy: validation

Validate before calling

// Never reuse a Metering across modules; enforce with a factory
fn metering_chain() -> Vec<Arc<dyn Middleware>> {
    vec![Arc::new(Metering::new(cost_fn, 10_000))] // fresh per call
}

Type guard

fn assert_fresh_metering(m: &Metering) { /* construct per module; Metering carries per-module global state, so there is no safe shared-handle check — always build new */ }

Prevention

When it happens

Trigger: Constructing one `Metering::new(cost_function, initial_points)` and adding it to the middleware chain of more than one module compilation (e.g. looping over several modules each with `.push_metering(metering.clone())` or reusing a shared Arc<Metering> across Module::new calls).

Common situations: Batch-compiling multiple Wasm modules with a single shared middleware; caching a middleware chain in app state and reusing it per request; misunderstanding that Metering holds mutable per-module state rather than being stateless.

Related errors


AI-assisted analysis of wasmerio/wasmer@8c4b9ee9d3 (2026-09-01). Data as JSON: /api/errors/95270c587af69354. Report an issue: GitHub.