Bound the request head by the read timeout as a whole

The read timeout applied to each read of the request line and headers,
so a client that sent them a byte now and then held a worker for as
long as it liked, and enough such clients left no worker for anyone
else. Bound the whole head by the read timeout, as the TLS handshake
already is, through a new Stream::set_max_timeout() that the socket
streams implement with their existing max timeout.

A request body is still bounded per read, and a WebSocket holds its
worker for its whole life. The README says so, and points to a
reverse proxy and the pre-request handler for untrusted clients.
This commit is contained in:
yhirose committed 2026-10-16 19:13:03 -04:00
1 parent ae169c6253
commit bc837b87fc
4 files changed
+134 -34

No files matched your search

+1 -1
View File
@@ -484,7 +484,7 @@ With the default ping interval of 30 seconds, `max_missed_pongs = 2` detects a d
## Threading Model
WebSocket connections share the same thread pool as HTTP requests. Each WebSocket connection occupies one thread for its entire lifetime.
WebSocket connections share the same thread pool as HTTP requests. Each WebSocket connection occupies one thread for its entire lifetime, so a client opening many of them can leave none for HTTP requests. Reject the upgrades you do not want in `set_pre_request_handler()`, before the connection takes a thread for good.
The default thread pool uses dynamic scaling: it maintains a base thread count of `CPPHTTPLIB_THREAD_POOL_COUNT` (8 or `std::thread::hardware_concurrency() - 1`, whichever is greater) and can scale up to 4x that count under load (`CPPHTTPLIB_THREAD_POOL_MAX_COUNT`). When all base threads are busy, temporary threads are spawned automatically up to the maximum. These dynamic threads exit after an idle timeout (`CPPHTTPLIB_THREAD_POOL_IDLE_TIMEOUT`, default 3 seconds).