Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions Lib/asyncio/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,9 @@ def _outer_done_callback(outer):
# Keep only one callback to log on cancel
inner.remove_done_callback(_log_on_exception)
inner.add_done_callback(_log_on_exception)
if cur_task is not None:
inner.remove_done_callback(_clear_awaited_by_callback)
futures.future_discard_from_awaited_by(inner, cur_task)

if cur_task is not None:
inner.add_done_callback(_clear_awaited_by_callback)
Expand Down
17 changes: 17 additions & 0 deletions Lib/test/test_asyncio/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -2109,6 +2109,8 @@ def test_shield_cancel_outer(self):
test_utils.run_briefly(self.loop)
self.assertTrue(outer.cancelled())
self.assertEqual(0, 0 if outer._callbacks is None else len(outer._callbacks))
self.assertFalse(inner._asyncio_awaited_by)
self.assertTrue({f for f, _ctx in inner._callbacks or []} <= {asyncio.tasks._log_on_exception})

def test_shield_cancel_outer_result(self):
mock_handler = mock.Mock()
Expand All @@ -2134,6 +2136,21 @@ def test_shield_cancel_outer_exception(self):
test_utils.run_briefly(self.loop)
mock_handler.assert_called_once()

def test_shield_cancel_outer_in_task(self):
inner = self.new_future(self.loop)

async def coro():
outer = asyncio.shield(inner)
self.assertNotEqual(0, len(inner._callbacks))
outer.cancel()
await asyncio.sleep(0)
self.assertTrue(outer.cancelled())

task = self.new_task(self.loop, coro())
self.loop.run_until_complete(task)
self.assertFalse(inner._asyncio_awaited_by)
self.assertTrue({f for f, _ctx in inner._callbacks or []} <= {asyncio.tasks._log_on_exception})

def test_shield_duplicate_log_once(self):
mock_handler = mock.Mock()
self.loop.set_exception_handler(mock_handler)
Expand Down
58 changes: 58 additions & 0 deletions Lib/test/test_free_threading/test_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,5 +324,63 @@ def wrapper():
for reader in readers:
reader.join()

def test_concurrent_setattr_deadlock(self):
# gh-155400: two threads assigning to a special method of the same
# class could deadlock. One thread held the type lock and waited for
# the type dict mutex, which its critical section had released when it
# blocked on the stop-the-world mutex, while the other held the type
# dict mutex and waited for the type lock.
# This is fairly difficult to trigger the race but this N seems to do
# it at least sometimes.
N = 200
done = False

class Base:
pass

def setter():
func = lambda self: "x"
barrier.wait()
while not done:
Base.__repr__ = func
try:
del Base.__repr__
except AttributeError:
pass

def subclasser():
barrier.wait()
while not done:
type('Sub', (Base,), {})()

def lister():
barrier.wait()
while not done:
Base.__subclasses__()

def basesetter():
nonlocal done
barrier.wait()
for _ in range(N):
class A:
pass
class C:
pass
class B(A):
pass
B.__bases__ = (C,)
done = True

# The setter threads are the ones that deadlock. The others are there
# to keep the type lock and the stop-the-world mutex contended, which
# is what gets the setters into the window where it happens.
targets = (setter, setter, subclasser, subclasser,
lister, lister, basesetter)
barrier = threading.Barrier(len(targets))
threads = [Thread(target=target) for target in targets]
with threading_helper.start_threads(threads):
pass


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix races in free-threaded builds when updating type slots for newly created
classes and when removing entries from a base type's subclasses dictionary.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Fix a deadlock in the free-threaded build between two threads assigning to a
special method of the same class. While applying the type slot updates with
the world stopped, only the type lock was prevented from being released; the
type dict mutex could still be released and re-acquired, in the wrong order,
if the thread blocked on the stop-the-world mutex.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix :func:`asyncio.shield` leaking the calling task via the await-graph and
callbacks when called on a future that never resolves.
Loading
Loading