10000 gh-111354: simplify detection of RESUME after YIELD_VALUE at except-depth 1 by iritkatriel · Pull Request #111368 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

gh-111354: simplify detection of RESUME after YIELD_VALUE at except-depth 1 #111368

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
03335e6
use RESUME oparg to store exception depth info
iritkatriel Oct 26, 2023
8b4e89d
remove the arg from YIELD_VALUE
iritkatriel Oct 26, 2023
84fbfc3
add docs
iritkatriel Oct 26, 2023
3a111ab
fix test_dis
iritkatriel Oct 26, 2023
6005db5
add news
iritkatriel Oct 26, 2023
00a0cef
regen-all
iritkatriel Oct 26, 2023
50e2d25
Merge branch 'main' into gen_close
iritkatriel Oct 26, 2023
59b3cce
regen-all
iritkatriel Oct 26, 2023
dd8af2d
Merge branch 'main' into gen_close
iritkatriel Oct 27, 2023
c4b4583
Fix typos in import system docs (#111396)
jonathanberthias Oct 27, 2023
c48ec85
gh-111406: Fix broken link to bpython's site (#111407)
zmc Oct 27, 2023
64ca0a9
gh-108765: Include explicitly <unistd.h> in signalmodule.c (#111402)
vstinner Oct 27, 2023
3aeffca
gh-59013: Make line number of function breakpoint more precise (#110582)
gaogaotiantian Oct 27, 2023
8000
2e74589
gh-110205: Fix asyncio ThreadedChildWatcher._join_threads() (#110884)
gvanrossum Oct 27, 2023
1b70f83
gh-111342: fix typo in math.sumprod (GH-111416)
skirpichev Oct 28, 2023
b632fc1
CI: Include Python version in cache.config key (#111410)
hugovk Oct 28, 2023
c483c69
gh-79033: Try to fix asyncio.Server.wait_closed() again (GH-111336)
gvanrossum Oct 28, 2023
5c9882e
GH-110109: Move tests for `pathlib.Path.walk()` into main test classe…
barneygale Oct 28, 2023
d5ec031
gh-111426: Remove `test_cmd.test_coverage` (#111427)
sobolevn Oct 28, 2023
31a9dcf
gh-66425: Remove the unreachable code to set `REMOTE_HOST` header (gh…
c-bata Oct 29, 2023
8d5134b
gh-111062: Separate macOS build into a reusable workflow (gh-111444)
dimaqq Oct 29, 2023
f2fc8ee
gh-94808: Add coverage test for number check (gh-111445)
ekohilas Oct 29, 2023
5499901
gh-111062: Build both default and free-threaded on macOS (gh-111449)
dimaqq Oct 29, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
gh-79033: Try to fix asyncio.Server.wait_closed() again (GH-111336)
* Try to fix asyncio.Server.wait_closed() again

I identified the condition that `wait_closed()` is intended
to wait for: the server is closed *and* there are no more
active connections.

When this condition first becomes true, `_wakeup()` is called
(either from `close()` or from `_detach()`) and it sets `_waiters`
to `None`. So we just check for `self._waiters is None`; if it's
not `None`, we know we have to wait, and do so.

A problem was that the new test introduced in 3.12 explicitly
tested that `wait_closed()` returns immediately when the server
is *not* closed but there are currently no active connections.
This was a mistake (probably a misunderstanding of the intended
semantics). I've fixed the test, and added a separate test that
checks exactly for this scenario.

I also fixed an oddity where in `_wakeup()` the result of the
waiter was set to the waiter itself. This result is not used
anywhere and I changed this to `None`, to avoid a GC cycle.

* Update Lib/asyncio/base_events.py

---------

Co-authored-by: Carol Willing <carolcode@willingconsulting.com>
  • Loading branch information
2 people authored and iritkatriel committed Oct 29, 2023
commit c483c697825c7ffaed7656d4b4f37c4a290004ad
8 changes: 5 additions & 3 deletions Doc/library/asyncio-eventloop.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1619,8 +1619,9 @@ Do not instantiate the :class:`Server` class directly.
The sockets that represent existing incoming client connections
are left open.

The server is closed asynchronously, use the :meth:`wait_closed`
coroutine to wait until the server is closed.
The server is closed asynchronously; use the :meth:`wait_closed`
coroutine to wait until the server is closed (and no more
connections are active).

.. method:: get_loop()

Expand Down Expand Up @@ -1678,7 +1679,8 @@ Do not instantiate the :class:`Server` class directly.

.. coroutinemethod:: wait_closed()

Wait until the :meth:`close` method completes.
Wait until the :meth:`close` method completes and all active
connections have finished.

.. attribute:: sockets

Expand Down
24 changes: 22 additions & 2 deletions Lib/asyncio/base_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ def _wakeup(self):
self._waiters = None
for waiter in waiters:
if not waiter.done():
waiter.set_result(waiter)
waiter.set_result(None)

def _start_serving(self):
if self._serving:
Expand Down Expand Up @@ -377,7 +377,27 @@ async def serve_forever(self):
self._serving_forever_fut = None

async def wait_closed(self):
if self._waiters is None or self._active_count == 0:
"""Wait until server is closed and all connections are dropped.

- If the server is not closed, wait.
- If it is closed, but there are still active connections, wait.

Anyone waiting here will be unblocked once both conditions
(server is closed and all connections have been dropped)
have become true, in either order.

Historical note: In 3.11 and before, this was broken, returning
immediately if the server was already closed, even if there
were still active connections. An attempted fix in 3.12.0 was
still broken, returning immediately if the server was still
open and there were no active connections. Hopefully in 3.12.1
we have it right.
"""
# Waiters are unblocked by self._wakeup(), which is called
# from two places: self.close() and self._detach(), but only
# when both conditions have become true. To signal that this
# has happened, self._wakeup() sets self._waiters to None.
if self._waiters is None:
return
waiter = self._loop.create_future()
self._waiters.append(waiter)
Expand Down
38 changes: 34 additions & 4 deletions Lib/test/test_asyncio/test_server.py
9F48
Original file line number Diff line number Diff line change
Expand Up @@ -122,29 +122,59 @@ async def main(srv):

class TestServer2(unittest.IsolatedAsyncioTestCase):

async def test_wait_closed(self):
async def test_wait_closed_basic(self):
async def serve(*args):
pass

srv = await asyncio.start_server(serve, socket_helper.HOSTv4, 0)
self.addCleanup(srv.close)

# active count = 0
# active count = 0, not closed: should block
task1 = asyncio.create_task(srv.wait_closed())
await asyncio.sleep(0)
self.assertTrue(task1.done())
self.assertFalse(task1.done())

# active count != 0
# active count != 0, not closed: should block
srv._attach()
task2 = asyncio.create_task(srv.wait_closed())
await asyncio.sleep(0)
self.assertFalse(task1.done())
self.assertFalse(task2.done())

srv.close()
await asyncio.sleep(0)
# active count != 0, closed: should block
task3 = asyncio.create_task(srv.wait_closed())
await asyncio.sleep(0)
self.assertFalse(task1.done())
self.assertFalse(task2.done())
self.assertFalse(task3.done())

srv._detach()
# active count == 0, closed: should unblock
await task1
await task2
await task3
await srv.wait_closed() # Return immediately

async def test_wait_closed_race(self):
# Test a regression in 3.12.0, should be fixed in 3.12.1
async def serve(*args):
pass

srv = await asyncio.start_server(serve, socket_helper.HOSTv4, 0)
self.addCleanup(srv.close)

task = asyncio.create_task(srv.wait_closed())
await asyncio.sleep(0)
self.assertFalse(task.done())
srv._attach()
loop = asyncio.get_running_loop()
loop.call_soon(srv.close)
loop.call_soon(srv._detach)
await srv.wait_closed()




@unittest.skipUnless(hasattr(asyncio, 'ProactorEventLoop'), 'Windows only')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Another attempt at fixing :func:`asyncio.Server.wait_closed()`. It now
blocks until both conditions are true: the server is closed, *and* there
are no more active connections. (This means that in some cases where in
3.12.0 this function would *incorrectly* have returned immediately,
it will now block; in particular, when there are no active connections
but the server hasn't been closed yet.)
0