@@ -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