Skip to content

Commit 3f24bde

Browse files
gh-118950: Fix SSLProtocol.connection_lost not being called when OSError is thrown (#118960)
Co-authored-by: Kumar Aditya <[email protected]>
1 parent 41bd9d9 commit 3f24bde

File tree

3 files changed

+53
-1
lines changed

3 files changed

+53
-1
lines changed

Lib/asyncio/sslproto.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def get_protocol(self):
101101
return self._ssl_protocol._app_protocol
102102

103103
def is_closing(self):
104-
return self._closed
104+
return self._closed or self._ssl_protocol._is_transport_closing()
105105

106106
def close(self):
107107
"""Close the transport.
@@ -379,6 +379,9 @@ def _get_app_transport(self):
379379
self._app_transport_created = True
380380
return self._app_transport
381381

382+
def _is_transport_closing(self):
383+
return self._transport is not None and self._transport.is_closing()
384+
382385
def connection_made(self, transport):
383386
"""Called when the low-level connection is made.
384387

Lib/test/test_asyncio/test_sslproto.py

+48
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,54 @@ def test_connection_lost(self):
109109
test_utils.run_briefly(self.loop)
110110
self.assertIsInstance(waiter.exception(), ConnectionAbortedError)
111111

112+
def test_connection_lost_when_busy(self):
113+
# gh-118950: SSLProtocol.connection_lost not being called when OSError
114+
# is thrown on asyncio.write.
115+
sock = mock.Mock()
116+
sock.fileno = mock.Mock(return_value=12345)
117+
sock.send = mock.Mock(side_effect=BrokenPipeError)
118+
119+
# construct StreamWriter chain that contains loop dependant logic this emulates
120+
# what _make_ssl_transport() does in BaseSelectorEventLoop
121+
reader = asyncio.StreamReader(limit=2 ** 16, loop=self.loop)
122+
protocol = asyncio.StreamReaderProtocol(reader, loop=self.loop)
123+
ssl_proto = self.ssl_protocol(proto=protocol)
124+
125+
# emulate reading decompressed data
126+
sslobj = mock.Mock()
127+
sslobj.read.side_effect = ssl.SSLWantReadError
128+
sslobj.write.side_effect = ssl.SSLWantReadError
129+
ssl_proto._sslobj = sslobj
130+
131+
# emulate outgoing data
132+
data = b'An interesting message'
133+
134+
outgoing = mock.Mock()
135+
outgoing.read = mock.Mock(return_value=data)
136+
outgoing.pending = len(data)
137+
ssl_proto._outgoing = outgoing
138+
139+
# use correct socket transport to initialize the SSLProtocol
140+
self.loop._make_socket_transport(sock, ssl_proto)
141+
142+
transport = ssl_proto._app_transport
143+
writer = asyncio.StreamWriter(transport, protocol, reader, self.loop)
144+
145+
async def main():
146+
# writes data to transport
147+
async def write():
148+
writer.write(data)
149+
await writer.drain()
150+
151+
# try to write for the first time
152+
await write()
153+
# try to write for the second time, this raises as the connection_lost
154+
# callback should be done with error
155+
with self.assertRaises(ConnectionResetError):
156+
await write()
157+
158+
self.loop.run_until_complete(main())
159+
112160
def test_close_during_handshake(self):
113161
# bpo-29743 Closing transport during handshake process leaks socket
114162
waiter = self.loop.create_future()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix bug where SSLProtocol.connection_lost wasn't getting called when OSError was thrown on writing to socket.

0 commit comments

Comments
 (0)