gorhill/uBlock · error · Error

Only a single instance is supported.

Error message

Only a single instance is supported.

What it means

StaticNetFilteringEngine is a singleton-by-design proxy over the shared module-level snfe engine. The constructor captures the first instance into the module-level snfeProxyInstance (index.js:212); a subsequent `new StaticNetFilteringEngine()` finds it non-null and throws (index.js:209). The slot is cleared only by StaticNetFilteringEngine.release() (index.js:276), so only one engine can live in a process at a time.

Source

Thrown at platform/nodejs/index.js:210

    await useLists.promise;
    useLists.promise = null;

    // Commit changes
    snfe.freeze();
    snfe.optimize();
}

useLists.promise = null;

/******************************************************************************/

const fctx = new FilteringContext();
let snfeProxyInstance = null;

class StaticNetFilteringEngine {
    constructor() {
        if ( snfeProxyInstance !== null ) {
            throw new Error('Only a single instance is supported.');
        }
        snfeProxyInstance = this;
    }

    useLists(lists) {
        return useLists(lists);
    }

    matchRequest(details) {
        return snfe.matchRequest(fctx.fromDetails(details));
    }

    matchAndFetchModifiers(details, modifier) {
        return snfe.matchAndFetchModifiers(fctx.fromDetails(details), modifier);
    }

    hasQuery(details) {
        return snfe.hasQuery(details);

View on GitHub (pinned to c68df492fd)

Solutions

  1. Reuse the single instance returned by the first create() and update lists via its useLists() method rather than recreating.
  2. Call `await StaticNetFilteringEngine.release()` before creating a new instance; release nulls snfeProxyInstance and resets lists.
  3. In test suites, create once in beforeAll and call release() in afterAll, never per-test without teardown.
  4. If you truly need two independent engines in one process, isolate them in separate child processes or worker threads.

Example fix

// before
const engine = await StaticNetFilteringEngine.create();
const engine2 = await StaticNetFilteringEngine.create(); // throws: single instance

// after
const engine = await StaticNetFilteringEngine.create();
await engine.useLists(lists);                  // update in place
await StaticNetFilteringEngine.release();      // free the singleton
const fresh = await StaticNetFilteringEngine.create(); // now ok
Defensive patterns

Strategy: validation

Validate before calling

// Cache and reuse the singleton; release() before creating a new one.
let engine = null;
async function getEngine() {
  if (engine) return engine;
  engine = await StaticNetFilteringEngine.create();
  return engine;
}
async function replaceEngine() {
  await StaticNetFilteringEngine.release();   // clears snfeProxyInstance
  engine = null;
  return getEngine();
}

Try / catch

// If a second create() may race (e.g. warm serverless), catch and reuse/release.
let engine = null;
async function once(fn) {
  if (engine) return engine;
  try {
    engine = await StaticNetFilteringEngine.create();
    return engine;
  } catch (err) {
    if (err && /single instance/i.test(err.message)) {
      await StaticNetFilteringEngine.release();
      engine = await StaticNetFilteringEngine.create();
      return engine;
    }
    throw err;
  }
}

Prevention

When it happens

Trigger: Calling StaticNetFilteringEngine.create() twice without release() in between (create calls the constructor at index.js:265). Directly invoking `new StaticNetFilteringEngine()` a second time. Hot-reloading the module in a long-lived process without releasing the prior instance first.

Common situations: Test suites that spin up a fresh engine per case without teardown; calling create() again after an earlier call threw, on the assumption the failed instance was never registered; serverless warm reuse where module state persists across invocations; recreating the engine instead of updating its lists.

Related errors


AI-assisted analysis of gorhill/uBlock@c68df492fd (2026-08-12). Data as JSON: /api/errors/a57851afa4791fbf. Report an issue: GitHub.