nodejs/node · error · BalancedPoolMissingUpstreamError

UND_ERR_BPL_MISSING_UPSTREAM

UND_ERR_BPL_MISSING_UPSTREAM

Error message

No upstream has been added to the BalancedPool

What it means

Thrown by BalancedPool's [kGetDispatcher] as BalancedPoolMissingUpstreamError (code UND_ERR_BPL_MISSING_UPSTREAM). The pool intentionally refuses to dispatch when this[kClients].length === 0 rather than queue a request that could wait forever for an upstream that may never arrive. So any dispatch before addUpstream has been called fails fast.

Solutions

  1. Add at least one upstream before dispatching: pool.addUpstream('https://upstream1.example.com').
  2. Pass upstreams in the constructor: new BalancedPool(['https://a.com', 'https://b.com']).
  3. Re-check pool.upstreams.length before dispatching when the pool is reconfigured dynamically.

Example fix

// before
const pool = new BalancedPool()
await pool.request({ method: 'GET', path: '/' }) // throws
// after
const pool = new BalancedPool(['https://a.com', 'https://b.com'])
await pool.request({ method: 'GET', path: '/' })
Defensive patterns

Strategy: validation

Validate before calling

function safeRequest(pool, opts) {
  if (pool.upstreams.length === 0) throw new Error('BalancedPool has no upstreams')
  return pool.request(opts)
}

Type guard

const hasUpstreams = (pool) => Array.isArray(pool.upstreams) && pool.upstreams.length > 0

Try / catch

try {
  await pool.request(opts)
} catch (e) {
  if (e.code === 'UND_ERR_BPL_MISSING_UPSTREAM') { pool.addUpstream(DEFAULT_UPSTREAM); /* retry */ }
  else throw e
}

Prevention

When it happens

Trigger: new BalancedPool() with no upstreams, then pool.request(...) or pool.dispatch(...); or all upstreams were removed/closed leaving kClients empty.

Common situations: Constructing a BalancedPool with an empty array and forgetting to call addUpstream; a race where the pool is used before the bootstrap that populates it runs; dynamic reconfiguration that removes every upstream.

Related errors


AI-assisted analysis of nodejs/node@1b2de5e052 (2026-08-13). Data as JSON: /api/errors/358cea3082ce3b7c. Report an issue: GitHub.

Appendix: source

Thrown at deps/undici/src/lib/dispatcher/balanced-pool.js:164

    return this[kClients].find((pool) => (
      pool[kUrl].origin === upstreamOrigin &&
      pool.closed !== true &&
      pool.destroyed !== true
    ))
  }

  get upstreams () {
    return this[kClients]
      .filter(dispatcher => dispatcher.closed !== true && dispatcher.destroyed !== true)
      .map((p) => p[kUrl].origin)
  }

  [kGetDispatcher] () {
    // We validate that pools is greater than 0,
    // otherwise we would have to wait until an upstream
    // is added, which might never happen.
    if (this[kClients].length === 0) {
      throw new BalancedPoolMissingUpstreamError()
    }

    let counter = 0

    let maxWeightIndex = -1

    while (counter++ < this[kClients].length) {
      this[kIndex] = (this[kIndex] + 1) % this[kClients].length
      const pool = this[kClients][this[kIndex]]

      // decrease the current weight every `this[kClients].length`.
      if (this[kIndex] === 0) {
        // Set the current weight to the next lower weight.
        this[kCurrentWeight] = this[kCurrentWeight] - this[kGreatestCommonDivisor]

        if (this[kCurrentWeight] <= 0) {
          this[kCurrentWeight] = this[kMaxWeightPerServer]
        }

View on GitHub (pinned to 1b2de5e052)