Skip to content

Commit f5bd65e

Browse files
jkriegshauserambv
andauthored
[3.8] gh-116773: Fix overlapped memory corruption crash (GH-116774) (GH-117083)
Co-authored-by: Łukasz Langa <lukasz@langa.pl>
1 parent 928d735 commit f5bd65e

File tree

4 files changed

+71
-12
lines changed

4 files changed

+71
-12
lines changed

Lib/asyncio/windows_events.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -318,13 +318,13 @@ def run_forever(self):
318318
if self._self_reading_future is not None:
319319
ov = self._self_reading_future._ov
320320
self._self_reading_future.cancel()
321-
# self_reading_future was just cancelled so if it hasn't been
322-
# finished yet, it never will be (it's possible that it has
323-
# already finished and its callback is waiting in the queue,
324-
# where it could still happen if the event loop is restarted).
325-
# Unregister it otherwise IocpProactor.close will wait for it
326-
# forever
327-
if ov is not None:
321+
# self_reading_future always uses IOCP, so even though it's
322+
# been cancelled, we need to make sure that the IOCP message
323+
# is received so that the kernel is not holding on to the
324+
# memory, possibly causing memory corruption later. Only
325+
# unregister it if IO is complete in all respects. Otherwise
326+
# we need another _poll() later to complete the IO.
327+
if ov is not None and not ov.pending:
328328
self._proactor._unregister(ov)
329329
self._self_reading_future = None
330330

Lib/test/test_asyncio/test_windows_events.py

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,23 @@ def data_received(self, data):
3636
self.trans.close()
3737

3838

39-
class ProactorLoopCtrlC(test_utils.TestCase):
39+
class WindowsEventsTestCase(test_utils.TestCase):
40+
def _unraisablehook(self, unraisable):
41+
# Storing unraisable.object can resurrect an object which is being
42+
# finalized. Storing unraisable.exc_value creates a reference cycle.
43+
self._unraisable = unraisable
44+
print(unraisable)
45+
46+
def setUp(self):
47+
self._prev_unraisablehook = sys.unraisablehook
48+
self._unraisable = None
49+
sys.unraisablehook = self._unraisablehook
50+
51+
def tearDown(self):
52+
sys.unraisablehook = self._prev_unraisablehook
53+
self.assertIsNone(self._unraisable)
54+
55+
class ProactorLoopCtrlC(WindowsEventsTestCase):
4056

4157
def test_ctrl_c(self):
4258

@@ -58,7 +74,7 @@ def SIGINT_after_delay():
5874
thread.join()
5975

6076

61-
class ProactorMultithreading(test_utils.TestCase):
77+
class ProactorMultithreading(WindowsEventsTestCase):
6278
def test_run_from_nonmain_thread(self):
6379
finished = False
6480

@@ -79,7 +95,7 @@ def func():
7995
self.assertTrue(finished)
8096

8197

82-
class ProactorTests(test_utils.TestCase):
98+
class ProactorTests(WindowsEventsTestCase):
8399

84100
def setUp(self):
85101
super().setUp()
@@ -239,8 +255,32 @@ def test_read_self_pipe_restart(self):
239255
self.close_loop(self.loop)
240256
self.assertFalse(self.loop.call_exception_handler.called)
241257

242-
243-
class WinPolicyTests(test_utils.TestCase):
258+
def test_loop_restart(self):
259+
# We're fishing for the "RuntimeError: <_overlapped.Overlapped object at XXX>
260+
# still has pending operation at deallocation, the process may crash" error
261+
stop = threading.Event()
262+
def threadMain():
263+
while not stop.is_set():
264+
self.loop.call_soon_threadsafe(lambda: None)
265+
time.sleep(0.01)
266+
thr = threading.Thread(target=threadMain)
267+
268+
# In 10 60-second runs of this test prior to the fix:
269+
# time in seconds until failure: (none), 15.0, 6.4, (none), 7.6, 8.3, 1.7, 22.2, 23.5, 8.3
270+
# 10 seconds had a 50% failure rate but longer would be more costly
271+
end_time = time.time() + 10 # Run for 10 seconds
272+
self.loop.call_soon(thr.start)
273+
while not self._unraisable: # Stop if we got an unraisable exc
274+
self.loop.stop()
275+
self.loop.run_forever()
276+
if time.time() >= end_time:
277+
break
278+
279+
stop.set()
280+
thr.join()
281+
282+
283+
class WinPolicyTests(WindowsEventsTestCase):
244284

245285
def test_selector_win_policy(self):
246286
async def main():
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix instances of ``<_overlapped.Overlapped object at 0xXXX> still has pending operation at deallocation, the process may crash``.

Modules/overlapped.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,24 @@ Overlapped_dealloc(OverlappedObject *self)
624624
if (!HasOverlappedIoCompleted(&self->overlapped) &&
625625
self->type != TYPE_NOT_STARTED)
626626
{
627+
// NOTE: We should not get here, if we do then something is wrong in
628+
// the IocpProactor or ProactorEventLoop. Since everything uses IOCP if
629+
// the overlapped IO hasn't completed yet then we should not be
630+
// deallocating!
631+
//
632+
// The problem is likely that this OverlappedObject was removed from
633+
// the IocpProactor._cache before it was complete. The _cache holds a
634+
// reference while IO is pending so that it does not get deallocated
635+
// while the kernel has retained the OVERLAPPED structure.
636+
//
637+
// CancelIoEx (likely called from self.cancel()) may have successfully
638+
// completed, but the OVERLAPPED is still in use until either
639+
// HasOverlappedIoCompleted() is true or GetQueuedCompletionStatus has
640+
// returned this OVERLAPPED object.
641+
//
642+
// NOTE: Waiting when IOCP is in use can hang indefinitely, but this
643+
// CancelIoEx is superfluous in that self.cancel() was already called,
644+
// so I've only ever seen this return FALSE with GLE=ERROR_NOT_FOUND
627645
if (Py_CancelIoEx && Py_CancelIoEx(self->handle, &self->overlapped))
628646
wait = TRUE;
629647

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy