From e4b2b383714d49e6023ba141a5a3f7a5e87304d4 Mon Sep 17 00:00:00 2001 From: moktamd Date: Fri, 27 Mar 2026 14:00:17 +0000 Subject: [PATCH 1/5] gh-146507: Make _SelectorTransport.get_write_buffer_size() O(1) Maintain a running _buffer_size counter instead of recalculating with sum(map(len, _buffer)) on every call. This matches the O(1) pattern already used by _SelectorDatagramTransport and _ProactorBaseWritePipeTransport in the same codebase. --- Lib/asyncio/selector_events.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py index 9685e7fc05d241..1cd8b96f0aa2e9 100644 --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -793,6 +793,7 @@ def __init__(self, loop, sock, protocol, extra=None, server=None, context=None): self._server = server self._buffer = collections.deque() + self._buffer_size = 0 self._conn_lost = 0 # Set when call to connection_lost scheduled. self._closing = False # Set when close() called. self._paused = False # Set when pause_reading() called @@ -897,6 +898,7 @@ def _force_close(self, exc): return if self._buffer: self._buffer.clear() + self._buffer_size = 0 self._loop._remove_writer(self._sock_fd) if not self._closing: self._closing = True @@ -919,7 +921,7 @@ def _call_connection_lost(self, exc): self._server = None def get_write_buffer_size(self): - return sum(map(len, self._buffer)) + return self._buffer_size def _add_reader(self, fd, callback, *args): if not self.is_reading(): @@ -1090,6 +1092,7 @@ def write(self, data): # Add it to the buffer. self._buffer.append(data) + self._buffer_size += len(data) self._maybe_pause_protocol() def _get_sendmsg_buffer(self): @@ -1109,6 +1112,7 @@ def _write_sendmsg(self): except BaseException as exc: self._loop._remove_writer(self._sock_fd) self._buffer.clear() + self._buffer_size = 0 self._fatal_error(exc, 'Fatal write error on socket transport') if self._empty_waiter is not None: self._empty_waiter.set_exception(exc) @@ -1124,6 +1128,7 @@ def _write_sendmsg(self): self._sock.shutdown(socket.SHUT_WR) def _adjust_leftover_buffer(self, nbytes: int) -> None: + self._buffer_size -= nbytes buffer = self._buffer while nbytes: b = buffer.popleft() @@ -1144,13 +1149,16 @@ def _write_send(self): if n != len(buffer): # Not all data was written self._buffer.appendleft(buffer[n:]) + self._buffer_size -= n except (BlockingIOError, InterruptedError): - pass + self._buffer.appendleft(buffer) + return except (SystemExit, KeyboardInterrupt): raise except BaseException as exc: self._loop._remove_writer(self._sock_fd) self._buffer.clear() + self._buffer_size = 0 self._fatal_error(exc, 'Fatal write error on socket transport') if self._empty_waiter is not None: self._empty_waiter.set_exception(exc) @@ -1186,7 +1194,9 @@ def writelines(self, list_of_data): self._conn_lost += 1 return - self._buffer.extend([memoryview(data) for data in list_of_data]) + views = [memoryview(data) for data in list_of_data] + self._buffer.extend(views) + self._buffer_size += sum(len(v) for v in views) self._write_ready() # If the entire buffer couldn't be written, register a write handler if self._buffer: From 6a79a27a85c1e0328cab9e04364a44beb377e1e4 Mon Sep 17 00:00:00 2001 From: moktamd Date: Fri, 27 Mar 2026 14:06:58 +0000 Subject: [PATCH 2/5] Add NEWS entry for gh-146507 --- .../Library/2026-03-27-12-00-00.gh-issue-146507.1D95A7.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-03-27-12-00-00.gh-issue-146507.1D95A7.rst diff --git a/Misc/NEWS.d/next/Library/2026-03-27-12-00-00.gh-issue-146507.1D95A7.rst b/Misc/NEWS.d/next/Library/2026-03-27-12-00-00.gh-issue-146507.1D95A7.rst new file mode 100644 index 00000000000000..f0aae2068fc9e6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-03-27-12-00-00.gh-issue-146507.1D95A7.rst @@ -0,0 +1,3 @@ +Make :meth:`asyncio.SelectorEventLoop` stream transport's +:meth:`~asyncio.WriteTransport.get_write_buffer_size` O(1) by maintaining a +running byte counter instead of iterating the buffer on every call. From ce683752abf1eaadc90a4b3f57e04e8b41890d78 Mon Sep 17 00:00:00 2001 From: moktamd Date: Fri, 27 Mar 2026 14:19:44 +0000 Subject: [PATCH 3/5] Simplify writelines buffer_size tracking per review --- Lib/asyncio/selector_events.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py index 1cd8b96f0aa2e9..1fc0fd4bc9ca83 100644 --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -1194,9 +1194,8 @@ def writelines(self, list_of_data): self._conn_lost += 1 return - views = [memoryview(data) for data in list_of_data] - self._buffer.extend(views) - self._buffer_size += sum(len(v) for v in views) + self._buffer_size += sum(len(data) for data in list_of_data) + self._buffer.extend([memoryview(data) for data in list_of_data]) self._write_ready() # If the entire buffer couldn't be written, register a write handler if self._buffer: From 7eeccbaaa0045f73433a8cc0e65d8c7e1203f13d Mon Sep 17 00:00:00 2001 From: moktamd Date: Fri, 27 Mar 2026 15:09:24 +0000 Subject: [PATCH 4/5] Iterate list_of_data once in writelines per review --- Lib/asyncio/selector_events.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py index 1fc0fd4bc9ca83..2c32b77197bd8e 100644 --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -1194,8 +1194,11 @@ def writelines(self, list_of_data): self._conn_lost += 1 return - self._buffer_size += sum(len(data) for data in list_of_data) - self._buffer.extend([memoryview(data) for data in list_of_data]) + views = [] + for data in list_of_data: + views.append(memoryview(data)) + self._buffer_size += len(data) + self._buffer.extend(views) self._write_ready() # If the entire buffer couldn't be written, register a write handler if self._buffer: From bfed0bd1b1a8b80eed38e8cbb7d85f29f638b5c1 Mon Sep 17 00:00:00 2001 From: moktamd Date: Fri, 27 Mar 2026 15:38:09 +0000 Subject: [PATCH 5/5] Append directly to self._buffer in writelines per review --- Lib/asyncio/selector_events.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py index 2c32b77197bd8e..961dbfb4b96303 100644 --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -1194,11 +1194,9 @@ def writelines(self, list_of_data): self._conn_lost += 1 return - views = [] for data in list_of_data: - views.append(memoryview(data)) + self._buffer.append(memoryview(data)) self._buffer_size += len(data) - self._buffer.extend(views) self._write_ready() # If the entire buffer couldn't be written, register a write handler if self._buffer: