diff options
author | Bert JW Regeer <bertjw@regeer.org> | 2020-08-16 01:59:25 -0700 |
---|---|---|
committer | Bert JW Regeer <bertjw@regeer.org> | 2020-08-16 16:49:07 -0700 |
commit | 4d2b2867933dc2f73c0755e137184fe0ea21f203 (patch) | |
tree | d5797538657083d246ec9406b12573de7948ce95 | |
parent | 15dc1e82356fd0b0dc763480d42d6542261f28e6 (diff) | |
download | waitress-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.py | 45 |
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 |