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
- Ensure 0 <= low <= high in whatever config produces the watermarks; validate before passing them on.
- 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.
- 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
- Prefer supplying only one watermark bound and let asyncio derive the other.
- Validate 0 <= low <= high in config loaders before use.
- Document that 'low' pauses resume and 'high' triggers pause_writing; ordering is fixed.
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
- ssl_handshake_timeout should be a positive number, got {ssl_
- ssl_shutdown_timeout should be a positive number, got {ssl_s
- Socket cannot be of type SSLSocket
- server_hostname is only meaningful with ssl
- You must set server_hostname when using ssl without a host
AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14).
Data as JSON: /api/errors/662de879cf945303.
Report an issue: GitHub.