@@ -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 ()
0 commit comments