Skip to content

Commit a5e7bb6

Browse files
committed
Issue #34: Fix UVPoll handler crash
1 parent 13fb68b commit a5e7bb6

File tree

3 files changed

+46
-13
lines changed

3 files changed

+46
-13
lines changed

tests/test_sockets.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,48 @@ def test_socket_blocking_error(self):
111111
self.loop.run_until_complete(
112112
self.loop.sock_connect(sock, (b'', 0)))
113113

114+
def test_socket_handler_cleanup(self):
115+
# This tests recreates a rare condition where we have a socket
116+
# with an attached reader. We then remove the reader, and close the
117+
# socket. If the libuv Poll handler is still cached when we open
118+
# a new TCP connection, it might so happen that the new TCP connection
119+
# will receive a fileno that our previous socket was registered on.
120+
# In this case, when the cached Poll handle is finally closed,
121+
# we have a failed assertion in uv_poll_stop.
122+
# See also https://github.com/MagicStack/uvloop/issues/34
123+
# for details.
124+
125+
srv_sock = socket.socket()
126+
with srv_sock:
127+
srv_sock.bind(('127.0.0.1', 0))
128+
srv_sock.listen(100)
129+
130+
srv = self.loop.run_until_complete(
131+
self.loop.create_server(
132+
lambda: None, host='127.0.0.1', port=0))
133+
key_fileno = srv.sockets[0].fileno()
134+
srv.close()
135+
self.loop.run_until_complete(srv.wait_closed())
136+
137+
# Schedule create_connection task's callbacks
138+
tsk = self.loop.create_task(
139+
self.loop.create_connection(
140+
asyncio.Protocol, *srv_sock.getsockname()))
141+
142+
sock = socket.socket()
143+
with sock:
144+
# Add/remove readers
145+
if sock.fileno() != key_fileno:
146+
raise unittest.SkipTest()
147+
self.loop.add_reader(sock.fileno(), lambda: None)
148+
self.loop.remove_reader(sock.fileno())
149+
150+
tr, pr = self.loop.run_until_complete(
151+
asyncio.wait_for(tsk, loop=self.loop, timeout=0.1))
152+
tr.close()
153+
# Let the transport close
154+
self.loop.run_until_complete(asyncio.sleep(0, loop=self.loop))
155+
114156

115157
class TestUVSockets(_TestSockets, tb.UVTestCase):
116158
pass

uvloop/loop.pxd

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ cdef class Loop:
5858

5959
set _timers
6060
dict _polls
61-
dict _polls_gc
6261

6362
dict _signal_handlers
6463
bint _custom_sigint

uvloop/loop.pyx

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ cdef class Loop:
7070

7171
self._timers = set()
7272
self._polls = dict()
73-
self._polls_gc = dict()
7473

7574
if MAIN_THREAD_ID == PyThread_get_thread_ident(): # XXX
7675
self.py_signals = SignalsStack()
@@ -243,14 +242,6 @@ cdef class Loop:
243242
if len(self._queued_streams):
244243
self._exec_queued_writes()
245244

246-
if len(self._polls_gc):
247-
for fd in tuple(self._polls_gc):
248-
poll = <UVPoll> self._polls_gc[fd]
249-
if not poll.is_active():
250-
poll._close()
251-
self._polls.pop(fd)
252-
self._polls_gc.pop(fd)
253-
254245
self._ready_len = len(self._ready)
255246
if self._ready_len == 0 and self.handler_idle.running:
256247
self.handler_idle.stop()
@@ -353,7 +344,6 @@ cdef class Loop:
353344
(<UVHandle>poll_handle)._close()
354345

355346
self._polls.clear()
356-
self._polls_gc.clear()
357347

358348
if self._timers:
359349
for timer_cbhandle in tuple(self._timers):
@@ -518,7 +508,8 @@ cdef class Loop:
518508

519509
result = poll.stop_reading()
520510
if not poll.is_active():
521-
self._polls_gc[fd] = poll
511+
del self._polls[fd]
512+
poll._close()
522513
return result
523514

524515
cdef inline _add_writer(self, fd, Handle handle):
@@ -549,7 +540,8 @@ cdef class Loop:
549540

550541
result = poll.stop_writing()
551542
if not poll.is_active():
552-
self._polls_gc[fd] = poll
543+
del self._polls[fd]
544+
poll._close()
553545
return result
554546

555547
cdef _getaddrinfo(self, object host, object port,

0 commit comments

Comments
 (0)