@@ -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
115157class TestUVSockets (_TestSockets , tb .UVTestCase ):
116158 pass
0 commit comments