mirror of
https://github.com/python/cpython.git
synced 2025-09-25 01:43:11 +00:00
[3.12] gh-116773: Fix overlapped memory corruption crash (GH-116774) (#117078)
Manual backport of gh-116774.
This commit is contained in:
parent
8e00ead351
commit
d69c51520e
4 changed files with 70 additions and 11 deletions
|
@ -323,13 +323,13 @@ class ProactorEventLoop(proactor_events.BaseProactorEventLoop):
|
||||||
if self._self_reading_future is not None:
|
if self._self_reading_future is not None:
|
||||||
ov = self._self_reading_future._ov
|
ov = self._self_reading_future._ov
|
||||||
self._self_reading_future.cancel()
|
self._self_reading_future.cancel()
|
||||||
# self_reading_future was just cancelled so if it hasn't been
|
# self_reading_future always uses IOCP, so even though it's
|
||||||
# finished yet, it never will be (it's possible that it has
|
# been cancelled, we need to make sure that the IOCP message
|
||||||
# already finished and its callback is waiting in the queue,
|
# is received so that the kernel is not holding on to the
|
||||||
# where it could still happen if the event loop is restarted).
|
# memory, possibly causing memory corruption later. Only
|
||||||
# Unregister it otherwise IocpProactor.close will wait for it
|
# unregister it if IO is complete in all respects. Otherwise
|
||||||
# forever
|
# we need another _poll() later to complete the IO.
|
||||||
if ov is not None:
|
if ov is not None and not ov.pending:
|
||||||
self._proactor._unregister(ov)
|
self._proactor._unregister(ov)
|
||||||
self._self_reading_future = None
|
self._self_reading_future = None
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,23 @@ class UpperProto(asyncio.Protocol):
|
||||||
self.trans.close()
|
self.trans.close()
|
||||||
|
|
||||||
|
|
||||||
class ProactorLoopCtrlC(test_utils.TestCase):
|
class WindowsEventsTestCase(test_utils.TestCase):
|
||||||
|
def _unraisablehook(self, unraisable):
|
||||||
|
# Storing unraisable.object can resurrect an object which is being
|
||||||
|
# finalized. Storing unraisable.exc_value creates a reference cycle.
|
||||||
|
self._unraisable = unraisable
|
||||||
|
print(unraisable)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self._prev_unraisablehook = sys.unraisablehook
|
||||||
|
self._unraisable = None
|
||||||
|
sys.unraisablehook = self._unraisablehook
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
sys.unraisablehook = self._prev_unraisablehook
|
||||||
|
self.assertIsNone(self._unraisable)
|
||||||
|
|
||||||
|
class ProactorLoopCtrlC(WindowsEventsTestCase):
|
||||||
|
|
||||||
def test_ctrl_c(self):
|
def test_ctrl_c(self):
|
||||||
|
|
||||||
|
@ -58,7 +74,7 @@ class ProactorLoopCtrlC(test_utils.TestCase):
|
||||||
thread.join()
|
thread.join()
|
||||||
|
|
||||||
|
|
||||||
class ProactorMultithreading(test_utils.TestCase):
|
class ProactorMultithreading(WindowsEventsTestCase):
|
||||||
def test_run_from_nonmain_thread(self):
|
def test_run_from_nonmain_thread(self):
|
||||||
finished = False
|
finished = False
|
||||||
|
|
||||||
|
@ -79,7 +95,7 @@ class ProactorMultithreading(test_utils.TestCase):
|
||||||
self.assertTrue(finished)
|
self.assertTrue(finished)
|
||||||
|
|
||||||
|
|
||||||
class ProactorTests(test_utils.TestCase):
|
class ProactorTests(WindowsEventsTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
@ -283,8 +299,32 @@ class ProactorTests(test_utils.TestCase):
|
||||||
|
|
||||||
return "done"
|
return "done"
|
||||||
|
|
||||||
|
def test_loop_restart(self):
|
||||||
|
# We're fishing for the "RuntimeError: <_overlapped.Overlapped object at XXX>
|
||||||
|
# still has pending operation at deallocation, the process may crash" error
|
||||||
|
stop = threading.Event()
|
||||||
|
def threadMain():
|
||||||
|
while not stop.is_set():
|
||||||
|
self.loop.call_soon_threadsafe(lambda: None)
|
||||||
|
time.sleep(0.01)
|
||||||
|
thr = threading.Thread(target=threadMain)
|
||||||
|
|
||||||
class WinPolicyTests(test_utils.TestCase):
|
# In 10 60-second runs of this test prior to the fix:
|
||||||
|
# time in seconds until failure: (none), 15.0, 6.4, (none), 7.6, 8.3, 1.7, 22.2, 23.5, 8.3
|
||||||
|
# 10 seconds had a 50% failure rate but longer would be more costly
|
||||||
|
end_time = time.time() + 10 # Run for 10 seconds
|
||||||
|
self.loop.call_soon(thr.start)
|
||||||
|
while not self._unraisable: # Stop if we got an unraisable exc
|
||||||
|
self.loop.stop()
|
||||||
|
self.loop.run_forever()
|
||||||
|
if time.time() >= end_time:
|
||||||
|
break
|
||||||
|
|
||||||
|
stop.set()
|
||||||
|
thr.join()
|
||||||
|
|
||||||
|
|
||||||
|
class WinPolicyTests(WindowsEventsTestCase):
|
||||||
|
|
||||||
def test_selector_win_policy(self):
|
def test_selector_win_policy(self):
|
||||||
async def main():
|
async def main():
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Fix instances of ``<_overlapped.Overlapped object at 0xXXX> still has pending operation at deallocation, the process may crash``.
|
|
@ -719,6 +719,24 @@ Overlapped_dealloc(OverlappedObject *self)
|
||||||
if (!HasOverlappedIoCompleted(&self->overlapped) &&
|
if (!HasOverlappedIoCompleted(&self->overlapped) &&
|
||||||
self->type != TYPE_NOT_STARTED)
|
self->type != TYPE_NOT_STARTED)
|
||||||
{
|
{
|
||||||
|
// NOTE: We should not get here, if we do then something is wrong in
|
||||||
|
// the IocpProactor or ProactorEventLoop. Since everything uses IOCP if
|
||||||
|
// the overlapped IO hasn't completed yet then we should not be
|
||||||
|
// deallocating!
|
||||||
|
//
|
||||||
|
// The problem is likely that this OverlappedObject was removed from
|
||||||
|
// the IocpProactor._cache before it was complete. The _cache holds a
|
||||||
|
// reference while IO is pending so that it does not get deallocated
|
||||||
|
// while the kernel has retained the OVERLAPPED structure.
|
||||||
|
//
|
||||||
|
// CancelIoEx (likely called from self.cancel()) may have successfully
|
||||||
|
// completed, but the OVERLAPPED is still in use until either
|
||||||
|
// HasOverlappedIoCompleted() is true or GetQueuedCompletionStatus has
|
||||||
|
// returned this OVERLAPPED object.
|
||||||
|
//
|
||||||
|
// NOTE: Waiting when IOCP is in use can hang indefinitely, but this
|
||||||
|
// CancelIoEx is superfluous in that self.cancel() was already called,
|
||||||
|
// so I've only ever seen this return FALSE with GLE=ERROR_NOT_FOUND
|
||||||
Py_BEGIN_ALLOW_THREADS
|
Py_BEGIN_ALLOW_THREADS
|
||||||
if (CancelIoEx(self->handle, &self->overlapped))
|
if (CancelIoEx(self->handle, &self->overlapped))
|
||||||
wait = TRUE;
|
wait = TRUE;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue