Skip to content

Commit 87d4f11

Browse files
committed
Fix crash in transport.get_extra_info('socket') when tr is closed
1 parent 94b0ad6 commit 87d4f11

File tree

4 files changed

+97
-5
lines changed

4 files changed

+97
-5
lines changed

tests/test_tcp.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,44 @@ async def start_server():
507507

508508
self.loop.run_until_complete(start_server())
509509

510+
def test_tcp_handle_exception_in_connection_made(self):
511+
# Test that if connection_made raises an exception,
512+
# 'create_connection' still returns.
513+
514+
# Silence error logging
515+
self.loop.set_exception_handler(lambda *args: None)
516+
517+
fut = asyncio.Future(loop=self.loop)
518+
519+
async def server(reader, writer):
520+
try:
521+
await reader.read()
522+
finally:
523+
writer.close()
524+
525+
class Proto(asyncio.Protocol):
526+
def connection_made(self, tr):
527+
1 / 0
528+
529+
srv = self.loop.run_until_complete(asyncio.start_server(
530+
server,
531+
'127.0.0.1', 0,
532+
family=socket.AF_INET,
533+
loop=self.loop))
534+
535+
async def runner():
536+
tr, pr = await asyncio.wait_for(
537+
self.loop.create_connection(
538+
Proto, *srv.sockets[0].getsockname()),
539+
timeout=1.0, loop=self.loop)
540+
fut.set_result(None)
541+
tr.close()
542+
543+
self.loop.run_until_complete(runner())
544+
srv.close()
545+
self.loop.run_until_complete(srv.wait_closed())
546+
self.loop.run_until_complete(fut)
547+
510548

511549
class Test_UV_TCP(_TestTCP, tb.UVTestCase):
512550

@@ -620,6 +658,40 @@ async def run():
620658
# we need to disable this check:
621659
self.skip_unclosed_handles_check()
622660

661+
def test_tcp_handle_abort_in_connection_made(self):
662+
async def server(reader, writer):
663+
try:
664+
await reader.read()
665+
finally:
666+
writer.close()
667+
668+
class Proto(asyncio.Protocol):
669+
def connection_made(self, tr):
670+
tr.abort()
671+
672+
srv = self.loop.run_until_complete(asyncio.start_server(
673+
server,
674+
'127.0.0.1', 0,
675+
family=socket.AF_INET,
676+
loop=self.loop))
677+
678+
async def runner():
679+
tr, pr = await asyncio.wait_for(
680+
self.loop.create_connection(
681+
Proto, *srv.sockets[0].getsockname()),
682+
timeout=1.0, loop=self.loop)
683+
684+
# Asyncio would return a closed socket, which we
685+
# can't do: the transport was aborted, hence there
686+
# is no FD to attach a socket to (to make
687+
# get_extra_info() work).
688+
self.assertIsNone(tr.get_extra_info('socket'))
689+
tr.close()
690+
691+
self.loop.run_until_complete(runner())
692+
srv.close()
693+
self.loop.run_until_complete(srv.wait_closed())
694+
623695

624696
class Test_AIO_TCP(_TestTCP, tb.AIOTestCase):
625697
pass

uvloop/handles/basetransport.pxd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ cdef class UVBaseTransport(UVSocketHandle):
2626
cdef inline _schedule_call_connection_made(self)
2727
cdef inline _schedule_call_connection_lost(self, exc)
2828

29+
cdef _wakeup_waiter(self)
2930
cdef _call_connection_made(self)
3031
cdef _call_connection_lost(self, exc)
3132

uvloop/handles/basetransport.pyx

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,16 +114,34 @@ cdef class UVBaseTransport(UVSocketHandle):
114114
'protocol': self._protocol,
115115
})
116116

117+
cdef _wakeup_waiter(self):
118+
if self._waiter is not None:
119+
if not self._waiter.cancelled():
120+
self._waiter.set_result(True)
121+
self._waiter = None
122+
117123
cdef _call_connection_made(self):
118124
cdef Py_ssize_t _loop_ready_len
119125
if self._protocol is None:
120126
raise RuntimeError(
121127
'protocol is not set, cannot call connection_made()')
122128

123129
_loop_ready_len = self._loop._ready_len
124-
self._protocol.connection_made(self)
130+
131+
try:
132+
self._protocol.connection_made(self)
133+
except:
134+
self._wakeup_waiter()
135+
raise
136+
125137
self._protocol_connected = 1
126138

139+
if self._closing:
140+
# This might happen when "transport.abort()" is called
141+
# from "Protocol.connection_made".
142+
self._wakeup_waiter()
143+
return
144+
127145
if _loop_ready_len == self._loop._ready_len:
128146
# No new calls were scheduled by 'protocol.connection_made',
129147
# so it's safe to start reading right now.
@@ -142,10 +160,7 @@ cdef class UVBaseTransport(UVSocketHandle):
142160
<method_t>self._start_reading,
143161
self))
144162

145-
if self._waiter is not None:
146-
if not self._waiter.cancelled():
147-
self._waiter.set_result(True)
148-
self._waiter = None
163+
self._wakeup_waiter()
149164

150165
cdef _call_connection_lost(self, exc):
151166
if self._waiter is not None:

uvloop/handles/handle.pyx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ cdef class UVSocketHandle(UVHandle):
221221
int fd
222222
int err
223223

224+
self._ensure_alive()
224225
err = uv.uv_fileno(self._handle, <uv.uv_os_fd_t*>&fd)
225226
if err < 0:
226227
raise convert_error(err)
@@ -234,6 +235,9 @@ cdef class UVSocketHandle(UVHandle):
234235
if self.__cached_socket is not None:
235236
return self.__cached_socket
236237

238+
if not self._is_alive():
239+
return None
240+
237241
self.__cached_socket = self._new_socket()
238242
if self.__cached_socket.fileno() == self._fileno():
239243
raise RuntimeError('new socket shares fileno with the transport')

0 commit comments

Comments
 (0)