|
1 | 1 | import asyncio
|
| 2 | +import gc |
2 | 3 | import os
|
3 | 4 | import socket
|
4 | 5 | import time
|
5 | 6 | import threading
|
6 | 7 | import unittest
|
7 | 8 |
|
| 9 | +from test import support |
8 | 10 | from test.support import socket_helper
|
| 11 | +from test.support import warnings_helper |
9 | 12 | from test.test_asyncio import utils as test_utils
|
10 | 13 | from test.test_asyncio import functional as func_tests
|
11 | 14 |
|
@@ -266,6 +269,49 @@ async def serve(rd, wr):
|
266 | 269 | await asyncio.sleep(0)
|
267 | 270 | self.assertTrue(task.done())
|
268 | 271 |
|
| 272 | + async def test_close_race(self): |
| 273 | + |
| 274 | + srv = await asyncio.start_server(lambda *_: None, socket_helper.HOSTv4, 0) |
| 275 | + srv_sock = srv.sockets[0] |
| 276 | + addr = srv_sock.getsockname() |
| 277 | + |
| 278 | + # When the server is closed before a connection is handled but after the |
| 279 | + # connection is accepted, then a race-condition exists between the handler |
| 280 | + # transport and the server, both which will attempt to wakeup the server to set |
| 281 | + # any server waiters. We can recreate race-condition by opening a connection and |
| 282 | + # waiting for the server reader callback before closing the server |
| 283 | + loop = asyncio.get_running_loop() |
| 284 | + srv_reader, _ = loop._selector.get_key(srv_sock.fileno()).data |
| 285 | + conn_task = asyncio.create_task(asyncio.open_connection(addr[0], addr[1])) |
| 286 | + for _ in range(10): |
| 287 | + await asyncio.sleep(0) |
| 288 | + if srv_reader in loop._ready: |
| 289 | + break |
| 290 | + |
| 291 | + # Ensure accepted connection task is scheduled by the server reader, but not |
| 292 | + # completed, before closing the server. |
| 293 | + await asyncio.sleep(0) |
| 294 | + srv.close() |
| 295 | + |
| 296 | + # Complete the client connection to close the socket. Suppress errors in the |
| 297 | + # handler transport due to failing to attach to closed server. |
| 298 | + with support.captured_stderr(): |
| 299 | + try: |
| 300 | + _, wr = await conn_task |
| 301 | + self.addCleanup(wr.close) |
| 302 | + except OSError: |
| 303 | + pass |
| 304 | + |
| 305 | + await srv.wait_closed() |
| 306 | + |
| 307 | + # Verify the handler transport does not raise an error due to multiple calls to |
| 308 | + # the server wakeup. Suppress expected ResourceWarnings from the handler |
| 309 | + # transport failing to attach to the closed server. |
| 310 | + with warnings_helper.check_warnings(("unclosed transport", ResourceWarning)), \ |
| 311 | + support.catch_unraisable_exception() as cm: |
| 312 | + support.gc_collect() |
| 313 | + self.assertIsNone(cm.unraisable) |
| 314 | + |
269 | 315 |
|
270 | 316 | # Test the various corner cases of Unix server socket removal
|
271 | 317 | class UnixServerCleanupTests(unittest.IsolatedAsyncioTestCase):
|
|
0 commit comments