GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — SSL transport drops buffered data when peer closes TCP without close_notify while reading is paused · Issue #792 · MagicStack/uvloop

SSL transport drops buffered data when peer closes TCP without close_notify while reading is paused #792

New issue
Copy link
New issue
Copy link
Open
Open
SSL transport drops buffered data when peer closes TCP without close_notify while reading is paused#792
Copy link

Description

@andrew-stelmach-fleet
andrew-stelmach-fleet
opened on Nov 11, 2026
Issue body actions

Summary

When a TLS peer sends its data and then closes the TCP connection without sending
close_notify, and the application protocol has called pause_reading() at that
moment, uvloop discards plaintext that was received but not yet delivered to
data_received(). connection_lost(None) is then called, so the loss looks like a
clean close. The standard asyncio loop delivers all the data in the same scenario.

Reproduction

Self-contained; needs only the openssl CLI to make a throwaway certificate.

"""Usage: python repro.py [uvloop|asyncio]"""
import asyncio, socket, ssl, subprocess, sys, tempfile, threading

SIZE = 2_000_000


def serve(listener, ctx):
    raw, _ = listener.accept()
    tls = ctx.wrap_socket(raw, server_side=True)
    tls.recv(2)
    tls.sendall(b"x" * SIZE)
    tls.close()  # TCP close, no unwrap(): no close_notify is sent


class SlowReader(asyncio.Protocol):
    def __init__(self, loop):
        self.loop, self.received = loop, 0
        self.closed = loop.create_future()

    def connection_made(self, transport):
        self.transport = transport
        transport.write(b"go")

    def data_received(self, data):
        self.received += len(data)
        self.transport.pause_reading()
        self.loop.call_later(0.02, self.transport.resume_reading)

    def connection_lost(self, exc):
        self.closed.set_result(exc)


async def main(port):
    loop = asyncio.get_running_loop()
    ctx = ssl.create_default_context()
    ctx.check_hostname, ctx.verify_mode = False, ssl.CERT_NONE
    _, proto = await loop.create_connection(lambda: SlowReader(loop), "127.0.0.1", port, ssl=ctx)
    exc = await asyncio.wait_for(proto.closed, 30)
    print(f"{type(loop).__module__}: received {proto.received} of {SIZE}, connection_lost({exc!r})")


with tempfile.TemporaryDirectory() as d:
    subprocess.run(
        ["openssl", "req", "-x509", "-newkey", "rsa:2048", "-nodes", "-days", "1",
         "-subj", "/CN=localhost", "-keyout", f"{d}/k.pem", "-out", f"{d}/c.pem"],
        check=True, capture_output=True,
    )
    server_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
    server_ctx.load_cert_chain(f"{d}/c.pem", f"{d}/k.pem")
listener = socket.create_server(("127.0.0.1", 0))
threading.Thread(target=serve, args=(listener, server_ctx), daemon=True).start()
port = listener.getsockname()[1]
if sys.argv[1:] == ["uvloop"]:
    import uvloop
    uvloop.run(main(port))
else:
    asyncio.run(main(port))

Actual vs expected

uvloop: received 1884160 of 2000000, connection_lost(None)
asyncio.unix_events: received 2000000 of 2000000, connection_lost(None)

The amount lost varies from run to run. If the server calls unwrap() before closing
(sends close_notify), uvloop delivers everything.

Real-world impact

Amazon S3 answers every 100th request on a keep-alive connection with
Connection: close and closes this way. An aiohttp client that streams such a
response to a slower consumer (so aiohttp's flow control pauses reading) gets
ClientPayloadError: Response payload is not completed: ContentLengthError on
uvloop, and never on asyncio.

Versions

  • uvloop 0.19.0, 0.21.0, 0.22.1 and 0.23.0 all reproduce
  • CPython 3.12.13, OpenSSL 3.5.5
  • macOS 27 (arm64) and Linux (x86_64)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402503 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.