hashicorp/nomad · error

stochastic dynamic port selection failed

Error message

stochastic dynamic port selection failed

What it means

getDynamicPortsStochastic picks dynamic ports by repeatedly drawing random values in [minDynamicPort, maxDynamicPort) and checking collisions. After maxRandPortAttempts failed draws for a single port, it gives up with "stochastic dynamic port selection failed". This is the fallback/randomized path used when precise enumeration isn't possible, and it fails when the range is too full for random search to find free ports.

Source

Thrown at nomad/structs/network.go:716

// getDynamicPortsStochastic takes the nodes used port bitmap which may be nil
// if no ports have been allocated yet, any ports already offered in the caller,
// and the network ask. It returns a set of unused ports to fulfil the ask's
// DynamicPorts or an error if it failed. An error does not mean the ask can not
// be satisfied as the method has a fixed amount of random probes and if these
// fail, the search is aborted.
func getDynamicPortsStochastic(nodeUsed Bitmap, portsInOffer []int, minDynamicPort, maxDynamicPort int, reservedPorts []Port, count int) ([]int, error) {
	var reserved, dynamic []int
	for _, port := range reservedPorts {
		reserved = append(reserved, port.Value)
	}

	for range count {
		attempts := 0
	PICK:
		attempts++
		if attempts > maxRandPortAttempts {
			return nil, fmt.Errorf("stochastic dynamic port selection failed")
		}

		randPort := minDynamicPort
		if maxDynamicPort-minDynamicPort > 0 {
			randPort = randPort + rand.Intn(maxDynamicPort-minDynamicPort)
		}

		if nodeUsed != nil && nodeUsed.Check(uint(randPort)) {
			goto PICK
		}

		for _, ports := range [][]int{reserved, dynamic} {
			if isPortReserved(ports, randPort) {
				goto PICK
			}
		}
		// the pick conflicted with a previous pick that hasn't been saved to
		// the index yet

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Increase the dynamic port range size on the client.
  2. Reduce concurrent dynamic port demand (fewer dynamic ports per allocation).
  3. Retry scheduling on another node with free port capacity.
  4. Raise maxRandPortAttempts if ranges are legitimately dense but not exhausted (library-level tuning).

Example fix

// before
# range too small for load
dynamic_port_range = "20000-20050"
// after
dynamic_port_range = "20000-40000"
Defensive patterns

Strategy: fallback

Validate before calling

utilization := float64(usedDynamicPorts(node)) / float64(rangeSize)
if utilization > 0.9 {
  return fmt.Errorf("dynamic port range %.0f%% full on %s; pick another node", utilization*100, node.ID)
}

Type guard

func rangeHealthy(used, size, attempts int) bool { return size-used > attempts }

Try / catch

null

Prevention

When it happens

Trigger: AssignPorts' dynamic port ask runs maxRandPortAttempts random draws without finding a free port: the dynamic range is nearly saturated on that address (or range is tiny relative to requested count).

Common situations: Heavily loaded nodes with thousands of allocations; very narrow dynamic_port_range configs; tests with artificially small ranges causing >maxRandPortAttempts collisions.

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/cced14bcec7ed713. Report an issue: GitHub.