2025, Dec 04 11:00
Understanding aiohttp ceil_threshold: timeout rounding, thresholds, and performance-friendly scheduling
Learn what aiohttp ceil_threshold does, how it rounds timeout expirations, and when to adjust it. Control connect and sock_read timing for tests or production.
Understanding ceil_threshold in aiohttp timeouts: what it is and when it applies
When tuning aiohttp client timeouts, the ceil_threshold parameter often raises questions. Lowering it below connect or sock_read may seem to have no visible effect at first glance. The reason is simple: this setting doesn’t change the semantic meaning of your timeouts; it controls how aiohttp schedules their expiration under the hood.
Consider a typical configuration:
import aiohttp
limit_cfg = aiohttp.ClientTimeout(
connect=10,
sock_read=5,
ceil_threshold=3
)
What ceil_threshold actually does
aiohttp ceils timeout if the value is equal or greater than 5 seconds. The timeout expires at the next integer second greater than
current_time + timeout.The ceiling is done for the sake of optimization, when many concurrent tasks are scheduled to wake-up at almost the same, but different absolute times. It leads to very many event loop wakeups, which kills performance.
The optimization shifts absolute wakeup times by scheduling them to exactly the same time as other neighbors, the loop wakes up once-per-second for timeout expiration.
Smaller timeouts are not rounded to help testing; in the real life network timeouts usually greater than tens of seconds. However, the default threshold value of 5 seconds can be configured using the
ceil_thresholdparameter.
In practice this means the following. aiohttp rounds the timeout’s wake-up moment up to the next whole second, but only for timeouts that are equal to or greater than the configured threshold. If a timeout is less than ceil_threshold, it won’t be rounded. The default threshold is 5 seconds; you can raise or lower it with ceil_threshold.
Why lowering ceil_threshold may look like “no change”
Lowering ceil_threshold doesn’t make your connect or sock_read numerically smaller. It only affects whether aiohttp rounds the internal wake-up time to a whole second. If rounding is enabled for a given timeout, expiration still occurs after your declared duration; the difference is that it is scheduled at the next integer second greater than current_time + timeout. If rounding is disabled for a given timeout, aiohttp uses the exact value as-is.
How to control the behavior
If you want to ensure there is no rounding for a particular timeout, make ceil_threshold larger than that timeout. If you want rounding to happen, make the timeout greater than or equal to the threshold.
For example, to avoid rounding for a 5-second sock_read, set ceil_threshold above 5:
import aiohttp
precise_cfg = aiohttp.ClientTimeout(
connect=10,
sock_read=5,
ceil_threshold=6 # 5 < 6, so no rounding is applied to 5s
)
Conversely, with ceil_threshold=3, both 5 and 10 meet or exceed the threshold, so their expirations are rounded to the next whole second.
Why this detail matters
The rounding exists to coalesce many near-simultaneous timers so the event loop wakes up less often. That helps performance under load. At the same time, smaller timeouts intentionally remain unrounded to keep testing predictable. Understanding this knob lets you choose between tighter timing for tests and optimized scheduling for production-like workloads.
Takeaways
Think of ceil_threshold as a scheduling hint, not a new timeout value. Timeouts smaller than the threshold are left untouched; timeouts equal to or greater than the threshold are rounded up to the next integer second. If you need exact, non-rounded behavior for specific timeouts, set ceil_threshold above them. If you prefer coalesced wake-ups for larger timeouts, keep the default or lower the threshold to include more values.