aiortc / aioquic

QUIC and HTTP/3 implementation in Python

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

aioquic.asyncio.connect does not respond to interrupts

b0bh00d opened this issue · comments

I'm using aioquic.asyncio.connect() in the following way:

do_run = True
while do_run:
    try:
        async with connect(self._settings.address,
                           int(self._settings.port),
                           configuration=self._quic_config,
                           create_protocol=self.generate_handler) as client:

            <use client here>

    except ConnectionError:
        await asyncio.sleep(self._settings.retry)

    except asyncio.CancelledError:
        do_run = False
    except KeyboardInterrupt:
        do_run = False

While sitting in aioquic.asyncio.connect() waiting for the connect, pressing ctrl-c (KeyboardInterrupt) from the command line does not break out as expected. Even an abort (ctrl-) does nothing.

I need aioquic.asyncio.connect() to interrupt when I need it to. What am I doing wrong here?

Do you know if the connection is actually established?

No, actually it isn't. The server side is deliberately not running (for testing this very thing) so connect() is sitting there attempting.

I can't replicate what you're seeing so far. I took the code sketch you provided and expanded it into something that could run with QUIC logging on, and ran it. The connect() call is configured to wait until connected, and I see QUIC following the expected "loss detection triggered" path since there is nothing on the other end answering. If I hit ^C, the asyncio wait is canceled and the program exits.

I'm running aioquic 1.0.0 with python 3.12.3 on macOS. What are your versions of aioquic, python, and what OS?

Sample run:

% python connect-issue.py
2024-05-10 06:18:06,692 DEBUG: Using selector: KqueueSelector
TOP
2024-05-10 06:18:06,698 DEBUG: [9b7c51fef967e7fe] TLS State.CLIENT_HANDSHAKE_START -> State.CLIENT_EXPECT_SERVER_HELLO
2024-05-10 06:18:06,899 DEBUG: [9b7c51fef967e7fe] Loss detection triggered
2024-05-10 06:18:06,899 DEBUG: [9b7c51fef967e7fe] Scheduled CRYPTO data for retransmission
2024-05-10 06:18:07,301 DEBUG: [9b7c51fef967e7fe] Loss detection triggered
2024-05-10 06:18:07,301 DEBUG: [9b7c51fef967e7fe] Scheduled CRYPTO data for retransmission
^C
2024-05-10 06:18:07,537 INFO: [9b7c51fef967e7fe] Connection close sent (code 0x0, reason )
2024-05-10 06:18:07,537 DEBUG: [9b7c51fef967e7fe] QuicConnectionState.FIRSTFLIGHT -> QuicConnectionState.CLOSING
2024-05-10 06:18:08,138 DEBUG: [9b7c51fef967e7fe] Discarding epoch Epoch.INITIAL
2024-05-10 06:18:08,139 DEBUG: [9b7c51fef967e7fe] Discarding epoch Epoch.HANDSHAKE
2024-05-10 06:18:08,139 DEBUG: [9b7c51fef967e7fe] Discarding epoch Epoch.ONE_RTT
2024-05-10 06:18:08,139 DEBUG: [9b7c51fef967e7fe] QuicConnectionState.CLOSING -> QuicConnectionState.TERMINATED
2024-05-10 06:18:08,139 ERROR: Future exception was never retrieved
future: <Future finished exception=ConnectionError()>
ConnectionError
CANCEL

and the updated source I tested with:

import asyncio
import logging

import aioquic.quic
import aioquic.quic.configuration
from aioquic.asyncio import connect

config = aioquic.quic.configuration.QuicConfiguration()

address = "10.0.0.1"
port = 5354

logging.basicConfig(
    format="%(asctime)s %(levelname)s: %(message)s", level=logging.DEBUG
)


async def try_connect():
    do_run = True
    while do_run:
        print("TOP")
        try:
            async with connect(
                address,
                port,
                configuration=config,
            ) as client:
                print("connected")
        except ConnectionError:
            print("RETRY")
            await asyncio.sleep(10)
        except asyncio.CancelledError:
            print("CANCEL")
            do_run = False
        except KeyboardInterrupt:
            print("KEYBOARD")
            do_run = False
        except Exception as e:
            print("EXCEPTION", type(e), e)


asyncio.run(try_connect())

This turns out to be pilot error. The code fragment I provided in the original post is in a class that inherits from another, and when I went down that rabbit hole, I found that the superclass is trapping Linux signals, and the signal handler is setting a class variable that subclasses are expected to regard. This prevents the try/except wrapper around aioquic.asyncio.connect() from responding to KeyboardInterrupt, and the loop does not terminate.

Effectively, this was a hidden catch-and-kill. My apologies, and my gratitude for the extra pair of eyes on this.