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

Trio backend doesn't map OSError exceptions #544

Closed
vfazio opened this issue Apr 27, 2022 · 0 comments · Fixed by #543
Closed

Trio backend doesn't map OSError exceptions #544

vfazio opened this issue Apr 27, 2022 · 0 comments · Fixed by #543

Comments

@vfazio
Copy link
Contributor

vfazio commented Apr 27, 2022

Ran into an issue earlier where a bad hostname didn't get propagated as a ConnectError when using Trio but did for asyncio. Because it wasn't mapped, httpx ended up not mapping it to the correct exception class either.

A quick test:

@pytest.mark.anyio
async def test_unavilable_resource():
    origin = Origin(b"https", b"not-a-domain.com", 443)
    with pytest.raises(ConnectError):
        async with AsyncHTTPConnection(origin=origin) as conn:
            response = await conn.request("GET", "https://not-a-domain.com")
(venv) vfazio@vfazio2 /mnt/development/httpcore $ pytest tests/_async/test_connection.py::test_unavilable_resource
==================================================================================================== test session starts ====================================================================================================
platform linux -- Python 3.8.10, pytest-7.0.1, pluggy-1.0.0
rootdir: /mnt/development/httpcore, configfile: setup.cfg
plugins: httpbin-1.0.1, asyncio-0.16.0, anyio-3.5.0, trio-0.7.0
collected 2 items                                                                                                                                                                                                           

tests/_async/test_connection.py .F                                                                                                                                                                                    [100%]

========================================================================================================= FAILURES ==========================================================================================================
______________________________________________________________________________________________ test_unavilable_resource[trio] _______________________________________________________________________________________________

    @pytest.mark.anyio
    async def test_unavilable_resource():
        origin = Origin(b"https", b"not-a-domain.com", 443)
        with pytest.raises(ConnectError):
            async with AsyncHTTPConnection(origin=origin) as conn:
>               response = await conn.request("GET", "https://not-a-domain.com")

tests/_async/test_connection.py:200: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
httpcore/_async/interfaces.py:41: in request
    response = await self.handle_async_request(request)
httpcore/_async/connection.py:86: in handle_async_request
    raise exc
httpcore/_async/connection.py:63: in handle_async_request
    stream = await self._connect(request)
httpcore/_async/connection.py:111: in _connect
    stream = await self._network_backend.connect_tcp(**kwargs)
httpcore/backends/auto.py:29: in connect_tcp
    return await self._backend.connect_tcp(
httpcore/backends/trio.py:122: in connect_tcp
    stream: trio.abc.Stream = await trio.open_tcp_stream(
venv/lib/python3.8/site-packages/trio/_highlevel_open_tcp_stream.py:251: in open_tcp_stream
    targets = await getaddrinfo(host, port, type=SOCK_STREAM)
venv/lib/python3.8/site-packages/trio/_socket.py:183: in getaddrinfo
    return await trio.to_thread.run_sync(
venv/lib/python3.8/site-packages/trio/_threads.py:207: in to_thread_run_sync
    return await trio.lowlevel.wait_task_rescheduled(abort)
venv/lib/python3.8/site-packages/trio/_core/_traps.py:166: in wait_task_rescheduled
    return (await _async_yield(WaitTaskRescheduled(abort_func))).unwrap()
venv/lib/python3.8/site-packages/outcome/_impl.py:138: in unwrap
    raise captured_error
venv/lib/python3.8/site-packages/trio/_threads.py:157: in do_release_then_return_result
    return result.unwrap()
venv/lib/python3.8/site-packages/outcome/_impl.py:138: in unwrap
    raise captured_error
venv/lib/python3.8/site-packages/trio/_threads.py:170: in worker_fn
    ret = sync_fn(*args)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

host = b'not-a-domain.com', port = 443, family = 0, type = <SocketKind.SOCK_STREAM: 1>, proto = 0, flags = 0

    def getaddrinfo(host, port, family=0, type=0, proto=0, flags=0):
        """Resolve host and port into list of address info entries.
    
        Translate the host/port argument into a sequence of 5-tuples that contain
        all the necessary arguments for creating a socket connected to that service.
        host is a domain name, a string representation of an IPv4/v6 address or
        None. port is a string service name such as 'http', a numeric port number or
        None. By passing None as the value of host and port, you can pass NULL to
        the underlying C API.
    
        The family, type and proto arguments can be optionally specified in order to
        narrow the list of addresses returned. Passing zero as a value for each of
        these arguments selects the full range of results.
        """
        # We override this function since we want to translate the numeric family
        # and socket type values to enum constants.
        addrlist = []
>       for res in _socket.getaddrinfo(host, port, family, type, proto, flags):
E       OSError: [Errno 16] Device or resource busy

/usr/lib/python3.8/socket.py:918: OSError
================================================================================================ 1 failed, 1 passed in 0.14s ================================================================================================

Quick fix:

diff --git a/httpcore/backends/trio.py b/httpcore/backends/trio.py
index 1e1f47d..c1d9794 100644
--- a/httpcore/backends/trio.py
+++ b/httpcore/backends/trio.py
@@ -111,6 +111,7 @@ class TrioBackend(AsyncNetworkBackend):
         exc_map = {
             trio.TooSlowError: ConnectTimeout,
             trio.BrokenResourceError: ConnectError,
+            OSError: ConnectError,
         }
         # Trio supports 'local_address' from 0.16.1 onwards.
         # We only include the keyword argument if a local_address
@@ -130,6 +131,7 @@ class TrioBackend(AsyncNetworkBackend):
         exc_map = {
             trio.TooSlowError: ConnectTimeout,
             trio.BrokenResourceError: ConnectError,
+            OSError: ConnectError,
         }
         with map_exceptions(exc_map):
             with trio.fail_after(timeout_or_inf):
(venv) vfazio@vfazio2 /mnt/development/httpcore $ 
(venv) vfazio@vfazio2 /mnt/development/httpcore $ pytest tests/_async/test_connection.py::test_unavilable_resource
==================================================================================================== test session starts ====================================================================================================
platform linux -- Python 3.8.10, pytest-7.0.1, pluggy-1.0.0
rootdir: /mnt/development/httpcore, configfile: setup.cfg
plugins: httpbin-1.0.1, asyncio-0.16.0, anyio-3.5.0, trio-0.7.0
collected 2 items                                                                                                                                                                                                           

tests/_async/test_connection.py ..                                                                                                                                                                                    [100%]

===================================================================================================== 2 passed in 0.05s =====================================================================================================

I've submitted PR #543

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant