From e99941d3c10a3def18291a5ceba4d1dba9232747 Mon Sep 17 00:00:00 2001 From: Dan Vinakovsky Date: Fri, 28 Sep 2018 15:07:30 -0400 Subject: [PATCH 01/24] enhance worker run loop allows workers to deal with non-blocking sockets using select --- cheroot/workers/threadpool.py | 54 ++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/cheroot/workers/threadpool.py b/cheroot/workers/threadpool.py index ff8fbcee8e..831392d3e8 100644 --- a/cheroot/workers/threadpool.py +++ b/cheroot/workers/threadpool.py @@ -7,6 +7,7 @@ import threading import time import socket +import select from six.moves import queue @@ -100,25 +101,46 @@ def run(self): self.server.stats['Worker Threads'][self.getName()] = self.stats try: self.ready = True + conn_socks = {} while True: - conn = self.server.requests.get() - if conn is _SHUTDOWNREQUEST: - return - - self.conn = conn - if self.server.stats['Enabled']: - self.start_time = time.time() try: - conn.communicate() - finally: - conn.close() + conn = self.server.requests.get(block=True, timeout=0.01) + except queue.Empty: + pass + else: + if conn is _SHUTDOWNREQUEST: + return + conn_socks[conn.socket] = (conn, time.time()) + + rlist, wlist, xlist = [], [], [] + socks = [sock for sock in conn_socks.keys() if sock.fileno() > -1] + if socks: + rlist, wlist, xlist = select.select(socks, socks, socks, 0) + for sock in rlist + wlist + xlist: + conn, conn_start_time = conn_socks[sock] + self.conn = conn if self.server.stats['Enabled']: - self.requests_seen += self.conn.requests_seen - self.bytes_read += self.conn.rfile.bytes_read - self.bytes_written += self.conn.wfile.bytes_written - self.work_time += time.time() - self.start_time - self.start_time = None - self.conn = None + self.start_time = time.time() + try: + err_occurred = conn.communicate() is False + finally: + if err_occurred: + conn.close() + conn_socks.pop(conn.socket) + else: + conn_socks[conn.socket] = (conn, time.time()) + if self.server.stats['Enabled']: + self.requests_seen += self.conn.requests_seen + self.bytes_read += self.conn.rfile.bytes_read + self.bytes_written += self.conn.wfile.bytes_written + self.work_time += time.time() - self.start_time + self.start_time = None + self.conn = None + cur_time = time.time() + for c, conn_last_active in list(conn_socks.values()): + if cur_time - conn_last_active > 60: + c.close() + conn_socks.pop(c.socket) except (KeyboardInterrupt, SystemExit) as ex: self.server.interrupt = ex From 7989913861abe4769a16235813000ea90826d049 Mon Sep 17 00:00:00 2001 From: Dan Vinakovsky Date: Fri, 28 Sep 2018 15:08:39 -0400 Subject: [PATCH 02/24] non-blocking --- cheroot/server.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cheroot/server.py b/cheroot/server.py index d635a1a878..263d5e0581 100644 --- a/cheroot/server.py +++ b/cheroot/server.py @@ -1223,12 +1223,13 @@ def communicate(self): # Something went wrong in the parsing (and the server has # probably already made a simple_response). Return and # let the conn close. - return + # return False + return True request_seen = True req.respond() if req.close_connection: - return + return False except socket.error as ex: errnum = ex.args[0] # sadly SSL sockets return a different (longer) time out string @@ -1244,6 +1245,7 @@ def communicate(self): self.server.error_log('socket.error %s' % repr(errnum), level=logging.WARNING, traceback=True) self._conditional_error(req, '500 Internal Server Error') + return False except (KeyboardInterrupt, SystemExit): raise except errors.FatalSSLAlert: @@ -1254,6 +1256,8 @@ def communicate(self): self.server.error_log( repr(ex), level=logging.ERROR, traceback=True) self._conditional_error(req, '500 Internal Server Error') + return False + return True linger = False From 55375568412144121a28a49840edc2410abc16a4 Mon Sep 17 00:00:00 2001 From: Dan Vinakovsky Date: Fri, 28 Sep 2018 15:25:04 -0400 Subject: [PATCH 03/24] Update threadpool.py --- cheroot/workers/threadpool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cheroot/workers/threadpool.py b/cheroot/workers/threadpool.py index 831392d3e8..073da96263 100644 --- a/cheroot/workers/threadpool.py +++ b/cheroot/workers/threadpool.py @@ -115,7 +115,7 @@ def run(self): rlist, wlist, xlist = [], [], [] socks = [sock for sock in conn_socks.keys() if sock.fileno() > -1] if socks: - rlist, wlist, xlist = select.select(socks, socks, socks, 0) + rlist, wlist, xlist = select.select(socks, [], [], 0) for sock in rlist + wlist + xlist: conn, conn_start_time = conn_socks[sock] self.conn = conn From 0b77338cd6a331e2bb87dedf89494e1b056f4a0b Mon Sep 17 00:00:00 2001 From: Dan Vinakovsky Date: Sun, 30 Sep 2018 17:48:19 -0400 Subject: [PATCH 04/24] just need rlist --- cheroot/workers/threadpool.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cheroot/workers/threadpool.py b/cheroot/workers/threadpool.py index 073da96263..f18f39697a 100644 --- a/cheroot/workers/threadpool.py +++ b/cheroot/workers/threadpool.py @@ -112,11 +112,11 @@ def run(self): return conn_socks[conn.socket] = (conn, time.time()) - rlist, wlist, xlist = [], [], [] + rlist = [] socks = [sock for sock in conn_socks.keys() if sock.fileno() > -1] if socks: - rlist, wlist, xlist = select.select(socks, [], [], 0) - for sock in rlist + wlist + xlist: + rlist = select.select(socks, [], [], 0)[0] + for sock in rlist: conn, conn_start_time = conn_socks[sock] self.conn = conn if self.server.stats['Enabled']: From 47952c62509a72cc3a29e2084e6ec1e770780a10 Mon Sep 17 00:00:00 2001 From: Date: Sun, 30 Sep 2018 20:31:15 -0400 Subject: [PATCH 05/24] lint and refactor run method. possibly fix a test.. --- .appveyor.yml | 0 .circleci/config.yml | 0 .codecov.yml | 0 .git_archival.txt | 0 .gitattributes | 0 .github/CODE_OF_CONDUCT.md | 0 .github/CONTRIBUTING.rst | 0 .github/ISSUE_TEMPLATE.md | 0 .github/ISSUE_TEMPLATE/Bug_report.md | 118 +++++++++++----------- .github/ISSUE_TEMPLATE/Feature_request.md | 90 ++++++++--------- .github/PULL_REQUEST_TEMPLATE.md | 0 .github/stale.yml | 0 .gitignore | 0 .mention-bot | 0 .pre-commit-config.yaml | 0 .pre-commit-config.yaml.failing | 0 .readthedocs.yml | 0 .travis.yml | 0 CHANGES.rst | 0 LICENSE.md | 0 README.rst | 0 cheroot/__init__.py | 0 cheroot/__main__.py | 0 cheroot/_compat.py | 0 cheroot/cli.py | 0 cheroot/errors.py | 0 cheroot/makefile.py | 0 cheroot/server.py | 2 +- cheroot/ssl/__init__.py | 0 cheroot/ssl/builtin.py | 0 cheroot/ssl/pyopenssl.py | 0 cheroot/test/__init__.py | 0 cheroot/test/conftest.py | 0 cheroot/test/helper.py | 0 cheroot/test/ssl/ca.cert | 0 cheroot/test/ssl/ca.key | 0 cheroot/test/ssl/client.cert | 0 cheroot/test/ssl/client.key | 0 cheroot/test/ssl/client_ip.cert | 0 cheroot/test/ssl/client_wildcard.cert | 0 cheroot/test/ssl/client_wrong_ca.cert | 0 cheroot/test/ssl/client_wrong_host.cert | 0 cheroot/test/ssl/server.cert | 0 cheroot/test/ssl/server.key | 0 cheroot/test/test.pem | 0 cheroot/test/test__compat.py | 0 cheroot/test/test_conn.py | 0 cheroot/test/test_core.py | 0 cheroot/test/test_errors.py | 0 cheroot/test/test_https.py | 0 cheroot/test/test_server.py | 0 cheroot/test/test_ssl.py | 0 cheroot/test/webtest.py | 0 cheroot/testing.py | 0 cheroot/workers/__init__.py | 0 cheroot/workers/threadpool.py | 68 +++++++------ cheroot/wsgi.py | 0 docs/conf.py | 0 docs/history.rst | 0 docs/index.rst | 0 docs/pkg/cheroot._compat.rst | 0 docs/pkg/cheroot.errors.rst | 0 docs/pkg/cheroot.makefile.rst | 0 docs/pkg/cheroot.server.rst | 0 docs/pkg/cheroot.ssl.builtin.rst | 0 docs/pkg/cheroot.ssl.pyopenssl.rst | 0 docs/pkg/cheroot.ssl.rst | 0 docs/pkg/cheroot.testing.rst | 0 docs/pkg/cheroot.wsgi.rst | 0 pyproject.toml | 0 pytest.ini | 0 setup.cfg | 0 setup.py | 0 tox.ini | 0 74 files changed, 143 insertions(+), 135 deletions(-) mode change 100644 => 100755 .appveyor.yml mode change 100644 => 100755 .circleci/config.yml mode change 100644 => 100755 .codecov.yml mode change 100644 => 100755 .git_archival.txt mode change 100644 => 100755 .gitattributes mode change 100644 => 100755 .github/CODE_OF_CONDUCT.md mode change 100644 => 100755 .github/CONTRIBUTING.rst mode change 100644 => 100755 .github/ISSUE_TEMPLATE.md mode change 100644 => 100755 .github/ISSUE_TEMPLATE/Bug_report.md mode change 100644 => 100755 .github/ISSUE_TEMPLATE/Feature_request.md mode change 100644 => 100755 .github/PULL_REQUEST_TEMPLATE.md mode change 100644 => 100755 .github/stale.yml mode change 100644 => 100755 .gitignore mode change 100644 => 100755 .mention-bot mode change 100644 => 100755 .pre-commit-config.yaml mode change 100644 => 100755 .pre-commit-config.yaml.failing mode change 100644 => 100755 .readthedocs.yml mode change 100644 => 100755 .travis.yml mode change 100644 => 100755 CHANGES.rst mode change 100644 => 100755 LICENSE.md mode change 100644 => 100755 README.rst mode change 100644 => 100755 cheroot/__init__.py mode change 100644 => 100755 cheroot/__main__.py mode change 100644 => 100755 cheroot/_compat.py mode change 100644 => 100755 cheroot/cli.py mode change 100644 => 100755 cheroot/errors.py mode change 100644 => 100755 cheroot/makefile.py mode change 100644 => 100755 cheroot/server.py mode change 100644 => 100755 cheroot/ssl/__init__.py mode change 100644 => 100755 cheroot/ssl/builtin.py mode change 100644 => 100755 cheroot/ssl/pyopenssl.py mode change 100644 => 100755 cheroot/test/__init__.py mode change 100644 => 100755 cheroot/test/conftest.py mode change 100644 => 100755 cheroot/test/helper.py mode change 100644 => 100755 cheroot/test/ssl/ca.cert mode change 100644 => 100755 cheroot/test/ssl/ca.key mode change 100644 => 100755 cheroot/test/ssl/client.cert mode change 100644 => 100755 cheroot/test/ssl/client.key mode change 100644 => 100755 cheroot/test/ssl/client_ip.cert mode change 100644 => 100755 cheroot/test/ssl/client_wildcard.cert mode change 100644 => 100755 cheroot/test/ssl/client_wrong_ca.cert mode change 100644 => 100755 cheroot/test/ssl/client_wrong_host.cert mode change 100644 => 100755 cheroot/test/ssl/server.cert mode change 100644 => 100755 cheroot/test/ssl/server.key mode change 100644 => 100755 cheroot/test/test.pem mode change 100644 => 100755 cheroot/test/test__compat.py mode change 100644 => 100755 cheroot/test/test_conn.py mode change 100644 => 100755 cheroot/test/test_core.py mode change 100644 => 100755 cheroot/test/test_errors.py mode change 100644 => 100755 cheroot/test/test_https.py mode change 100644 => 100755 cheroot/test/test_server.py mode change 100644 => 100755 cheroot/test/test_ssl.py mode change 100644 => 100755 cheroot/test/webtest.py mode change 100644 => 100755 cheroot/testing.py mode change 100644 => 100755 cheroot/workers/__init__.py mode change 100644 => 100755 cheroot/workers/threadpool.py mode change 100644 => 100755 cheroot/wsgi.py mode change 100644 => 100755 docs/conf.py mode change 100644 => 100755 docs/history.rst mode change 100644 => 100755 docs/index.rst mode change 100644 => 100755 docs/pkg/cheroot._compat.rst mode change 100644 => 100755 docs/pkg/cheroot.errors.rst mode change 100644 => 100755 docs/pkg/cheroot.makefile.rst mode change 100644 => 100755 docs/pkg/cheroot.server.rst mode change 100644 => 100755 docs/pkg/cheroot.ssl.builtin.rst mode change 100644 => 100755 docs/pkg/cheroot.ssl.pyopenssl.rst mode change 100644 => 100755 docs/pkg/cheroot.ssl.rst mode change 100644 => 100755 docs/pkg/cheroot.testing.rst mode change 100644 => 100755 docs/pkg/cheroot.wsgi.rst mode change 100644 => 100755 pyproject.toml mode change 100644 => 100755 pytest.ini mode change 100644 => 100755 setup.cfg mode change 100644 => 100755 setup.py mode change 100644 => 100755 tox.ini diff --git a/.appveyor.yml b/.appveyor.yml old mode 100644 new mode 100755 diff --git a/.circleci/config.yml b/.circleci/config.yml old mode 100644 new mode 100755 diff --git a/.codecov.yml b/.codecov.yml old mode 100644 new mode 100755 diff --git a/.git_archival.txt b/.git_archival.txt old mode 100644 new mode 100755 diff --git a/.gitattributes b/.gitattributes old mode 100644 new mode 100755 diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md old mode 100644 new mode 100755 diff --git a/.github/CONTRIBUTING.rst b/.github/CONTRIBUTING.rst old mode 100644 new mode 100755 diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md old mode 100644 new mode 100755 diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md old mode 100644 new mode 100755 index 6bdea233b0..286dbbbe24 --- a/.github/ISSUE_TEMPLATE/Bug_report.md +++ b/.github/ISSUE_TEMPLATE/Bug_report.md @@ -1,59 +1,59 @@ ---- -name: "\U0001F41B Bug report" -about: Create a report to help us improve - ---- - - -**I'm submitting a ...** -- [X] bug report -- [ ] feature request -- [ ] question about the decisions made in the repository - -**Describe the bug. What is the current behavior?** - - -**What is the motivation / use case for changing the behavior?** - - -**To Reproduce** - -Steps to reproduce the behavior: -1. Run '...' -2. Make request '....' -3. See error - -**Expected behavior** - - -**Details** - - -**Environment** - -- Cheroot version: X.X.X -- CherryPy version: X.X.X (if applicable) -- Python version: 3.6.X -- OS: XXX -- Browser: [all | Chrome XX | Firefox XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView ] - -**Additional context** - +--- +name: "\U0001F41B Bug report" +about: Create a report to help us improve + +--- + + +**I'm submitting a ...** +- [X] bug report +- [ ] feature request +- [ ] question about the decisions made in the repository + +**Describe the bug. What is the current behavior?** + + +**What is the motivation / use case for changing the behavior?** + + +**To Reproduce** + +Steps to reproduce the behavior: +1. Run '...' +2. Make request '....' +3. See error + +**Expected behavior** + + +**Details** + + +**Environment** + +- Cheroot version: X.X.X +- CherryPy version: X.X.X (if applicable) +- Python version: 3.6.X +- OS: XXX +- Browser: [all | Chrome XX | Firefox XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView ] + +**Additional context** + diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md old mode 100644 new mode 100755 index 3f491af799..ce822bf84d --- a/.github/ISSUE_TEMPLATE/Feature_request.md +++ b/.github/ISSUE_TEMPLATE/Feature_request.md @@ -1,45 +1,45 @@ ---- -name: "\U0001F680 Feature request" -about: Suggest an idea for this project - ---- - - - -**I'm submitting a ...** -- [ ] bug report -- [X] feature request -- [ ] question about the decisions made in the repository - - - -**Is your feature request related to a problem? Please describe.** - - - - -**Describe the solution you'd like** - - - -**Describe alternatives you've considered** - - - - -**Additional context** - +--- +name: "\U0001F680 Feature request" +about: Suggest an idea for this project + +--- + + + +**I'm submitting a ...** +- [ ] bug report +- [X] feature request +- [ ] question about the decisions made in the repository + + + +**Is your feature request related to a problem? Please describe.** + + + + +**Describe the solution you'd like** + + + +**Describe alternatives you've considered** + + + + +**Additional context** + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md old mode 100644 new mode 100755 diff --git a/.github/stale.yml b/.github/stale.yml old mode 100644 new mode 100755 diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/.mention-bot b/.mention-bot old mode 100644 new mode 100755 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml old mode 100644 new mode 100755 diff --git a/.pre-commit-config.yaml.failing b/.pre-commit-config.yaml.failing old mode 100644 new mode 100755 diff --git a/.readthedocs.yml b/.readthedocs.yml old mode 100644 new mode 100755 diff --git a/.travis.yml b/.travis.yml old mode 100644 new mode 100755 diff --git a/CHANGES.rst b/CHANGES.rst old mode 100644 new mode 100755 diff --git a/LICENSE.md b/LICENSE.md old mode 100644 new mode 100755 diff --git a/README.rst b/README.rst old mode 100644 new mode 100755 diff --git a/cheroot/__init__.py b/cheroot/__init__.py old mode 100644 new mode 100755 diff --git a/cheroot/__main__.py b/cheroot/__main__.py old mode 100644 new mode 100755 diff --git a/cheroot/_compat.py b/cheroot/_compat.py old mode 100644 new mode 100755 diff --git a/cheroot/cli.py b/cheroot/cli.py old mode 100644 new mode 100755 diff --git a/cheroot/errors.py b/cheroot/errors.py old mode 100644 new mode 100755 diff --git a/cheroot/makefile.py b/cheroot/makefile.py old mode 100644 new mode 100755 diff --git a/cheroot/server.py b/cheroot/server.py old mode 100644 new mode 100755 index 263d5e0581..cd07dca73b --- a/cheroot/server.py +++ b/cheroot/server.py @@ -1223,7 +1223,7 @@ def communicate(self): # Something went wrong in the parsing (and the server has # probably already made a simple_response). Return and # let the conn close. - # return False + # return False return True request_seen = True diff --git a/cheroot/ssl/__init__.py b/cheroot/ssl/__init__.py old mode 100644 new mode 100755 diff --git a/cheroot/ssl/builtin.py b/cheroot/ssl/builtin.py old mode 100644 new mode 100755 diff --git a/cheroot/ssl/pyopenssl.py b/cheroot/ssl/pyopenssl.py old mode 100644 new mode 100755 diff --git a/cheroot/test/__init__.py b/cheroot/test/__init__.py old mode 100644 new mode 100755 diff --git a/cheroot/test/conftest.py b/cheroot/test/conftest.py old mode 100644 new mode 100755 diff --git a/cheroot/test/helper.py b/cheroot/test/helper.py old mode 100644 new mode 100755 diff --git a/cheroot/test/ssl/ca.cert b/cheroot/test/ssl/ca.cert old mode 100644 new mode 100755 diff --git a/cheroot/test/ssl/ca.key b/cheroot/test/ssl/ca.key old mode 100644 new mode 100755 diff --git a/cheroot/test/ssl/client.cert b/cheroot/test/ssl/client.cert old mode 100644 new mode 100755 diff --git a/cheroot/test/ssl/client.key b/cheroot/test/ssl/client.key old mode 100644 new mode 100755 diff --git a/cheroot/test/ssl/client_ip.cert b/cheroot/test/ssl/client_ip.cert old mode 100644 new mode 100755 diff --git a/cheroot/test/ssl/client_wildcard.cert b/cheroot/test/ssl/client_wildcard.cert old mode 100644 new mode 100755 diff --git a/cheroot/test/ssl/client_wrong_ca.cert b/cheroot/test/ssl/client_wrong_ca.cert old mode 100644 new mode 100755 diff --git a/cheroot/test/ssl/client_wrong_host.cert b/cheroot/test/ssl/client_wrong_host.cert old mode 100644 new mode 100755 diff --git a/cheroot/test/ssl/server.cert b/cheroot/test/ssl/server.cert old mode 100644 new mode 100755 diff --git a/cheroot/test/ssl/server.key b/cheroot/test/ssl/server.key old mode 100644 new mode 100755 diff --git a/cheroot/test/test.pem b/cheroot/test/test.pem old mode 100644 new mode 100755 diff --git a/cheroot/test/test__compat.py b/cheroot/test/test__compat.py old mode 100644 new mode 100755 diff --git a/cheroot/test/test_conn.py b/cheroot/test/test_conn.py old mode 100644 new mode 100755 diff --git a/cheroot/test/test_core.py b/cheroot/test/test_core.py old mode 100644 new mode 100755 diff --git a/cheroot/test/test_errors.py b/cheroot/test/test_errors.py old mode 100644 new mode 100755 diff --git a/cheroot/test/test_https.py b/cheroot/test/test_https.py old mode 100644 new mode 100755 diff --git a/cheroot/test/test_server.py b/cheroot/test/test_server.py old mode 100644 new mode 100755 diff --git a/cheroot/test/test_ssl.py b/cheroot/test/test_ssl.py old mode 100644 new mode 100755 diff --git a/cheroot/test/webtest.py b/cheroot/test/webtest.py old mode 100644 new mode 100755 diff --git a/cheroot/testing.py b/cheroot/testing.py old mode 100644 new mode 100755 diff --git a/cheroot/workers/__init__.py b/cheroot/workers/__init__.py old mode 100644 new mode 100755 diff --git a/cheroot/workers/threadpool.py b/cheroot/workers/threadpool.py old mode 100644 new mode 100755 index f18f39697a..97c02ddff8 --- a/cheroot/workers/threadpool.py +++ b/cheroot/workers/threadpool.py @@ -93,6 +93,42 @@ def __init__(self, server): } threading.Thread.__init__(self) + def close_expired_conns(self, conn_socks): + cur_time = time.time() + for c, last_active in list(conn_socks.values()): + sock = c.socket + srv_timeout = self.server.timeout + if (sock.timeout == 0.0 and cur_time - last_active > 60 or + sock.timeout and cur_time - last_active > srv_timeout): + c.close() + conn_socks.pop(c.socket) + + def process_conns(self, conn_socks): + rlist = [] + socks = [sck for sck in conn_socks.keys() if sck.fileno() > -1] + if socks: + rlist = select.select(socks, [], [], 0)[0] + for sock in rlist: + conn, conn_start_time = conn_socks[sock] + self.conn = conn + if self.server.stats['Enabled']: + self.start_time = time.time() + try: + err_occurred = conn.communicate() is False + finally: + if err_occurred: + conn.close() + conn_socks.pop(conn.socket) + else: + conn_socks[conn.socket] = (conn, time.time()) + if self.server.stats['Enabled']: + self.requests_seen += self.conn.requests_seen + self.bytes_read += self.conn.rfile.bytes_read + self.bytes_written += self.conn.wfile.bytes_written + self.work_time += time.time() - self.start_time + self.start_time = None + self.conn = None + def run(self): """Process incoming HTTP connections. @@ -111,36 +147,8 @@ def run(self): if conn is _SHUTDOWNREQUEST: return conn_socks[conn.socket] = (conn, time.time()) - - rlist = [] - socks = [sock for sock in conn_socks.keys() if sock.fileno() > -1] - if socks: - rlist = select.select(socks, [], [], 0)[0] - for sock in rlist: - conn, conn_start_time = conn_socks[sock] - self.conn = conn - if self.server.stats['Enabled']: - self.start_time = time.time() - try: - err_occurred = conn.communicate() is False - finally: - if err_occurred: - conn.close() - conn_socks.pop(conn.socket) - else: - conn_socks[conn.socket] = (conn, time.time()) - if self.server.stats['Enabled']: - self.requests_seen += self.conn.requests_seen - self.bytes_read += self.conn.rfile.bytes_read - self.bytes_written += self.conn.wfile.bytes_written - self.work_time += time.time() - self.start_time - self.start_time = None - self.conn = None - cur_time = time.time() - for c, conn_last_active in list(conn_socks.values()): - if cur_time - conn_last_active > 60: - c.close() - conn_socks.pop(c.socket) + self.process_conns(conn_socks) + self.close_expired_conns(conn_socks) except (KeyboardInterrupt, SystemExit) as ex: self.server.interrupt = ex diff --git a/cheroot/wsgi.py b/cheroot/wsgi.py old mode 100644 new mode 100755 diff --git a/docs/conf.py b/docs/conf.py old mode 100644 new mode 100755 diff --git a/docs/history.rst b/docs/history.rst old mode 100644 new mode 100755 diff --git a/docs/index.rst b/docs/index.rst old mode 100644 new mode 100755 diff --git a/docs/pkg/cheroot._compat.rst b/docs/pkg/cheroot._compat.rst old mode 100644 new mode 100755 diff --git a/docs/pkg/cheroot.errors.rst b/docs/pkg/cheroot.errors.rst old mode 100644 new mode 100755 diff --git a/docs/pkg/cheroot.makefile.rst b/docs/pkg/cheroot.makefile.rst old mode 100644 new mode 100755 diff --git a/docs/pkg/cheroot.server.rst b/docs/pkg/cheroot.server.rst old mode 100644 new mode 100755 diff --git a/docs/pkg/cheroot.ssl.builtin.rst b/docs/pkg/cheroot.ssl.builtin.rst old mode 100644 new mode 100755 diff --git a/docs/pkg/cheroot.ssl.pyopenssl.rst b/docs/pkg/cheroot.ssl.pyopenssl.rst old mode 100644 new mode 100755 diff --git a/docs/pkg/cheroot.ssl.rst b/docs/pkg/cheroot.ssl.rst old mode 100644 new mode 100755 diff --git a/docs/pkg/cheroot.testing.rst b/docs/pkg/cheroot.testing.rst old mode 100644 new mode 100755 diff --git a/docs/pkg/cheroot.wsgi.rst b/docs/pkg/cheroot.wsgi.rst old mode 100644 new mode 100755 diff --git a/pyproject.toml b/pyproject.toml old mode 100644 new mode 100755 diff --git a/pytest.ini b/pytest.ini old mode 100644 new mode 100755 diff --git a/setup.cfg b/setup.cfg old mode 100644 new mode 100755 diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 diff --git a/tox.ini b/tox.ini old mode 100644 new mode 100755 From 965be2e0147f9c3cc2c595713071832f9a332d62 Mon Sep 17 00:00:00 2001 From: Date: Mon, 1 Oct 2018 20:28:13 -0400 Subject: [PATCH 06/24] fix perms --- .appveyor.yml | 0 .circleci/config.yml | 0 .codecov.yml | 0 .git_archival.txt | 0 .gitattributes | 0 .github/CODE_OF_CONDUCT.md | 0 .github/CONTRIBUTING.rst | 0 .github/ISSUE_TEMPLATE.md | 0 .github/ISSUE_TEMPLATE/Bug_report.md | 0 .github/ISSUE_TEMPLATE/Feature_request.md | 0 .github/PULL_REQUEST_TEMPLATE.md | 0 .github/stale.yml | 0 .gitignore | 0 .mention-bot | 0 .pre-commit-config.yaml | 0 .pre-commit-config.yaml.failing | 0 .readthedocs.yml | 0 .travis.yml | 0 CHANGES.rst | 0 LICENSE.md | 0 README.rst | 0 cheroot/__init__.py | 0 cheroot/__main__.py | 0 cheroot/_compat.py | 0 cheroot/cli.py | 0 cheroot/errors.py | 0 cheroot/makefile.py | 0 cheroot/server.py | 0 cheroot/ssl/__init__.py | 0 cheroot/ssl/builtin.py | 0 cheroot/ssl/pyopenssl.py | 0 cheroot/test/__init__.py | 0 cheroot/test/conftest.py | 0 cheroot/test/helper.py | 0 cheroot/test/ssl/ca.cert | 0 cheroot/test/ssl/ca.key | 0 cheroot/test/ssl/client.cert | 0 cheroot/test/ssl/client.key | 0 cheroot/test/ssl/client_ip.cert | 0 cheroot/test/ssl/client_wildcard.cert | 0 cheroot/test/ssl/client_wrong_ca.cert | 0 cheroot/test/ssl/client_wrong_host.cert | 0 cheroot/test/ssl/server.cert | 0 cheroot/test/ssl/server.key | 0 cheroot/test/test.pem | 0 cheroot/test/test__compat.py | 0 cheroot/test/test_conn.py | 0 cheroot/test/test_core.py | 0 cheroot/test/test_errors.py | 0 cheroot/test/test_https.py | 0 cheroot/test/test_server.py | 0 cheroot/test/test_ssl.py | 0 cheroot/test/webtest.py | 0 cheroot/testing.py | 0 cheroot/workers/__init__.py | 0 cheroot/workers/threadpool.py | 0 cheroot/wsgi.py | 0 docs/conf.py | 0 docs/history.rst | 0 docs/index.rst | 0 docs/pkg/cheroot._compat.rst | 0 docs/pkg/cheroot.errors.rst | 0 docs/pkg/cheroot.makefile.rst | 0 docs/pkg/cheroot.server.rst | 0 docs/pkg/cheroot.ssl.builtin.rst | 0 docs/pkg/cheroot.ssl.pyopenssl.rst | 0 docs/pkg/cheroot.ssl.rst | 0 docs/pkg/cheroot.testing.rst | 0 docs/pkg/cheroot.wsgi.rst | 0 pyproject.toml | 0 pytest.ini | 0 setup.cfg | 0 setup.py | 0 tox.ini | 0 74 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 .appveyor.yml mode change 100755 => 100644 .circleci/config.yml mode change 100755 => 100644 .codecov.yml mode change 100755 => 100644 .git_archival.txt mode change 100755 => 100644 .gitattributes mode change 100755 => 100644 .github/CODE_OF_CONDUCT.md mode change 100755 => 100644 .github/CONTRIBUTING.rst mode change 100755 => 100644 .github/ISSUE_TEMPLATE.md mode change 100755 => 100644 .github/ISSUE_TEMPLATE/Bug_report.md mode change 100755 => 100644 .github/ISSUE_TEMPLATE/Feature_request.md mode change 100755 => 100644 .github/PULL_REQUEST_TEMPLATE.md mode change 100755 => 100644 .github/stale.yml mode change 100755 => 100644 .gitignore mode change 100755 => 100644 .mention-bot mode change 100755 => 100644 .pre-commit-config.yaml mode change 100755 => 100644 .pre-commit-config.yaml.failing mode change 100755 => 100644 .readthedocs.yml mode change 100755 => 100644 .travis.yml mode change 100755 => 100644 CHANGES.rst mode change 100755 => 100644 LICENSE.md mode change 100755 => 100644 README.rst mode change 100755 => 100644 cheroot/__init__.py mode change 100755 => 100644 cheroot/__main__.py mode change 100755 => 100644 cheroot/_compat.py mode change 100755 => 100644 cheroot/cli.py mode change 100755 => 100644 cheroot/errors.py mode change 100755 => 100644 cheroot/makefile.py mode change 100755 => 100644 cheroot/server.py mode change 100755 => 100644 cheroot/ssl/__init__.py mode change 100755 => 100644 cheroot/ssl/builtin.py mode change 100755 => 100644 cheroot/ssl/pyopenssl.py mode change 100755 => 100644 cheroot/test/__init__.py mode change 100755 => 100644 cheroot/test/conftest.py mode change 100755 => 100644 cheroot/test/helper.py mode change 100755 => 100644 cheroot/test/ssl/ca.cert mode change 100755 => 100644 cheroot/test/ssl/ca.key mode change 100755 => 100644 cheroot/test/ssl/client.cert mode change 100755 => 100644 cheroot/test/ssl/client.key mode change 100755 => 100644 cheroot/test/ssl/client_ip.cert mode change 100755 => 100644 cheroot/test/ssl/client_wildcard.cert mode change 100755 => 100644 cheroot/test/ssl/client_wrong_ca.cert mode change 100755 => 100644 cheroot/test/ssl/client_wrong_host.cert mode change 100755 => 100644 cheroot/test/ssl/server.cert mode change 100755 => 100644 cheroot/test/ssl/server.key mode change 100755 => 100644 cheroot/test/test.pem mode change 100755 => 100644 cheroot/test/test__compat.py mode change 100755 => 100644 cheroot/test/test_conn.py mode change 100755 => 100644 cheroot/test/test_core.py mode change 100755 => 100644 cheroot/test/test_errors.py mode change 100755 => 100644 cheroot/test/test_https.py mode change 100755 => 100644 cheroot/test/test_server.py mode change 100755 => 100644 cheroot/test/test_ssl.py mode change 100755 => 100644 cheroot/test/webtest.py mode change 100755 => 100644 cheroot/testing.py mode change 100755 => 100644 cheroot/workers/__init__.py mode change 100755 => 100644 cheroot/workers/threadpool.py mode change 100755 => 100644 cheroot/wsgi.py mode change 100755 => 100644 docs/conf.py mode change 100755 => 100644 docs/history.rst mode change 100755 => 100644 docs/index.rst mode change 100755 => 100644 docs/pkg/cheroot._compat.rst mode change 100755 => 100644 docs/pkg/cheroot.errors.rst mode change 100755 => 100644 docs/pkg/cheroot.makefile.rst mode change 100755 => 100644 docs/pkg/cheroot.server.rst mode change 100755 => 100644 docs/pkg/cheroot.ssl.builtin.rst mode change 100755 => 100644 docs/pkg/cheroot.ssl.pyopenssl.rst mode change 100755 => 100644 docs/pkg/cheroot.ssl.rst mode change 100755 => 100644 docs/pkg/cheroot.testing.rst mode change 100755 => 100644 docs/pkg/cheroot.wsgi.rst mode change 100755 => 100644 pyproject.toml mode change 100755 => 100644 pytest.ini mode change 100755 => 100644 setup.cfg mode change 100755 => 100644 setup.py mode change 100755 => 100644 tox.ini diff --git a/.appveyor.yml b/.appveyor.yml old mode 100755 new mode 100644 diff --git a/.circleci/config.yml b/.circleci/config.yml old mode 100755 new mode 100644 diff --git a/.codecov.yml b/.codecov.yml old mode 100755 new mode 100644 diff --git a/.git_archival.txt b/.git_archival.txt old mode 100755 new mode 100644 diff --git a/.gitattributes b/.gitattributes old mode 100755 new mode 100644 diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md old mode 100755 new mode 100644 diff --git a/.github/CONTRIBUTING.rst b/.github/CONTRIBUTING.rst old mode 100755 new mode 100644 diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md old mode 100755 new mode 100644 diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md old mode 100755 new mode 100644 diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md old mode 100755 new mode 100644 diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md old mode 100755 new mode 100644 diff --git a/.github/stale.yml b/.github/stale.yml old mode 100755 new mode 100644 diff --git a/.gitignore b/.gitignore old mode 100755 new mode 100644 diff --git a/.mention-bot b/.mention-bot old mode 100755 new mode 100644 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml old mode 100755 new mode 100644 diff --git a/.pre-commit-config.yaml.failing b/.pre-commit-config.yaml.failing old mode 100755 new mode 100644 diff --git a/.readthedocs.yml b/.readthedocs.yml old mode 100755 new mode 100644 diff --git a/.travis.yml b/.travis.yml old mode 100755 new mode 100644 diff --git a/CHANGES.rst b/CHANGES.rst old mode 100755 new mode 100644 diff --git a/LICENSE.md b/LICENSE.md old mode 100755 new mode 100644 diff --git a/README.rst b/README.rst old mode 100755 new mode 100644 diff --git a/cheroot/__init__.py b/cheroot/__init__.py old mode 100755 new mode 100644 diff --git a/cheroot/__main__.py b/cheroot/__main__.py old mode 100755 new mode 100644 diff --git a/cheroot/_compat.py b/cheroot/_compat.py old mode 100755 new mode 100644 diff --git a/cheroot/cli.py b/cheroot/cli.py old mode 100755 new mode 100644 diff --git a/cheroot/errors.py b/cheroot/errors.py old mode 100755 new mode 100644 diff --git a/cheroot/makefile.py b/cheroot/makefile.py old mode 100755 new mode 100644 diff --git a/cheroot/server.py b/cheroot/server.py old mode 100755 new mode 100644 diff --git a/cheroot/ssl/__init__.py b/cheroot/ssl/__init__.py old mode 100755 new mode 100644 diff --git a/cheroot/ssl/builtin.py b/cheroot/ssl/builtin.py old mode 100755 new mode 100644 diff --git a/cheroot/ssl/pyopenssl.py b/cheroot/ssl/pyopenssl.py old mode 100755 new mode 100644 diff --git a/cheroot/test/__init__.py b/cheroot/test/__init__.py old mode 100755 new mode 100644 diff --git a/cheroot/test/conftest.py b/cheroot/test/conftest.py old mode 100755 new mode 100644 diff --git a/cheroot/test/helper.py b/cheroot/test/helper.py old mode 100755 new mode 100644 diff --git a/cheroot/test/ssl/ca.cert b/cheroot/test/ssl/ca.cert old mode 100755 new mode 100644 diff --git a/cheroot/test/ssl/ca.key b/cheroot/test/ssl/ca.key old mode 100755 new mode 100644 diff --git a/cheroot/test/ssl/client.cert b/cheroot/test/ssl/client.cert old mode 100755 new mode 100644 diff --git a/cheroot/test/ssl/client.key b/cheroot/test/ssl/client.key old mode 100755 new mode 100644 diff --git a/cheroot/test/ssl/client_ip.cert b/cheroot/test/ssl/client_ip.cert old mode 100755 new mode 100644 diff --git a/cheroot/test/ssl/client_wildcard.cert b/cheroot/test/ssl/client_wildcard.cert old mode 100755 new mode 100644 diff --git a/cheroot/test/ssl/client_wrong_ca.cert b/cheroot/test/ssl/client_wrong_ca.cert old mode 100755 new mode 100644 diff --git a/cheroot/test/ssl/client_wrong_host.cert b/cheroot/test/ssl/client_wrong_host.cert old mode 100755 new mode 100644 diff --git a/cheroot/test/ssl/server.cert b/cheroot/test/ssl/server.cert old mode 100755 new mode 100644 diff --git a/cheroot/test/ssl/server.key b/cheroot/test/ssl/server.key old mode 100755 new mode 100644 diff --git a/cheroot/test/test.pem b/cheroot/test/test.pem old mode 100755 new mode 100644 diff --git a/cheroot/test/test__compat.py b/cheroot/test/test__compat.py old mode 100755 new mode 100644 diff --git a/cheroot/test/test_conn.py b/cheroot/test/test_conn.py old mode 100755 new mode 100644 diff --git a/cheroot/test/test_core.py b/cheroot/test/test_core.py old mode 100755 new mode 100644 diff --git a/cheroot/test/test_errors.py b/cheroot/test/test_errors.py old mode 100755 new mode 100644 diff --git a/cheroot/test/test_https.py b/cheroot/test/test_https.py old mode 100755 new mode 100644 diff --git a/cheroot/test/test_server.py b/cheroot/test/test_server.py old mode 100755 new mode 100644 diff --git a/cheroot/test/test_ssl.py b/cheroot/test/test_ssl.py old mode 100755 new mode 100644 diff --git a/cheroot/test/webtest.py b/cheroot/test/webtest.py old mode 100755 new mode 100644 diff --git a/cheroot/testing.py b/cheroot/testing.py old mode 100755 new mode 100644 diff --git a/cheroot/workers/__init__.py b/cheroot/workers/__init__.py old mode 100755 new mode 100644 diff --git a/cheroot/workers/threadpool.py b/cheroot/workers/threadpool.py old mode 100755 new mode 100644 diff --git a/cheroot/wsgi.py b/cheroot/wsgi.py old mode 100755 new mode 100644 diff --git a/docs/conf.py b/docs/conf.py old mode 100755 new mode 100644 diff --git a/docs/history.rst b/docs/history.rst old mode 100755 new mode 100644 diff --git a/docs/index.rst b/docs/index.rst old mode 100755 new mode 100644 diff --git a/docs/pkg/cheroot._compat.rst b/docs/pkg/cheroot._compat.rst old mode 100755 new mode 100644 diff --git a/docs/pkg/cheroot.errors.rst b/docs/pkg/cheroot.errors.rst old mode 100755 new mode 100644 diff --git a/docs/pkg/cheroot.makefile.rst b/docs/pkg/cheroot.makefile.rst old mode 100755 new mode 100644 diff --git a/docs/pkg/cheroot.server.rst b/docs/pkg/cheroot.server.rst old mode 100755 new mode 100644 diff --git a/docs/pkg/cheroot.ssl.builtin.rst b/docs/pkg/cheroot.ssl.builtin.rst old mode 100755 new mode 100644 diff --git a/docs/pkg/cheroot.ssl.pyopenssl.rst b/docs/pkg/cheroot.ssl.pyopenssl.rst old mode 100755 new mode 100644 diff --git a/docs/pkg/cheroot.ssl.rst b/docs/pkg/cheroot.ssl.rst old mode 100755 new mode 100644 diff --git a/docs/pkg/cheroot.testing.rst b/docs/pkg/cheroot.testing.rst old mode 100755 new mode 100644 diff --git a/docs/pkg/cheroot.wsgi.rst b/docs/pkg/cheroot.wsgi.rst old mode 100755 new mode 100644 diff --git a/pyproject.toml b/pyproject.toml old mode 100755 new mode 100644 diff --git a/pytest.ini b/pytest.ini old mode 100755 new mode 100644 diff --git a/setup.cfg b/setup.cfg old mode 100755 new mode 100644 diff --git a/setup.py b/setup.py old mode 100755 new mode 100644 diff --git a/tox.ini b/tox.ini old mode 100755 new mode 100644 From c3dd1c7bdb141f1e32ca39989e90ca36d3ded3f6 Mon Sep 17 00:00:00 2001 From: Daniel Vinakovsky Date: Sun, 7 Oct 2018 12:22:24 -0400 Subject: [PATCH 07/24] refactor process_conns and use socket.gettimeout --- cheroot/workers/threadpool.py | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/cheroot/workers/threadpool.py b/cheroot/workers/threadpool.py index 97c02ddff8..04b610ae38 100644 --- a/cheroot/workers/threadpool.py +++ b/cheroot/workers/threadpool.py @@ -93,13 +93,25 @@ def __init__(self, server): } threading.Thread.__init__(self) - def close_expired_conns(self, conn_socks): - cur_time = time.time() + def log_start_stats(self): + if self.server.stats['Enabled']: + self.start_time = time.time() + + def log_close_stats(self): + if self.server.stats['Enabled']: + self.requests_seen += self.conn.requests_seen + self.bytes_read += self.conn.rfile.bytes_read + self.bytes_written += self.conn.wfile.bytes_written + self.work_time += time.time() - self.start_time + self.start_time = None + + def close_expired_conns(self, conn_socks, cur_time): for c, last_active in list(conn_socks.values()): sock = c.socket srv_timeout = self.server.timeout - if (sock.timeout == 0.0 and cur_time - last_active > 60 or - sock.timeout and cur_time - last_active > srv_timeout): + sock_timeout = sock.gettimeout() + if (sock_timeout == 0.0 and cur_time - last_active > 60 or + sock_timeout and cur_time - last_active > srv_timeout): c.close() conn_socks.pop(c.socket) @@ -111,8 +123,7 @@ def process_conns(self, conn_socks): for sock in rlist: conn, conn_start_time = conn_socks[sock] self.conn = conn - if self.server.stats['Enabled']: - self.start_time = time.time() + self.log_start_stats() try: err_occurred = conn.communicate() is False finally: @@ -121,12 +132,7 @@ def process_conns(self, conn_socks): conn_socks.pop(conn.socket) else: conn_socks[conn.socket] = (conn, time.time()) - if self.server.stats['Enabled']: - self.requests_seen += self.conn.requests_seen - self.bytes_read += self.conn.rfile.bytes_read - self.bytes_written += self.conn.wfile.bytes_written - self.work_time += time.time() - self.start_time - self.start_time = None + self.log_close_stats() self.conn = None def run(self): @@ -148,7 +154,7 @@ def run(self): return conn_socks[conn.socket] = (conn, time.time()) self.process_conns(conn_socks) - self.close_expired_conns(conn_socks) + self.close_expired_conns(conn_socks, time.time()) except (KeyboardInterrupt, SystemExit) as ex: self.server.interrupt = ex From e89db2c3d31ffec65c1d6fc0e2c89f609957dac7 Mon Sep 17 00:00:00 2001 From: Daniel Vinakovsky Date: Sun, 7 Oct 2018 12:55:13 -0400 Subject: [PATCH 08/24] remove unnecessary variable, better var naming --- cheroot/workers/threadpool.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/cheroot/workers/threadpool.py b/cheroot/workers/threadpool.py index 04b610ae38..1925b29fb2 100644 --- a/cheroot/workers/threadpool.py +++ b/cheroot/workers/threadpool.py @@ -106,14 +106,13 @@ def log_close_stats(self): self.start_time = None def close_expired_conns(self, conn_socks, cur_time): - for c, last_active in list(conn_socks.values()): - sock = c.socket + for conn, last_active in list(conn_socks.values()): srv_timeout = self.server.timeout - sock_timeout = sock.gettimeout() + sock_timeout = conn.socket.gettimeout() if (sock_timeout == 0.0 and cur_time - last_active > 60 or sock_timeout and cur_time - last_active > srv_timeout): - c.close() - conn_socks.pop(c.socket) + conn.close() + conn_socks.pop(conn.socket) def process_conns(self, conn_socks): rlist = [] From 9c347f31dbe0495b88ef575ae98c25e72003633b Mon Sep 17 00:00:00 2001 From: Daniel Vinakovsky Date: Sun, 7 Oct 2018 13:02:29 -0400 Subject: [PATCH 09/24] refactor close_expired_conns --- cheroot/workers/threadpool.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/cheroot/workers/threadpool.py b/cheroot/workers/threadpool.py index 1925b29fb2..2e3fef3f97 100644 --- a/cheroot/workers/threadpool.py +++ b/cheroot/workers/threadpool.py @@ -105,14 +105,18 @@ def log_close_stats(self): self.work_time += time.time() - self.start_time self.start_time = None - def close_expired_conns(self, conn_socks, cur_time): - for conn, last_active in list(conn_socks.values()): + def get_expired_conns(self, conn_socks, cur_time): + for conn, last_active in tuple(conn_socks.values()): srv_timeout = self.server.timeout sock_timeout = conn.socket.gettimeout() if (sock_timeout == 0.0 and cur_time - last_active > 60 or sock_timeout and cur_time - last_active > srv_timeout): - conn.close() - conn_socks.pop(conn.socket) + yield conn + + def close_conns(conn_list, conn_socks): + for conn in conn_list: + conn.close() + conn_socks.pop(conn.socket) def process_conns(self, conn_socks): rlist = [] @@ -153,7 +157,8 @@ def run(self): return conn_socks[conn.socket] = (conn, time.time()) self.process_conns(conn_socks) - self.close_expired_conns(conn_socks, time.time()) + expired_conns = self.get_expired_conns(conn_socks, time.time()) + self.close_conns(expired_conns, conn_socks) except (KeyboardInterrupt, SystemExit) as ex: self.server.interrupt = ex From c3bd269af5feb59c5b431ecfbb2383be7b3f1fcb Mon Sep 17 00:00:00 2001 From: Daniel Vinakovsky Date: Sun, 7 Oct 2018 13:06:12 -0400 Subject: [PATCH 10/24] attempt to lower cog complexity --- cheroot/workers/threadpool.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/cheroot/workers/threadpool.py b/cheroot/workers/threadpool.py index 2e3fef3f97..96dfeb26c8 100644 --- a/cheroot/workers/threadpool.py +++ b/cheroot/workers/threadpool.py @@ -105,12 +105,15 @@ def log_close_stats(self): self.work_time += time.time() - self.start_time self.start_time = None + def conn_expired(self, conn, last_active, cur_time): + srv_timeout = self.server.timeout + sock_timeout = conn.socket.gettimeout() + return (sock_timeout == 0.0 and cur_time - last_active > 60 or + sock_timeout and cur_time - last_active > srv_timeout) + def get_expired_conns(self, conn_socks, cur_time): for conn, last_active in tuple(conn_socks.values()): - srv_timeout = self.server.timeout - sock_timeout = conn.socket.gettimeout() - if (sock_timeout == 0.0 and cur_time - last_active > 60 or - sock_timeout and cur_time - last_active > srv_timeout): + if self.conn_expired(conn, last_active, cur_time): yield conn def close_conns(conn_list, conn_socks): From 18c429c7cffe94752c2613485e67617c75529949 Mon Sep 17 00:00:00 2001 From: Daniel Vinakovsky Date: Sun, 7 Oct 2018 13:18:34 -0400 Subject: [PATCH 11/24] oops --- cheroot/workers/threadpool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cheroot/workers/threadpool.py b/cheroot/workers/threadpool.py index 96dfeb26c8..030f66f199 100644 --- a/cheroot/workers/threadpool.py +++ b/cheroot/workers/threadpool.py @@ -116,7 +116,7 @@ def get_expired_conns(self, conn_socks, cur_time): if self.conn_expired(conn, last_active, cur_time): yield conn - def close_conns(conn_list, conn_socks): + def close_conns(self, conn_list, conn_socks): for conn in conn_list: conn.close() conn_socks.pop(conn.socket) From 161eac64867f12dd27fd91c14da83de1b1034956 Mon Sep 17 00:00:00 2001 From: Dan Vinakovsky Date: Mon, 8 Oct 2018 09:16:54 -0400 Subject: [PATCH 12/24] lines From 8aaa9778a8f6c30bbd79d32536a121c59d53fcb2 Mon Sep 17 00:00:00 2001 From: Daniel Vinakovsky Date: Mon, 8 Oct 2018 20:22:31 -0400 Subject: [PATCH 13/24] revert changes to issue template --- .github/ISSUE_TEMPLATE/Bug_report.md | 118 +++++++++++----------- .github/ISSUE_TEMPLATE/Feature_request.md | 90 ++++++++--------- 2 files changed, 104 insertions(+), 104 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md index 286dbbbe24..6bdea233b0 100644 --- a/.github/ISSUE_TEMPLATE/Bug_report.md +++ b/.github/ISSUE_TEMPLATE/Bug_report.md @@ -1,59 +1,59 @@ ---- -name: "\U0001F41B Bug report" -about: Create a report to help us improve - ---- - - -**I'm submitting a ...** -- [X] bug report -- [ ] feature request -- [ ] question about the decisions made in the repository - -**Describe the bug. What is the current behavior?** - - -**What is the motivation / use case for changing the behavior?** - - -**To Reproduce** - -Steps to reproduce the behavior: -1. Run '...' -2. Make request '....' -3. See error - -**Expected behavior** - - -**Details** - - -**Environment** - -- Cheroot version: X.X.X -- CherryPy version: X.X.X (if applicable) -- Python version: 3.6.X -- OS: XXX -- Browser: [all | Chrome XX | Firefox XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView ] - -**Additional context** - +--- +name: "\U0001F41B Bug report" +about: Create a report to help us improve + +--- + + +**I'm submitting a ...** +- [X] bug report +- [ ] feature request +- [ ] question about the decisions made in the repository + +**Describe the bug. What is the current behavior?** + + +**What is the motivation / use case for changing the behavior?** + + +**To Reproduce** + +Steps to reproduce the behavior: +1. Run '...' +2. Make request '....' +3. See error + +**Expected behavior** + + +**Details** + + +**Environment** + +- Cheroot version: X.X.X +- CherryPy version: X.X.X (if applicable) +- Python version: 3.6.X +- OS: XXX +- Browser: [all | Chrome XX | Firefox XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView ] + +**Additional context** + diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md index ce822bf84d..3f491af799 100644 --- a/.github/ISSUE_TEMPLATE/Feature_request.md +++ b/.github/ISSUE_TEMPLATE/Feature_request.md @@ -1,45 +1,45 @@ ---- -name: "\U0001F680 Feature request" -about: Suggest an idea for this project - ---- - - - -**I'm submitting a ...** -- [ ] bug report -- [X] feature request -- [ ] question about the decisions made in the repository - - - -**Is your feature request related to a problem? Please describe.** - - - - -**Describe the solution you'd like** - - - -**Describe alternatives you've considered** - - - - -**Additional context** - +--- +name: "\U0001F680 Feature request" +about: Suggest an idea for this project + +--- + + + +**I'm submitting a ...** +- [ ] bug report +- [X] feature request +- [ ] question about the decisions made in the repository + + + +**Is your feature request related to a problem? Please describe.** + + + + +**Describe the solution you'd like** + + + +**Describe alternatives you've considered** + + + + +**Additional context** + From 0a2eea44b1dca5b0c8f30e4dfd84864320f2f2f8 Mon Sep 17 00:00:00 2001 From: Dan Vinakovsky Date: Thu, 11 Oct 2018 11:42:19 -0400 Subject: [PATCH 14/24] return False only when a non-zero timeout is specified --- cheroot/server.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cheroot/server.py b/cheroot/server.py index cd07dca73b..7543966521 100644 --- a/cheroot/server.py +++ b/cheroot/server.py @@ -1219,11 +1219,14 @@ def communicate(self): req.parse_request() if self.server.stats['Enabled']: self.requests_seen += 1 - if not req.ready: + if not req.ready and self.server.timeout: # Something went wrong in the parsing (and the server has # probably already made a simple_response). Return and # let the conn close. - # return False + return False + elif not req.ready and self.server.timeout == 0.0: + # Allow conn to stay open to support non-blocking + # sockets return True request_seen = True From 368b607ba138718c0b428a20d784cce899d5e6df Mon Sep 17 00:00:00 2001 From: Dan Vinakovsky Date: Wed, 17 Oct 2018 08:48:43 -0400 Subject: [PATCH 15/24] collapse if branches --- cheroot/server.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/cheroot/server.py b/cheroot/server.py index 7543966521..7dce858a70 100644 --- a/cheroot/server.py +++ b/cheroot/server.py @@ -1219,15 +1219,11 @@ def communicate(self): req.parse_request() if self.server.stats['Enabled']: self.requests_seen += 1 - if not req.ready and self.server.timeout: - # Something went wrong in the parsing (and the server has - # probably already made a simple_response). Return and - # let the conn close. - return False - elif not req.ready and self.server.timeout == 0.0: + if not req.ready: # Allow conn to stay open to support non-blocking - # sockets - return True + # sockets if timeout is 0, otherwise return and + # let the conn close. + return self.server.timeout == 0.0 request_seen = True req.respond() From 00654cc96a23b3a48eee49c5c24dc2f58a87567b Mon Sep 17 00:00:00 2001 From: Daniel Vinakovsky Date: Thu, 25 Oct 2018 10:53:31 -0400 Subject: [PATCH 16/24] test_HTTP11_Timeout passes now --- cheroot/server.py | 7 ++----- cheroot/workers/threadpool.py | 5 ++--- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/cheroot/server.py b/cheroot/server.py index 7dce858a70..b103798f9a 100644 --- a/cheroot/server.py +++ b/cheroot/server.py @@ -696,7 +696,6 @@ def parse_request(self): else: if not success: return - try: success = self.read_request_headers() except errors.MaxSizeExceeded: @@ -1220,10 +1219,8 @@ def communicate(self): if self.server.stats['Enabled']: self.requests_seen += 1 if not req.ready: - # Allow conn to stay open to support non-blocking - # sockets if timeout is 0, otherwise return and - # let the conn close. - return self.server.timeout == 0.0 + # Allow conn to stay open to support non-blocking sockets + return True request_seen = True req.respond() diff --git a/cheroot/workers/threadpool.py b/cheroot/workers/threadpool.py index 030f66f199..8d2d86fee4 100644 --- a/cheroot/workers/threadpool.py +++ b/cheroot/workers/threadpool.py @@ -107,9 +107,7 @@ def log_close_stats(self): def conn_expired(self, conn, last_active, cur_time): srv_timeout = self.server.timeout - sock_timeout = conn.socket.gettimeout() - return (sock_timeout == 0.0 and cur_time - last_active > 60 or - sock_timeout and cur_time - last_active > srv_timeout) + return cur_time - last_active > srv_timeout def get_expired_conns(self, conn_socks, cur_time): for conn, last_active in tuple(conn_socks.values()): @@ -118,6 +116,7 @@ def get_expired_conns(self, conn_socks, cur_time): def close_conns(self, conn_list, conn_socks): for conn in conn_list: + conn.communicate() # allow for 408 to be sent conn.close() conn_socks.pop(conn.socket) From 12471991b2b202ea1a5bb21c818f59b54415e207 Mon Sep 17 00:00:00 2001 From: Dan Vinakovsky Date: Thu, 25 Oct 2018 11:07:16 -0400 Subject: [PATCH 17/24] lint --- cheroot/workers/threadpool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cheroot/workers/threadpool.py b/cheroot/workers/threadpool.py index 8d2d86fee4..9d48317357 100644 --- a/cheroot/workers/threadpool.py +++ b/cheroot/workers/threadpool.py @@ -116,7 +116,7 @@ def get_expired_conns(self, conn_socks, cur_time): def close_conns(self, conn_list, conn_socks): for conn in conn_list: - conn.communicate() # allow for 408 to be sent + conn.communicate() # allow for 408 to be sent conn.close() conn_socks.pop(conn.socket) From 1be10238164f82a7d83bfa8a404a3f1f9961cdf3 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 4 Feb 2019 14:13:09 -0500 Subject: [PATCH 18/24] Restore inconsequential newline --- cheroot/server.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cheroot/server.py b/cheroot/server.py index b103798f9a..06638424d4 100644 --- a/cheroot/server.py +++ b/cheroot/server.py @@ -696,6 +696,7 @@ def parse_request(self): else: if not success: return + try: success = self.read_request_headers() except errors.MaxSizeExceeded: From 1dd9fd22fc1ece9a853b17f176a8a0896ae31a98 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 4 Feb 2019 17:15:42 -0500 Subject: [PATCH 19/24] Rely on Exceptions to trap exceptional conditions in WorkerThread.process_conns. --- cheroot/errors.py | 4 ++++ cheroot/server.py | 9 ++++----- cheroot/workers/threadpool.py | 17 ++++++++--------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/cheroot/errors.py b/cheroot/errors.py index 809287319d..e0cc8c495b 100644 --- a/cheroot/errors.py +++ b/cheroot/errors.py @@ -22,6 +22,10 @@ class FatalSSLAlert(Exception): """Exception raised when the SSL implementation signals a fatal alert.""" +class CloseConnection(Exception): + """Exception raised when client requested the connection to be closed.""" + + def plat_specific_errors(*errnames): """Return error numbers for all errors in errnames on this platform. diff --git a/cheroot/server.py b/cheroot/server.py index 3efe239456..65e9365646 100644 --- a/cheroot/server.py +++ b/cheroot/server.py @@ -1265,12 +1265,12 @@ def communicate(self): self.requests_seen += 1 if not req.ready: # Allow conn to stay open to support non-blocking sockets - return True + return request_seen = True req.respond() if req.close_connection: - return False + raise errors.CloseConnection() except socket.error as ex: errnum = ex.args[0] # sadly SSL sockets return a different (longer) time out string @@ -1288,7 +1288,7 @@ def communicate(self): level=logging.WARNING, traceback=True, ) self._conditional_error(req, '500 Internal Server Error') - return False + raise except (KeyboardInterrupt, SystemExit): raise except errors.FatalSSLAlert: @@ -1300,8 +1300,7 @@ def communicate(self): repr(ex), level=logging.ERROR, traceback=True, ) self._conditional_error(req, '500 Internal Server Error') - return False - return True + raise linger = False diff --git a/cheroot/workers/threadpool.py b/cheroot/workers/threadpool.py index fd547d181d..36fbbd347b 100644 --- a/cheroot/workers/threadpool.py +++ b/cheroot/workers/threadpool.py @@ -132,15 +132,14 @@ def process_conns(self, conn_socks): self.conn = conn self.log_start_stats() try: - err_occurred = conn.communicate() is False - finally: - if err_occurred: - conn.close() - conn_socks.pop(conn.socket) - else: - conn_socks[conn.socket] = (conn, time.time()) - self.log_close_stats() - self.conn = None + conn.communicate() + except Exception: + conn.close() + conn_socks.pop(conn.socket) + else: + conn_socks[conn.socket] = (conn, time.time()) + self.log_close_stats() + self.conn = None def run(self): """Process incoming HTTP connections. From d83398eb63d1d271f9e8138fa9fef2dd2986bb48 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 4 Feb 2019 17:19:14 -0500 Subject: [PATCH 20/24] Remove unused parameter --- cheroot/workers/threadpool.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cheroot/workers/threadpool.py b/cheroot/workers/threadpool.py index 36fbbd347b..8caef6bbb0 100644 --- a/cheroot/workers/threadpool.py +++ b/cheroot/workers/threadpool.py @@ -107,13 +107,13 @@ def log_close_stats(self): self.work_time += time.time() - self.start_time self.start_time = None - def conn_expired(self, conn, last_active, cur_time): + def conn_expired(self, last_active, cur_time): srv_timeout = self.server.timeout return cur_time - last_active > srv_timeout def get_expired_conns(self, conn_socks, cur_time): for conn, last_active in tuple(conn_socks.values()): - if self.conn_expired(conn, last_active, cur_time): + if self.conn_expired(last_active, cur_time): yield conn def close_conns(self, conn_list, conn_socks): From 14bcf2a5c1034e23fddc621616b68f66d04d20be Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 4 Feb 2019 17:23:30 -0500 Subject: [PATCH 21/24] Extract 'if stats enabled' logic into a decorator --- cheroot/workers/threadpool.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/cheroot/workers/threadpool.py b/cheroot/workers/threadpool.py index 8caef6bbb0..efabf6ecca 100644 --- a/cheroot/workers/threadpool.py +++ b/cheroot/workers/threadpool.py @@ -8,6 +8,7 @@ import time import socket import select +import functools from six.moves import queue @@ -95,17 +96,23 @@ def __init__(self, server): } threading.Thread.__init__(self) + def if_stats(func): + @functools.wraps(func) + def wrapper(self, *args, **kwargs): + return self.server.stats['Enabled'] and func(self, *args, **kwargs) + return wrapper + + @if_stats def log_start_stats(self): - if self.server.stats['Enabled']: - self.start_time = time.time() + self.start_time = time.time() + @if_stats def log_close_stats(self): - if self.server.stats['Enabled']: - self.requests_seen += self.conn.requests_seen - self.bytes_read += self.conn.rfile.bytes_read - self.bytes_written += self.conn.wfile.bytes_written - self.work_time += time.time() - self.start_time - self.start_time = None + self.requests_seen += self.conn.requests_seen + self.bytes_read += self.conn.rfile.bytes_read + self.bytes_written += self.conn.wfile.bytes_written + self.work_time += time.time() - self.start_time + self.start_time = None def conn_expired(self, last_active, cur_time): srv_timeout = self.server.timeout From d1fc2a506a749de0bb57fd7ef409215453587e5e Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 14 Feb 2019 09:30:12 -0500 Subject: [PATCH 22/24] Ran pre-commit --- cheroot/test/test_ssl.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cheroot/test/test_ssl.py b/cheroot/test/test_ssl.py index 973e0e9185..fa45f6fc21 100644 --- a/cheroot/test/test_ssl.py +++ b/cheroot/test/test_ssl.py @@ -291,7 +291,8 @@ def test_tls_client_auth( make_https_request() except OpenSSL.SSL.Error: pytest.xfail( - reason="https://github.com/cherrypy/cheroot/issues/173") + reason='https://github.com/cherrypy/cheroot/issues/173', + ) err_text = ssl_err.value.args[0].reason.args[0].args[0] From 05f446a9c1e9facab2a4a681aa5bc097ce664578 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 14 Feb 2019 09:36:05 -0500 Subject: [PATCH 23/24] Satisfy the linter's need for docstrings --- cheroot/workers/threadpool.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cheroot/workers/threadpool.py b/cheroot/workers/threadpool.py index efabf6ecca..90ee498d3c 100644 --- a/cheroot/workers/threadpool.py +++ b/cheroot/workers/threadpool.py @@ -97,6 +97,7 @@ def __init__(self, server): threading.Thread.__init__(self) def if_stats(func): + """Decorate the function to only invoke if stats are enabled.""" @functools.wraps(func) def wrapper(self, *args, **kwargs): return self.server.stats['Enabled'] and func(self, *args, **kwargs) @@ -104,10 +105,12 @@ def wrapper(self, *args, **kwargs): @if_stats def log_start_stats(self): + """Record the start time.""" self.start_time = time.time() @if_stats def log_close_stats(self): + """On close, record the stats.""" self.requests_seen += self.conn.requests_seen self.bytes_read += self.conn.rfile.bytes_read self.bytes_written += self.conn.wfile.bytes_written @@ -115,21 +118,25 @@ def log_close_stats(self): self.start_time = None def conn_expired(self, last_active, cur_time): + """Return True if the connection has expired.""" srv_timeout = self.server.timeout return cur_time - last_active > srv_timeout def get_expired_conns(self, conn_socks, cur_time): + """Generate all expired connections.""" for conn, last_active in tuple(conn_socks.values()): if self.conn_expired(last_active, cur_time): yield conn def close_conns(self, conn_list, conn_socks): + """Close all connections and associated sockets.""" for conn in conn_list: conn.communicate() # allow for 408 to be sent conn.close() conn_socks.pop(conn.socket) def process_conns(self, conn_socks): + """Process connections.""" rlist = [] socks = [sck for sck in conn_socks.keys() if sck.fileno() > -1] if socks: From 9413ed9c4859e422edc20cff8a919da5af682129 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 30 Apr 2019 02:03:53 -0400 Subject: [PATCH 24/24] Update changelog. --- CHANGES.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 63b47b8ca5..a7de7a59e5 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,10 @@ +v6.7.0 +====== + +- :issue:`91` via :pr:`176`: Server now processes sockets in a + non-blocking manner, improving support for keep-alive connections + on HTTP 1.1 especially when operating under load. + v6.5.5 ======