summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBert JW Regeer <bertjw@regeer.org>2020-08-16 01:59:25 -0700
committerBert JW Regeer <bertjw@regeer.org>2020-08-16 16:49:07 -0700
commit4d2b2867933dc2f73c0755e137184fe0ea21f203 (patch)
treed5797538657083d246ec9406b12573de7948ce95
parent15dc1e82356fd0b0dc763480d42d6542261f28e6 (diff)
downloadwaitress-4d2b2867933dc2f73c0755e137184fe0ea21f203.tar.gz
Stop marking socket as readable when flushing data
We no longer mark the socket as readable if we are attempting to flush whatever remaining data we have and are trying to shut down the channel. Whatever data is ready to be read, it's no longer our concern. We don't want to spend time reading data we don't care about.
-rw-r--r--src/waitress/channel.py45
1 files changed, 42 insertions, 3 deletions
diff --git a/src/waitress/channel.py b/src/waitress/channel.py
index 7332e40..d756b96 100644
--- a/src/waitress/channel.py
+++ b/src/waitress/channel.py
@@ -76,16 +76,20 @@ class HTTPChannel(wasyncore.dispatcher):
# if there's data in the out buffer or we've been instructed to close
# the channel (possibly by our server maintenance logic), run
# handle_write
+
return self.total_outbufs_len or self.will_close or self.close_when_flushed
def handle_write(self):
# Precondition: there's data in the out buffer to be sent, or
# there's a pending will_close request
+
if not self.connected:
# we dont want to close the channel twice
+
return
# try to flush any pending output
+
if not self.requests:
# 1. There are no running tasks, so we don't need to try to lock
# the outbuf before sending
@@ -125,10 +129,18 @@ class HTTPChannel(wasyncore.dispatcher):
def readable(self):
# We might want to create a new task. We can only do this if:
# 1. We're not already about to close the connection.
- # 2. There's no already currently running task(s).
- # 3. There's no data in the output buffer that needs to be sent
+ # 2. We're not waiting to flush remaining data before closing the
+ # connection
+ # 3. There's no already currently running task(s).
+ # 4. There's no data in the output buffer that needs to be sent
# before we potentially create a new task.
- return not (self.will_close or self.requests or self.total_outbufs_len)
+
+ return not (
+ self.will_close
+ or self.close_when_flushed
+ or self.requests
+ or self.total_outbufs_len
+ )
def handle_read(self):
try:
@@ -137,7 +149,9 @@ class HTTPChannel(wasyncore.dispatcher):
if self.adj.log_socket_errors:
self.logger.exception("Socket error")
self.handle_close()
+
return
+
if data:
self.last_activity = time.time()
self.received(data)
@@ -158,9 +172,11 @@ class HTTPChannel(wasyncore.dispatcher):
if request is None:
request = self.parser_class(self.adj)
n = request.received(data)
+
if request.expect_continue and request.headers_finished:
# guaranteed by parser to be a 1.1 request
request.expect_continue = False
+
if not self.sent_continue:
# there's no current task, so we don't need to try to
# lock the outbuf to append to it.
@@ -172,14 +188,17 @@ class HTTPChannel(wasyncore.dispatcher):
self.sent_continue = True
self._flush_some()
request.completed = False
+
if request.completed:
# The request (with the body) is ready to use.
self.request = None
+
if not request.empty:
requests.append(request)
request = None
else:
self.request = request
+
if n >= len(data):
break
data = data[n:]
@@ -193,6 +212,7 @@ class HTTPChannel(wasyncore.dispatcher):
def _flush_some_if_lockable(self):
# Since our task may be appending to the outbuf, we try to acquire
# the lock, but we don't block if we can't.
+
if self.outbuf_lock.acquire(False):
try:
self._flush_some()
@@ -213,9 +233,11 @@ class HTTPChannel(wasyncore.dispatcher):
# use outbuf.__len__ rather than len(outbuf) FBO of not getting
# OverflowError on 32-bit Python
outbuflen = outbuf.__len__()
+
while outbuflen > 0:
chunk = outbuf.get(self.sendbuf_len)
num_sent = self.send(chunk)
+
if num_sent:
outbuf.skip(num_sent, True)
outbuflen -= num_sent
@@ -224,9 +246,11 @@ class HTTPChannel(wasyncore.dispatcher):
else:
# failed to write anything, break out entirely
dobreak = True
+
break
else:
# self.outbufs[-1] must always be a writable outbuf
+
if len(self.outbufs) > 1:
toclose = self.outbufs.pop(0)
try:
@@ -242,6 +266,7 @@ class HTTPChannel(wasyncore.dispatcher):
if sent:
self.last_activity = time.time()
+
return True
return False
@@ -276,6 +301,7 @@ class HTTPChannel(wasyncore.dispatcher):
fd = self._fileno # next line sets this to None
wasyncore.dispatcher.del_channel(self, map)
ac = self.server.active_channels
+
if fd in ac:
del ac[fd]
@@ -288,14 +314,17 @@ class HTTPChannel(wasyncore.dispatcher):
# if the socket is closed then interrupt the task so that it
# can cleanup possibly before the app_iter is exhausted
raise ClientDisconnected
+
if data:
# the async mainloop might be popping data off outbuf; we can
# block here waiting for it because we're in a task thread
with self.outbuf_lock:
self._flush_outbufs_below_high_watermark()
+
if not self.connected:
raise ClientDisconnected
num_bytes = len(data)
+
if data.__class__ is ReadOnlyFileBasedBuffer:
# they used wsgi.file_wrapper
self.outbufs.append(data)
@@ -312,13 +341,17 @@ class HTTPChannel(wasyncore.dispatcher):
self.outbufs[-1].append(data)
self.current_outbuf_count += num_bytes
self.total_outbufs_len += num_bytes
+
if self.total_outbufs_len >= self.adj.send_bytes:
self.server.pull_trigger()
+
return num_bytes
+
return 0
def _flush_outbufs_below_high_watermark(self):
# check first to avoid locking if possible
+
if self.total_outbufs_len > self.adj.outbuf_high_watermark:
with self.outbuf_lock:
while (
@@ -333,6 +366,7 @@ class HTTPChannel(wasyncore.dispatcher):
with self.task_lock:
while self.requests:
request = self.requests[0]
+
if request.error:
task = self.error_task_class(self, request)
else:
@@ -348,6 +382,7 @@ class HTTPChannel(wasyncore.dispatcher):
self.logger.exception(
"Exception while serving %s" % task.request.path
)
+
if not task.wrote_header:
if self.adj.expose_tracebacks:
body = traceback.format_exc()
@@ -376,8 +411,10 @@ class HTTPChannel(wasyncore.dispatcher):
task.close_on_finish = True
# we cannot allow self.requests to drop to empty til
# here; otherwise the mainloop gets confused
+
if task.close_on_finish:
self.close_when_flushed = True
+
for request in self.requests:
request.close()
self.requests = []
@@ -389,6 +426,7 @@ class HTTPChannel(wasyncore.dispatcher):
# that we need to account for, otherwise it'd be better
# to do this check at the start of the request instead of
# at the end to account for consecutive service() calls
+
if len(self.requests) > 1:
self._flush_outbufs_below_high_watermark()
@@ -397,6 +435,7 @@ class HTTPChannel(wasyncore.dispatcher):
# outbufs across requests which can cause outbufs to
# not be deallocated regularly when a connection is open
# for a long time
+
if self.current_outbuf_count > 0:
self.current_outbuf_count = self.adj.outbuf_high_watermark