Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reuse expiration_interval as select() timeout #401

Merged
merged 1 commit into from
Jan 3, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 35 additions & 7 deletions cheroot/connections.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from . import errors
from ._compat import selectors
from ._compat import suppress
from ._compat import IS_WINDOWS
from .makefile import MakeFile

import six
Expand Down Expand Up @@ -152,17 +153,18 @@ def put(self, conn):
conn.socket.fileno(), selectors.EVENT_READ, data=conn,
)

def _expire(self):
"""Expire least recently used connections.
def _expire(self, threshold):
r"""Expire least recently used connections.

This happens if there are either too many open connections, or if the
connections have been timed out.
:param threshold: Connections that have not been used within this \
duration (in seconds), are considered expired and \
are closed and removed.
:type threshold: float

This should be called periodically.
"""
# find any connections still registered with the selector
# that have not been active recently enough.
threshold = time.time() - self.server.timeout
timed_out_connections = [
(sock_fd, conn)
for (sock_fd, conn) in self._selector.connections
Expand Down Expand Up @@ -203,11 +205,37 @@ def run(self, expiration_interval):
self._serving = False

def _run(self, expiration_interval):
r"""Run connection handler loop until stop was requested.

:param expiration_interval: Interval, in seconds, at which \
connections will be checked for \
expiration.
:type expiration_interval: float

Use ``expiration_interval`` as ``select()`` timeout
to assure expired connections are closed in time.

On Windows cap the timeout to 0.05 seconds
as ``select()`` does not return when a socket is ready.
"""
last_expiration_check = time.time()
if IS_WINDOWS:
# 0.05 seconds are used as an empirically obtained balance between
# max connection delay and idle system load. Benchmarks show a
# mean processing time per connection of ~0.03 seconds on Linux
# and with 0.01 seconds timeout on Windows:
# https://github.com/cherrypy/cheroot/pull/352
# While this highly depends on system and hardware, 0.05 seconds
# max delay should hence usually not significantly increase the
# mean time/delay per connection, but significantly reduce idle
# system load by reducing socket loops to 1/5 with 0.01 seconds.
select_timeout = min(expiration_interval, 0.05)
else:
select_timeout = expiration_interval

while not self._stop_requested:
try:
active_list = self._selector.select(timeout=0.01)
active_list = self._selector.select(timeout=select_timeout)
webknjaz marked this conversation as resolved.
Show resolved Hide resolved
except OSError:
self._remove_invalid_sockets()
continue
Expand All @@ -226,7 +254,7 @@ def _run(self, expiration_interval):

now = time.time()
if (now - last_expiration_check) > expiration_interval:
self._expire()
self._expire(threshold=now - self.server.timeout)
last_expiration_check = now

def _remove_invalid_sockets(self):
Expand Down