python/cpython · error · ValueError

high (%r) must be >= low (%r) must be >= 0

Error message

high (%r) must be >= low (%r) must be >= 0

What it means

Raised by add_flowcontrol_defaults() in asyncio.sslproto as ValueError when the computed flow-control watermarks violate hi >= lo >= 0. These high/low watermarks govern pause_writing()/resume_writing() backpressure on SSL transports; a high watermark smaller than the low watermark, or a negative value, is nonsensical and rejected.

Source

Thrown at Lib/asyncio/sslproto.py:76

    return sslcontext


def add_flowcontrol_defaults(high, low, kb):
    if high is None:
        if low is None:
            hi = kb * 1024
        else:
            lo = low
            hi = 4 * lo
    else:
        hi = high
    if low is None:
        lo = hi // 4
    else:
        lo = low

    if not hi >= lo >= 0:
        raise ValueError('high (%r) must be >= low (%r) must be >= 0' %
                         (hi, lo))

    return hi, lo


class _SSLProtocolTransport(transports._FlowControlMixin,
                            transports.Transport):

    _start_tls_compatible = True
    _sendfile_compatible = constants._SendfileMode.FALLBACK

    def __init__(self, loop, ssl_protocol):
        self._loop = loop
        self._ssl_protocol = ssl_protocol
        self._closed = False

    def get_extra_info(self, name, default=None):
        """Get optional transport information."""

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Ensure 0 <= low <= high in whatever config produces the watermarks; validate before passing them on.
  2. Remember the defaulting rules: low=None -> high//4; when only kb is given, low=kb*1024 and high=4*low — prefer passing only one bound.
  3. Omit high/low entirely unless you specifically need custom backpressure thresholds.

Example fix

// before
await loop.start_tls(transport, protocol, ctx, high=4096, low=8192)
# ValueError: high (4096) must be >= low (8192) must be >= 0

// after
await loop.start_tls(transport, protocol, ctx, high=32768, low=8192)
Defensive patterns

Strategy: validation

Validate before calling

def valid_watermarks(high, low):
    if low is None:
        low = high // 4
    return high >= low >= 0

assert valid_watermarks(32768, 8192)  # ok
# only pass kwargs that pass this check to start_tls/protocol factories

Type guard

def are_valid_watermarks(high, low) -> bool:
    if high is None or high < 0:
        return False
    eff_low = high // 4 if low is None else low
    return high >= eff_low >= 0

Try / catch

try:
    await loop.start_tls(transport, protocol, ctx, high=h, low=l)
except ValueError as e:
    if 'must be >=' in str(e):
        await loop.start_tls(transport, protocol, ctx)  # sane defaults
    else:
        raise

Prevention

When it happens

Trigger: Creating an SSL-backed endpoint with explicit watermark kwargs where high < low or either is negative — e.g. passing high=4096, low=8192, or high=-1 — through APIs that forward to _SSLProtocol (loop.start_tls / internal protocol creation with high/low arguments).

Common situations: Tuning write-buffer watermarks for streaming protocols and swapping the high/low values; passing 0 or negative sentinels intended to mean 'unlimited'; configuration typos in transport tuning dicts.

Related errors


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/662de879cf945303. Report an issue: GitHub.