MagicStack / uvloop

Ultra fast asyncio event loop.
Apache License 2.0
10.39k stars 541 forks source link

Missing support for `interleave` and `happy_eyeballs_delay` in `create_connection` #406

Open vuori777 opened 3 years ago

vuori777 commented 3 years ago

asyncio loop.create_connection supports the interleave and happy_eyeballs_delay keyword arguments to enable RFC 8305 "Happy Eyeballs" IPv6-to-IPv4 fallback since Python 3.8. The arguments are documented here: https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.create_connection

The corresponding function in uvloop does not accept these arguments. These would be nice to have because I'm working on a server that proxies requests to external third-party servers which occasionally have a broken IPv6 configuration and traditional connect timeout behavior causes quite a bit of delay.

paulefoe commented 3 years ago

This looks interesting to me. I've dug around for a few days now, but I've never worked with the cython.

I've read this RFC https://tools.ietf.org/html/rfc8305.html and basically try to backport this https://github.com/python/cpython/pull/7237/files

If someone has any tips or guidance I would really appreciate that.

vuori777 commented 3 years ago

I haven't implemented this myself and am also unfamiliar with Cython so can't really help. We managed to work around the problem with selective application of native asyncio, since this problem only affected a relative low-traffic portion of the app.

paulefoe commented 3 years ago

Hey @1st1 @fantix I think I really want to dig into this and implement it, but I want to double-check if it's something you will be willing to accept?

1st1 commented 3 years ago

We're obviously willing to accept such a PR.

dmgolembiowski commented 3 years ago

Seems like this is doable. From what I've seen, the scope of the feature would apply to changes to loop.pyx and loop.pyi. Hypothetically, it might need to satisfy:

import uvloop
import asyncio
from typing import *

async def leave_happy(
        host, 
        port,  
        happy_eyeballs_delay: Optional[float] = None, 
        interleave: Optional[int] = None):

    if interleave is not None:
        if not interleave & 1:
            raise ValueError("`interleave` argument must be non-negative")
    else:
        interleave = 0

    loop = asyncio.get_running_loop()

    (transp, pro) = await loop.create_connection(
            uvloop.EventLoopPolicy, 
            host=host, 
            port=port,
            happy_eyeballs_delay=happy_eyeballs_delay,
            interleave=interleave
    )

    return (transp, pro)

async def main():
    await leave_happy(host="127.0.0.1", port=2160)

asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
asyncio.run(main())
dmgolembiowski commented 3 years ago

We're obviously willing to accept such a PR.

I briefly compared Lib/asyncio/base_events.py against uvloop/handles/handle.pyx, and believe I found the appropriate entrypoint to inject this logic. Since loop.create_server shares a similar signature with loop.create_connection, does it seem valid to apply this here?