-
-
Notifications
You must be signed in to change notification settings - Fork 33.3k
bpo-41279: Add StreamReaderBufferedProtocol #21446
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
9a65cfe
6db642c
71d6a90
cdc14d8
9e81f1a
3dc4d18
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -112,7 +112,7 @@ def factory(): | |
| return await loop.create_unix_server(factory, path, **kwds) | ||
|
|
||
|
|
||
| class FlowControlMixin(protocols.Protocol): | ||
| class FlowControlMixin(protocols.BaseProtocol): | ||
| """Reusable flow control logic for StreamWriter.drain(). | ||
|
|
||
| This implements the protocol methods pause_writing(), | ||
|
|
@@ -180,7 +180,7 @@ def _get_close_waiter(self, stream): | |
| raise NotImplementedError | ||
|
|
||
|
|
||
| class StreamReaderProtocol(FlowControlMixin, protocols.Protocol): | ||
| class BaseStreamReaderProtocol(FlowControlMixin): | ||
| """Helper class to adapt between Protocol and StreamReader. | ||
|
|
||
| (This is a helper class instead of making StreamReader itself a | ||
|
|
@@ -267,11 +267,6 @@ def connection_lost(self, exc): | |
| self._stream_writer = None | ||
| self._transport = None | ||
|
|
||
| def data_received(self, data): | ||
| reader = self._stream_reader | ||
| if reader is not None: | ||
| reader.feed_data(data) | ||
|
|
||
| def eof_received(self): | ||
| reader = self._stream_reader | ||
| if reader is not None: | ||
|
|
@@ -298,6 +293,30 @@ def __del__(self): | |
| closed.exception() | ||
|
|
||
|
|
||
| class StreamReaderProtocol(BaseStreamReaderProtocol, protocols.Protocol): | ||
| def data_received(self, data): | ||
| reader = self._stream_reader | ||
| if reader is not None: | ||
| reader.feed_data(data) | ||
|
|
||
|
|
||
| class StreamReaderBufferedProtocol(BaseStreamReaderProtocol, protocols.BufferedProtocol): | ||
|
||
| def __init__(self, stream_reader, client_connected_cb=None, loop=None, | ||
| buffer_size=65536): | ||
| super().__init__(stream_reader, | ||
| client_connected_cb=client_connected_cb, | ||
| loop=loop) | ||
| self._buffer = memoryview(bytearray(buffer_size)) | ||
|
|
||
| def get_buffer(self, sizehint): | ||
| return self._buffer | ||
|
|
||
| def buffer_updated(self, nbytes): | ||
| reader = self._stream_reader | ||
| if reader is not None: | ||
| reader.feed_data(self._buffer[:nbytes]) | ||
|
|
||
|
|
||
| class StreamWriter: | ||
| """Wraps a Transport. | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,7 @@ | |
| from . import events | ||
| from . import exceptions | ||
| from . import futures | ||
| from . import protocols | ||
| from . import selector_events | ||
| from . import tasks | ||
| from . import transports | ||
|
|
@@ -480,9 +481,13 @@ def __init__(self, loop, pipe, protocol, waiter=None, extra=None): | |
| os.set_blocking(self._fileno, False) | ||
|
|
||
| self._loop.call_soon(self._protocol.connection_made, self) | ||
|
|
||
| # only start reading when connection_made() has been called | ||
| self._loop.call_soon(self._loop._add_reader, | ||
| self._fileno, self._read_ready) | ||
| if isinstance(protocol, protocols.BufferedProtocol): | ||
| self._read_ready = self._readinto_buffer_ready | ||
| else: | ||
| self._read_ready = self._read_buffer_ready | ||
| self._loop.call_soon(self._loop._add_reader, self._fileno, self._read_ready) | ||
| if waiter is not None: | ||
| # only wake up the waiter when connection_made() has been called | ||
| self._loop.call_soon(futures._set_result_unless_cancelled, | ||
|
|
@@ -509,7 +514,37 @@ def __repr__(self): | |
| info.append('closed') | ||
| return '<{}>'.format(' '.join(info)) | ||
|
|
||
| def _read_ready(self): | ||
| def _readinto_buffer_ready(self): | ||
| try: | ||
| buf = self._protocol.get_buffer(-1) | ||
| if not len(buf): | ||
| raise RuntimeError('get_buffer() returned an empty buffer') | ||
| except (SystemExit, KeyboardInterrupt): | ||
| raise | ||
| except BaseException as exc: | ||
|
||
| self._fatal_error( | ||
| exc, 'Fatal error: protocol.get_buffer() call failed.') | ||
| return | ||
|
|
||
| nbytes = 0 | ||
| try: | ||
| nbytes = self._pipe.readinto(buf) | ||
| except (BlockingIOError, InterruptedError): | ||
| pass | ||
| except OSError as exc: | ||
| self._fatal_error(exc, 'Fatal read error on pipe transport') | ||
| else: | ||
| if nbytes: | ||
| self._protocol.buffer_updated(nbytes) | ||
| else: | ||
| if self._loop.get_debug(): | ||
| logger.info("%r was closed by peer", self) | ||
| self._closing = True | ||
| self._loop._remove_reader(self._fileno) | ||
| self._loop.call_soon(self._protocol.eof_received) | ||
| self._loop.call_soon(self._call_connection_lost, None) | ||
|
|
||
| def _read_buffer_ready(self): | ||
| try: | ||
| data = os.read(self._fileno, self.max_size) | ||
| except (BlockingIOError, InterruptedError): | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see the corresponding changes in selector_events.py. Does it handle these use cases already?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes it has the following bit of code: