Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[webtransport] Suppress benign error from StreamWriter.close() #48351

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 28 additions & 3 deletions tools/webtransport/h3/webtransport_h3_server.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# mypy: allow-subclassing-any, no-warn-return-any

import asyncio
import contextlib
import logging
import os
import ssl
Expand All @@ -9,17 +10,21 @@
import traceback
from enum import IntEnum
from urllib.parse import urlparse
from typing import Any, Dict, List, Optional, Tuple
from typing import Any, Dict, List, Optional, Tuple, cast

# TODO(bashi): Remove import check suppressions once aioquic dependency is resolved.
from aioquic.buffer import Buffer # type: ignore
from aioquic.asyncio import QuicConnectionProtocol, serve # type: ignore
from aioquic.asyncio.client import connect # type: ignore
from aioquic.asyncio.protocol import QuicStreamAdapter # type: ignore
from aioquic.h3.connection import H3_ALPN, FrameType, H3Connection, ProtocolError, SettingsError # type: ignore
from aioquic.h3.events import H3Event, HeadersReceived, WebTransportStreamDataReceived, DatagramReceived, DataReceived # type: ignore
from aioquic.quic.configuration import QuicConfiguration # type: ignore
from aioquic.quic.connection import logger as quic_connection_logger # type: ignore
from aioquic.quic.connection import stream_is_unidirectional
from aioquic.quic.connection import (
stream_is_client_initiated,
stream_is_unidirectional,
)
from aioquic.quic.events import QuicEvent, ProtocolNegotiated, ConnectionTerminated, StreamReset # type: ignore
from aioquic.tls import SessionTicket # type: ignore

Expand Down Expand Up @@ -602,12 +607,32 @@ async def _connect_server_with_timeout(host: str, port: int, timeout: float) ->
return True


def _close_unusable_writer(reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None:
# Starting in python3.11, `StreamWriter.__del__` implicitly `close()`s
# itself [0], if it has not done so already. Because aioquic sometimes
# models a unidirectional stream with a bidirectional transport [1], the
# `writer` here for a server -> client stream may log a benign but
# scary-looking exception when it's GC'ed and unsuccessfully writes EOF.
#
# For the purpose of checking connectivity, work around this for now by
# preemptively closing such streams and suppressing the resulting
# exceptions.
#
# [0]: https://github.com/python/cpython/blob/3.11/Lib/asyncio/streams.py#L413
# [1]: https://github.com/aiortc/aioquic/blob/1.2.0/src/aioquic/asyncio/protocol.py#L241
stream_id = cast(QuicStreamAdapter, writer.transport).stream_id
if stream_is_unidirectional(stream_id) and not stream_is_client_initiated(stream_id):
with contextlib.suppress(ValueError):
writer.close()


async def _connect_to_server(host: str, port: int) -> None:
configuration = QuicConfiguration(
alpn_protocols=H3_ALPN,
is_client=True,
verify_mode=ssl.CERT_NONE,
)

async with connect(host, port, configuration=configuration) as protocol:
async with connect(host, port, configuration=configuration,
stream_handler=_close_unusable_writer) as protocol:
await protocol.ping()