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

Skip to content

Commit c44ecdf

Browse files
committed
Issue #25441: asyncio: Raise error from drain() when socket is closed.
1 parent 2bf91bf commit c44ecdf

3 files changed

Lines changed: 54 additions & 0 deletions

File tree

Lib/asyncio/streams.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,15 @@ def drain(self):
301301
exc = self._reader.exception()
302302
if exc is not None:
303303
raise exc
304+
if self._transport is not None:
305+
if self._transport._closing:
306+
# Yield to the event loop so connection_lost() may be
307+
# called. Without this, _drain_helper() would return
308+
# immediately, and code that calls
309+
# write(...); yield from drain()
310+
# in a loop would never call connection_lost(), so it
311+
# would not see an error when the socket is closed.
312+
yield
304313
yield from self._protocol._drain_helper()
305314

306315

Lib/test/test_asyncio/test_streams.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
import gc
44
import os
5+
import queue
56
import socket
67
import sys
8+
import threading
79
import unittest
810
from unittest import mock
911
try:
@@ -632,6 +634,47 @@ def test_streamreaderprotocol_constructor(self):
632634
protocol = asyncio.StreamReaderProtocol(reader)
633635
self.assertIs(protocol._loop, self.loop)
634636

637+
def test_drain_raises(self):
638+
# See http://bugs.python.org/issue25441
639+
640+
# This test should not use asyncio for the mock server; the
641+
# whole point of the test is to test for a bug in drain()
642+
# where it never gives up the event loop but the socket is
643+
# closed on the server side.
644+
645+
q = queue.Queue()
646+
647+
def server():
648+
# Runs in a separate thread.
649+
sock = socket.socket()
650+
sock.bind(('localhost', 0))
651+
sock.listen(1)
652+
addr = sock.getsockname()
653+
q.put(addr)
654+
clt, _ = sock.accept()
655+
clt.close()
656+
657+
@asyncio.coroutine
658+
def client(host, port):
659+
reader, writer = yield from asyncio.open_connection(host, port, loop=self.loop)
660+
while True:
661+
writer.write(b"foo\n")
662+
yield from writer.drain()
663+
664+
# Start the server thread and wait for it to be listening.
665+
thread = threading.Thread(target=server)
666+
thread.setDaemon(True)
667+
thread.start()
668+
addr = q.get()
669+
670+
# Should not be stuck in an infinite loop.
671+
with self.assertRaises((ConnectionResetError, BrokenPipeError)):
672+
self.loop.run_until_complete(client(*addr))
673+
674+
# Clean up the thread. (Only on success; on failure, it may
675+
# be stuck in accept().)
676+
thread.join()
677+
635678
def test___repr__(self):
636679
stream = asyncio.StreamReader(loop=self.loop)
637680
self.assertEqual("<StreamReader>", repr(stream))

Misc/NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ Core and Builtins
9696
Library
9797
-------
9898

99+
- Issue #25441: asyncio: Raise error from drain() when socket is closed.
100+
99101
- Issue #25411: Improved Unicode support in SMTPHandler through better use of
100102
the email package. Thanks to user simon04 for the patch.
101103

0 commit comments

Comments
 (0)