Skip to content

AttributeError 'NoneType' object has no attribute 'fileno'. #239

Description

@povilasb

We somewhat discussed the issue at https://gitter.im/encode/community .

TL;DR when connection is closed/reset httpcore assumes socket is still available for transport and crashes with

AttributeError 'NoneType' object has no attribute 'fileno'.

Environment

Docker image: python:3.8.6-buster
Python: 3.8.6
httpcore: 0.12.2

Details

    def is_readable(self) -> bool:
        transport = self.stream_reader._transport  # type: ignore
        sock: socket.socket = transport.get_extra_info("socket")
        return is_socket_readable(sock.fileno())
  • If transport.get_extra_info("socket") returns None, sock.fileno() crashes.
  • First they type of sock should probably be Optional[socket.socket].
  • The python docs are not very detailed about the guarantees of extra["socket"] existence but in the example code they do test it: https://docs.python.org/3/library/asyncio-protocol.html#asyncio.BaseTransport.get_extra_info
  • So as other code e.g. in uvicorn or aiohttp
  • I wish I could provide a working example, but this only occasionally fails in my system.
  • However I do have a stacktrace capture:
AttributeError 'NoneType' object has no attribute 'fileno'.

  File "httpx/_client.py", line 1371, in request
    response = await self.send(
  File "httpx/_client.py", line 1406, in send
    response = await self._send_handling_auth(
  File "httpx/_client.py", line 1444, in _send_handling_auth
    response = await self._send_handling_redirects(
  File "httpx/_client.py", line 1476, in _send_handling_redirects
    response = await self._send_single_request(request, timeout)
  File "httpx/_client.py", line 1502, in _send_single_request
    (status_code, headers, stream, ext,) = await transport.arequest(
  File "httpcore/_async/http_proxy.py", line 124, in arequest
    return await self._tunnel_request(
  File "httpcore/_async/http_proxy.py", line 190, in _tunnel_request
    connection = await self._get_connection_from_pool(origin)
  File "httpcore/_async/connection_pool.py", line 248, in _get_connection_from_pool
    if connection.is_socket_readable():
  File "httpcore/_async/connection.py", line 169, in is_socket_readable
    return self.connection is not None and self.connection.is_socket_readable()
  File "httpcore/_async/http11.py", line 205, in is_socket_readable
    return self.socket.is_readable()
  File "httpcore/_backends/asyncio.py", line 179, in is_readable
    return is_socket_readable(sock.fileno())
  • Was going through asyncio implementation trying to locate where the extra["socket"] gets cleared, but couldn't find anything related 🤔 For reference it is set here

Anyway, even though I do not fully understand asyncio transports, based on other examples I guess it is pretty reasonable to test if get_exra_info("socket") is not None?

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinggood first issueGood for newcomers

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions