Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 42d3bde

Browse files
committed
asyncio, tulip issue 196: ProactorIocp._register() now registers the overlapped
in the _cache dictionary, even if we already got the result. We need to keep a reference to the overlapped object, otherwise the memory may be reused and GetQueuedCompletionStatus() may use random bytes and behaves badly. There is still a hack for ConnectNamedPipe(): the overlapped object is not register into _cache if the overlapped object completed directly. Log also an error in debug mode in ProactorIocp._loop() if we get an unexpected event. Add a protection in ProactorIocp.close() to avoid blocking, even if it should not happen. I still don't understand exactly why some the completion of some overlapped objects are not notified.
1 parent 8b95d5e commit 42d3bde

1 file changed

Lines changed: 38 additions & 15 deletions

File tree

Lib/asyncio/windows_events.py

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,10 @@ def finish_accept_pipe(trans, key, ov):
369369
ov.getresult()
370370
return pipe
371371

372-
return self._register(ov, pipe, finish_accept_pipe)
372+
# FIXME: Tulip issue 196: why to we neeed register=False?
373+
# See also the comment in the _register() method
374+
return self._register(ov, pipe, finish_accept_pipe,
375+
register=False)
373376

374377
def connect_pipe(self, address):
375378
ov = _overlapped.Overlapped(NULL)
@@ -429,17 +432,13 @@ def _register_with_iocp(self, obj):
429432
# to avoid sending notifications to completion port of ops
430433
# that succeed immediately.
431434

432-
def _register(self, ov, obj, callback, wait_for_post=False):
435+
def _register(self, ov, obj, callback,
436+
wait_for_post=False, register=True):
433437
# Return a future which will be set with the result of the
434438
# operation when it completes. The future's value is actually
435439
# the value returned by callback().
436440
f = _OverlappedFuture(ov, loop=self._loop)
437-
if ov.pending or wait_for_post:
438-
# Register the overlapped operation for later. Note that
439-
# we only store obj to prevent it from being garbage
440-
# collected too early.
441-
self._cache[ov.address] = (f, ov, obj, callback)
442-
else:
441+
if not ov.pending and not wait_for_post:
443442
# The operation has completed, so no need to postpone the
444443
# work. We cannot take this short cut if we need the
445444
# NumberOfBytes, CompletionKey values returned by
@@ -450,6 +449,23 @@ def _register(self, ov, obj, callback, wait_for_post=False):
450449
f.set_exception(e)
451450
else:
452451
f.set_result(value)
452+
# Even if GetOverlappedResult() was called, we have to wait for the
453+
# notification of the completion in GetQueuedCompletionStatus().
454+
# Register the overlapped operation to keep a reference to the
455+
# OVERLAPPED object, otherwise the memory is freed and Windows may
456+
# read uninitialized memory.
457+
#
458+
# For an unknown reason, ConnectNamedPipe() behaves differently:
459+
# the completion is not notified by GetOverlappedResult() if we
460+
# already called GetOverlappedResult(). For this specific case, we
461+
# don't expect notification (register is set to False).
462+
else:
463+
register = True
464+
if register:
465+
# Register the overlapped operation for later. Note that
466+
# we only store obj to prevent it from being garbage
467+
# collected too early.
468+
self._cache[ov.address] = (f, ov, obj, callback)
453469
return f
454470

455471
def _get_accept_socket(self, family):
@@ -476,22 +492,26 @@ def _poll(self, timeout=None):
476492
try:
477493
f, ov, obj, callback = self._cache.pop(address)
478494
except KeyError:
495+
if self._loop.get_debug():
496+
self._loop.call_exception_handler({
497+
'message': ('GetQueuedCompletionStatus() returned an '
498+
'unexpected event'),
499+
'status': ('err=%s transferred=%s key=%#x address=%#x'
500+
% (err, transferred, key, address)),
501+
})
502+
479503
# key is either zero, or it is used to return a pipe
480504
# handle which should be closed to avoid a leak.
481505
if key not in (0, _overlapped.INVALID_HANDLE_VALUE):
482506
_winapi.CloseHandle(key)
483507
ms = 0
484508
continue
485509

486-
if ov.pending:
487-
# False alarm: the overlapped operation is not completed.
488-
# FIXME: why do we get false alarms?
489-
self._cache[address] = (f, ov, obj, callback)
490-
continue
491-
492510
if obj in self._stopped_serving:
493511
f.cancel()
494-
elif not f.cancelled():
512+
# Don't call the callback if _register() already read the result or
513+
# if the overlapped has been cancelled
514+
elif not f.done():
495515
try:
496516
value = callback(transferred, key, ov)
497517
except OSError as e:
@@ -516,6 +536,9 @@ def close(self):
516536
# queues a task to Windows' thread pool. This cannot
517537
# be cancelled, so just forget it.
518538
del self._cache[address]
539+
# FIXME: Tulip issue 196: remove this case, it should not happen
540+
elif fut.done() and not fut.cancelled():
541+
del self._cache[address]
519542
else:
520543
try:
521544
fut.cancel()

0 commit comments

Comments
 (0)