diff options
author | Bert JW Regeer <bertjw@regeer.org> | 2022-05-24 20:26:47 -0600 |
---|---|---|
committer | Bert JW Regeer <bertjw@regeer.org> | 2022-05-24 20:26:47 -0600 |
commit | c7a3d7ed70e57aa37b1c2efd6b5365ac75cee2ac (patch) | |
tree | ea019046aafec6d9cc517e30c05201958099baa9 | |
parent | 7c3739b27f04f01fcabe6f20e328759cbe46ea5c (diff) | |
download | waitress-c7a3d7ed70e57aa37b1c2efd6b5365ac75cee2ac.tar.gz |
Only close socket in the main thread
This solves a race condition that may exist when attempting to loop over
the open sockets and then calling select() and accidentally have called
close() on the socket in an app thread.
-rw-r--r-- | src/waitress/channel.py | 18 | ||||
-rw-r--r-- | src/waitress/wasyncore.py | 5 | ||||
-rw-r--r-- | tests/test_channel.py | 4 |
3 files changed, 15 insertions, 12 deletions
diff --git a/src/waitress/channel.py b/src/waitress/channel.py index 948b498..eb59dd3 100644 --- a/src/waitress/channel.py +++ b/src/waitress/channel.py @@ -126,10 +126,10 @@ class HTTPChannel(wasyncore.dispatcher): if self.will_close: self.handle_close() - def _flush_exception(self, flush): + def _flush_exception(self, flush, do_close=True): if flush: try: - return (flush(), False) + return (flush(do_close=do_close), False) except OSError: if self.adj.log_socket_errors: self.logger.exception("Socket error") @@ -240,20 +240,20 @@ class HTTPChannel(wasyncore.dispatcher): return True - def _flush_some_if_lockable(self): + def _flush_some_if_lockable(self, do_close=True): # 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() + self._flush_some(do_close=do_close) if self.total_outbufs_len < self.adj.outbuf_high_watermark: self.outbuf_lock.notify() finally: self.outbuf_lock.release() - def _flush_some(self): + def _flush_some(self, do_close=True): # Send as much data as possible to our client sent = 0 @@ -267,7 +267,7 @@ class HTTPChannel(wasyncore.dispatcher): while outbuflen > 0: chunk = outbuf.get(self.sendbuf_len) - num_sent = self.send(chunk) + num_sent = self.send(chunk, do_close=do_close) if num_sent: outbuf.skip(num_sent, True) @@ -374,7 +374,9 @@ class HTTPChannel(wasyncore.dispatcher): self.total_outbufs_len += num_bytes if self.total_outbufs_len >= self.adj.send_bytes: - (flushed, exception) = self._flush_exception(self._flush_some) + (flushed, exception) = self._flush_exception( + self._flush_some, do_close=False + ) if ( exception @@ -392,7 +394,7 @@ class HTTPChannel(wasyncore.dispatcher): if self.total_outbufs_len > self.adj.outbuf_high_watermark: with self.outbuf_lock: - (_, exception) = self._flush_exception(self._flush_some) + (_, exception) = self._flush_exception(self._flush_some, do_close=False) if exception: # An exception happened while flushing, wake up the main diff --git a/src/waitress/wasyncore.py b/src/waitress/wasyncore.py index 9a68c51..c260f56 100644 --- a/src/waitress/wasyncore.py +++ b/src/waitress/wasyncore.py @@ -426,7 +426,7 @@ class dispatcher: else: return conn, addr - def send(self, data): + def send(self, data, do_close=True): try: result = self.socket.send(data) return result @@ -434,7 +434,8 @@ class dispatcher: if why.args[0] == EWOULDBLOCK: return 0 elif why.args[0] in _DISCONNECTED: - self.handle_close() + if do_close: + self.handle_close() return 0 else: raise diff --git a/tests/test_channel.py b/tests/test_channel.py index b1c317d..8467ae7 100644 --- a/tests/test_channel.py +++ b/tests/test_channel.py @@ -376,7 +376,7 @@ class TestHTTPChannel(unittest.TestCase): inst.total_outbufs_len = len(inst.outbufs[0]) inst.adj.send_bytes = 1 inst.adj.outbuf_high_watermark = 2 - sock.send = lambda x: False + sock.send = lambda x, do_close=True: False inst.will_close = False inst.last_activity = 0 result = inst.handle_write() @@ -453,7 +453,7 @@ class TestHTTPChannel(unittest.TestCase): buf = DummyHugeOutbuffer() inst.outbufs = [buf] - inst.send = lambda *arg: 0 + inst.send = lambda *arg, do_close: 0 result = inst._flush_some() # we are testing that _flush_some doesn't raise an OverflowError # when one of its outbufs has a __len__ that returns gt sys.maxint |