Skip to content

Commit 7508a14

Browse files
committed
Merge remote-tracking branch 'upstream/main' into fix-structseq-validate-desc
2 parents d5771c0 + 416c346 commit 7508a14

3 files changed

Lines changed: 98 additions & 58 deletions

File tree

Lib/asyncio/windows_events.py

Lines changed: 42 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -760,6 +760,46 @@ def _get_accept_socket(self, family):
760760
s.settimeout(0)
761761
return s
762762

763+
def _process_completion_status(self, status):
764+
"""Process a single status from the completion port.
765+
766+
A caller that waits on the completion port itself can pass each
767+
status it receives here.
768+
"""
769+
err, transferred, key, address = status
770+
try:
771+
f, ov, obj, callback = self._cache.pop(address)
772+
except KeyError:
773+
if self._loop.get_debug():
774+
self._loop.call_exception_handler({
775+
'message': ('GetQueuedCompletionStatus() returned an '
776+
'unexpected event'),
777+
'status': ('err=%s transferred=%s key=%#x address=%#x'
778+
% (err, transferred, key, address)),
779+
})
780+
781+
# key is either zero, or it is used to return a pipe
782+
# handle which should be closed to avoid a leak.
783+
if key not in (0, _overlapped.INVALID_HANDLE_VALUE):
784+
_winapi.CloseHandle(key)
785+
return
786+
787+
if obj in self._stopped_serving:
788+
f.cancel()
789+
# Don't call the callback if _register() already read the result or
790+
# if the overlapped has been cancelled
791+
elif not f.done():
792+
try:
793+
value = callback(transferred, key, ov)
794+
except OSError as e:
795+
f.set_exception(e)
796+
self._results.append(f)
797+
else:
798+
f.set_result(value)
799+
self._results.append(f)
800+
finally:
801+
f = None
802+
763803
def _poll(self, timeout=None):
764804
if timeout is None:
765805
ms = INFINITE
@@ -778,39 +818,8 @@ def _poll(self, timeout=None):
778818
break
779819
ms = 0
780820

781-
err, transferred, key, address = status
782-
try:
783-
f, ov, obj, callback = self._cache.pop(address)
784-
except KeyError:
785-
if self._loop.get_debug():
786-
self._loop.call_exception_handler({
787-
'message': ('GetQueuedCompletionStatus() returned an '
788-
'unexpected event'),
789-
'status': ('err=%s transferred=%s key=%#x address=%#x'
790-
% (err, transferred, key, address)),
791-
})
792-
793-
# key is either zero, or it is used to return a pipe
794-
# handle which should be closed to avoid a leak.
795-
if key not in (0, _overlapped.INVALID_HANDLE_VALUE):
796-
_winapi.CloseHandle(key)
797-
continue
798-
799-
if obj in self._stopped_serving:
800-
f.cancel()
801-
# Don't call the callback if _register() already read the result or
802-
# if the overlapped has been cancelled
803-
elif not f.done():
804-
try:
805-
value = callback(transferred, key, ov)
806-
except OSError as e:
807-
f.set_exception(e)
808-
self._results.append(f)
809-
else:
810-
f.set_result(value)
811-
self._results.append(f)
812-
finally:
813-
f = None
821+
# gh-154971: split out so custom event loops can call it directly
822+
self._process_completion_status(status)
814823

815824
# Remove unregistered futures
816825
for ov in self._unregistered:

Lib/test/test_asyncio/test_windows_events.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,28 @@ def threadMain():
327327
stop.set()
328328
thr.join()
329329

330+
def test_custom_poll_integration(self):
331+
# gh-154971: a caller can wait on the completion port and process statuses itself
332+
proactor = self.loop._proactor
333+
334+
a, b = socket.socketpair()
335+
self.addCleanup(a.close)
336+
self.addCleanup(b.close)
337+
338+
fut = proactor.recv(a, 100)
339+
self.assertFalse(fut.done())
340+
341+
b.send(b'data')
342+
343+
deadline = time.monotonic() + support.SHORT_TIMEOUT
344+
while not fut.done() and time.monotonic() < deadline:
345+
status = _overlapped.GetQueuedCompletionStatus(proactor._iocp, 100)
346+
if status is not None:
347+
proactor._process_completion_status(status)
348+
349+
self.assertTrue(fut.done())
350+
self.assertEqual(fut.result(), b'data')
351+
330352

331353
class ProactorPipeObjectSupportTests(unittest.TestCase):
332354

Lib/test/test_class.py

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import unittest
44
from test import support
5-
from test.support import cpython_only, import_helper, script_helper
5+
from test.support import cpython_only, import_helper, isolation
66

77
testmeths = [
88

@@ -1014,34 +1014,43 @@ class C:
10141014
C.a = X()
10151015

10161016
@support.nomemtest
1017+
@isolation.runInSubprocess()
10171018
def test_detach_materialized_dict_no_memory(self):
1018-
code = """if 1:
1019-
import test.support
1020-
import _testcapi
1021-
1022-
class A:
1023-
def __init__(self):
1024-
self.a = 1
1025-
self.b = 2
1019+
import _testcapi
1020+
1021+
class A:
1022+
def __init__(self):
1023+
self.a = 1
1024+
self.b = 2
1025+
1026+
# The failing allocation should be the one which detaches the
1027+
# dictionary from the object, but other allocations can happen
1028+
# first, so try to fail every one of the first allocations.
1029+
raised = False
1030+
for n in range(20):
10261031
a = A()
10271032
d = a.__dict__
1028-
1029-
test.support.gc_collect()
1030-
with test.support.catch_unraisable_exception() as ex:
1031-
_testcapi.set_nomemory(0, 1)
1032-
del a
1033-
assert ex.unraisable.exc_type is MemoryError
10341033
try:
1035-
d["a"]
1036-
except KeyError:
1037-
pass
1038-
else:
1039-
assert False, "KeyError not raised"
1040-
"""
1041-
rc, out, err = script_helper.assert_python_ok("-c", code)
1042-
self.assertEqual(rc, 0)
1043-
self.assertFalse(out, msg=out.decode('utf-8'))
1044-
self.assertFalse(err, msg=err.decode('utf-8'))
1034+
with support.catch_unraisable_exception() as ex:
1035+
_testcapi.set_nomemory(n, n + 1)
1036+
try:
1037+
del a
1038+
finally:
1039+
_testcapi.remove_mem_hooks()
1040+
exc_type = ex.unraisable and ex.unraisable.exc_type
1041+
except MemoryError:
1042+
# The failing allocation was not in the deallocation code.
1043+
continue
1044+
if exc_type is not MemoryError:
1045+
continue
1046+
raised = True
1047+
if "a" not in d:
1048+
# The dictionary was cleared, as expected.
1049+
break
1050+
else:
1051+
if not raised:
1052+
self.fail("MemoryError was not raised during deallocation")
1053+
self.fail("the dictionary was not cleared")
10451054

10461055
if __name__ == '__main__':
10471056
unittest.main()

0 commit comments

Comments
 (0)