Skip to main content

asyncio/protocols.py

protocols.py defines the abstract base classes that asyncio transports call into. A transport owns the socket; a protocol owns the application logic. The two objects are connected by connection_made, after which the transport drives the protocol by calling its methods on I/O events. Every method in the file is a no-op stub — subclasses override only what they need.

Map​

LinesSymbolRole
1–20module topimports, __all__
21–70BaseProtocolconnection_made, connection_lost, pause_writing, resume_writing
71–100Protocoladds data_received, eof_received
101–115DatagramProtocoladds datagram_received, error_received
116–135SubprocessProtocoladds pipe_data_received, pipe_connection_lost, process_exited
136–150BufferedProtocoladds get_buffer, buffer_updated, eof_received

Reading​

BaseProtocol anchors the lifecycle​

connection_made is called once by the transport when the connection is established. It receives the transport object, which the protocol should store for later writes. connection_lost is called exactly once at teardown.

# CPython: Lib/asyncio/protocols.py:21 BaseProtocol
class BaseProtocol:
def connection_made(self, transport):
"""Called when a connection is made."""

def connection_lost(self, exc):
"""Called when the connection is lost or closed."""

def pause_writing(self):
"""Called when the transport's buffer goes over the high-water mark."""

def resume_writing(self):
"""Called when the transport's buffer drains below the low-water mark."""

pause_writing / resume_writing are the back-pressure hooks. The transport calls them to tell the protocol to slow down or speed up production.

Protocol adds the data path​

Protocol extends BaseProtocol with the two methods that carry inbound data.

# CPython: Lib/asyncio/protocols.py:71 Protocol
class Protocol(BaseProtocol):
def data_received(self, data):
"""Called when some data is received."""

def eof_received(self):
"""Called when the other end signals it won't send more data."""

eof_received may return a truthy value to keep the transport open for writing after the read side is closed (half-close). Returning None or False causes the transport to close completely.

BufferedProtocol for zero-copy reads​

BufferedProtocol lets the protocol supply the receive buffer itself. The transport calls get_buffer to obtain a writable buffer object (any object supporting the buffer protocol), writes received bytes directly into it, then calls buffer_updated with the number of bytes written.

# CPython: Lib/asyncio/protocols.py:136 BufferedProtocol
class BufferedProtocol(BaseProtocol):
def get_buffer(self, sizehint):
"""Return a buffer for the transport to write into."""

def buffer_updated(self, nbytes):
"""Called when the buffer was updated with received data."""

def eof_received(self):
"""Called when the other end signals EOF."""

This avoids the intermediate bytes allocation that Protocol.data_received requires.

gopy notes​

  • Every method body is pass or just a docstring. The ABCs carry no logic; the hierarchy exists only so isinstance checks and __subclasshook__ can work.
  • SubprocessProtocol.pipe_data_received receives a fd integer (0=stdin, 1=stdout, 2=stderr) alongside the data bytes. This differs from data_received, which has no fd argument.
  • BufferedProtocol is the asyncio counterpart to the readinto pattern in synchronous I/O. When porting, map get_buffer to a []byte slice argument and buffer_updated to the byte count returned.
  • DatagramProtocol.error_received receives an OSError, not an exception chain. It is informational only — the transport does not close on datagram errors.

CPython 3.14 changes​

  • No structural changes to the ABC hierarchy in 3.14. The classes are stable since 3.4.
  • BufferedProtocol moved from provisional status (3.7) to fully supported in 3.12; 3.14 carries no further changes to it.
  • Type annotations were added across the file in 3.13 and refined in 3.14 (PEP 688 buffer protocol typing).