Skip to content

An open stream object is being garbage collected; call "stream.close()" explicitly #714

Closed

Description

@euri10

I can trigger an ERROR: An open stream object is being garbage collected; call "stream.close()" explicitly. on demand in my test suite

the trace output is just below:

DEBUG:conftest:Starting ngrok tunnel for port 5002

-------------------------------- live log setup --------------------------------
DEBUG    conftest:conftest.py:46 Starting ngrok tunnel for port 5002
DEBUG:asyncio:Using selector: EpollSelector
DEBUG    asyncio:selector_events.py:54 Using selector: EpollSelector
INFO:     Started server process [3098472]
INFO:     Uvicorn running on http://127.0.0.1:5002 (Press CTRL+C to quit)
INFO:     Waiting for application startup.
INFO:     Connected to database postgresql://postgres:********@localhost:5438/rss2inbox_test
INFO:     Application startup complete.
TRACE [2020-01-02 12:02:01] httpx.dispatch.connection_pool - acquire_connection origin=Origin(scheme='https' host='websub.rocks' port=443)
TRACE:    acquire_connection origin=Origin(scheme='https' host='websub.rocks' port=443)
TRACE [2020-01-02 12:02:01] httpx.dispatch.connection_pool - new_connection connection=HTTPConnection(origin=Origin(scheme='https' host='websub.rocks' port=443))
TRACE:    new_connection connection=HTTPConnection(origin=Origin(scheme='https' host='websub.rocks' port=443))
TRACE [2020-01-02 12:02:01] httpx.config - load_ssl_context verify=True cert=None trust_env=True http2=False
TRACE:    load_ssl_context verify=True cert=None trust_env=True http2=False
TRACE [2020-01-02 12:02:01] httpx.config - load_verify_locations cafile=/home/lotso/.local/share/virtualenvs/rss2inbox-IweRxOhH/lib/python3.8/site-packages/certifi/cacert.pem
TRACE:    load_verify_locations cafile=/home/lotso/.local/share/virtualenvs/rss2inbox-IweRxOhH/lib/python3.8/site-packages/certifi/cacert.pem
TRACE [2020-01-02 12:02:01] httpx.dispatch.connection - start_connect tcp host='websub.rocks' port=443 timeout=Timeout(timeout=5.0)
TRACE:    start_connect tcp host='websub.rocks' port=443 timeout=Timeout(timeout=5.0)
TRACE [2020-01-02 12:02:01] httpx.dispatch.connection - connected http_version='HTTP/1.1'
TRACE:    connected http_version='HTTP/1.1'
TRACE [2020-01-02 12:02:01] httpx.dispatch.http11 - send_headers method='GET' target='/subscriber/100' headers=Headers({'host': 'websub.rocks', 'user-agent': 'python-httpx/0.10.0', 'accept': '*/*', 'accept-encoding': 'gzip, deflate', 'connection': 'keep-alive'})
TRACE:    send_headers method='GET' target='/subscriber/100' headers=Headers({'host': 'websub.rocks', 'user-agent': 'python-httpx/0.10.0', 'accept': '*/*', 'accept-encoding': 'gzip, deflate', 'connection': 'keep-alive'})
TRACE [2020-01-02 12:02:01] httpx.dispatch.http11 - send_data data=Data(<0 bytes>)
TRACE:    send_data data=Data(<0 bytes>)
TRACE [2020-01-02 12:02:01] httpx.dispatch.http11 - receive_event event=NEED_DATA
TRACE:    receive_event event=NEED_DATA
TRACE [2020-01-02 12:02:02] httpx.dispatch.http11 - receive_event event=Response(status_code=200, headers=[(b'server', b'nginx/1.14.2'), (b'date', b'Thu, 02 Jan 2020 11:02:02 GMT'), (b'content-type', b'text/html; charset=UTF-8'), (b'content-length', b'4912'), (b'connection', b'keep-alive')], http_version=b'1.1', reason=b'OK')
TRACE:    receive_event event=Response(status_code=200, headers=[(b'server', b'nginx/1.14.2'), (b'date', b'Thu, 02 Jan 2020 11:02:02 GMT'), (b'content-type', b'text/html; charset=UTF-8'), (b'content-length', b'4912'), (b'connection', b'keep-alive')], http_version=b'1.1', reason=b'OK')
DEBUG [2020-01-02 12:02:02] httpx.client - HTTP Request: GET https://websub.rocks/subscriber/100 "HTTP/1.1 200 OK"
DEBUG:    HTTP Request: GET https://websub.rocks/subscriber/100 "HTTP/1.1 200 OK"
TRACE [2020-01-02 12:02:02] httpx.dispatch.http11 - receive_event event=Data(<3932 bytes>)
TRACE:    receive_event event=Data(<3932 bytes>)
TRACE [2020-01-02 12:02:02] httpx.dispatch.http11 - receive_event event=NEED_DATA
TRACE:    receive_event event=NEED_DATA
TRACE [2020-01-02 12:02:02] httpx.dispatch.http11 - receive_event event=Data(<980 bytes>)
TRACE:    receive_event event=Data(<980 bytes>)
TRACE [2020-01-02 12:02:02] httpx.dispatch.http11 - receive_event event=EndOfMessage(headers=[])
TRACE:    receive_event event=EndOfMessage(headers=[])
TRACE [2020-01-02 12:02:02] httpx.dispatch.http11 - response_closed our_state=DONE their_state=DONE
TRACE:    response_closed our_state=DONE their_state=DONE
TRACE [2020-01-02 12:02:02] httpx.dispatch.connection_pool - release_connection connection=HTTPConnection(origin=Origin(scheme='https' host='websub.rocks' port=443))
TRACE:    release_connection connection=HTTPConnection(origin=Origin(scheme='https' host='websub.rocks' port=443))
TRACE [2020-01-02 12:02:02] httpx.dispatch.connection - close_connection
TRACE:    close_connection
TRACE [2020-01-02 12:02:02] httpx.dispatch.http11 - send_event event=ConnectionClosed()
TRACE:    send_event event=ConnectionClosed()
TRACE [2020-01-02 12:02:02] httpx.dispatch.connection_pool - acquire_connection origin=Origin(scheme='https' host='75d55904.ngrok.io' port=443)
TRACE:    acquire_connection origin=Origin(scheme='https' host='75d55904.ngrok.io' port=443)
TRACE [2020-01-02 12:02:02] httpx.dispatch.connection_pool - new_connection connection=HTTPConnection(origin=Origin(scheme='https' host='75d55904.ngrok.io' port=443))
TRACE:    new_connection connection=HTTPConnection(origin=Origin(scheme='https' host='75d55904.ngrok.io' port=443))
TRACE [2020-01-02 12:02:02] httpx.config - load_ssl_context verify=True cert=None trust_env=True http2=False
TRACE:    load_ssl_context verify=True cert=None trust_env=True http2=False
TRACE [2020-01-02 12:02:02] httpx.config - load_verify_locations cafile=/home/lotso/.local/share/virtualenvs/rss2inbox-IweRxOhH/lib/python3.8/site-packages/certifi/cacert.pem
TRACE:    load_verify_locations cafile=/home/lotso/.local/share/virtualenvs/rss2inbox-IweRxOhH/lib/python3.8/site-packages/certifi/cacert.pem
TRACE [2020-01-02 12:02:02] httpx.dispatch.connection - start_connect tcp host='75d55904.ngrok.io' port=443 timeout=Timeout(timeout=5.0)
TRACE:    start_connect tcp host='75d55904.ngrok.io' port=443 timeout=Timeout(timeout=5.0)
TRACE [2020-01-02 12:02:02] httpx.dispatch.connection - connected http_version='HTTP/1.1'
TRACE:    connected http_version='HTTP/1.1'
TRACE [2020-01-02 12:02:02] httpx.dispatch.http11 - send_headers method='GET' target='/subscribe?subscribe_to_url=https%3A%2F%2Fwebsub.rocks%2Fblog%2F100%2FWBj716u7rareKpjbfoh9' headers=Headers({'host': '75d55904.ngrok.io', 'user-agent': 'python-httpx/0.10.0', 'accept': '*/*', 'accept-encoding': 'gzip, deflate', 'connection': 'keep-alive'})
TRACE:    send_headers method='GET' target='/subscribe?subscribe_to_url=https%3A%2F%2Fwebsub.rocks%2Fblog%2F100%2FWBj716u7rareKpjbfoh9' headers=Headers({'host': '75d55904.ngrok.io', 'user-agent': 'python-httpx/0.10.0', 'accept': '*/*', 'accept-encoding': 'gzip, deflate', 'connection': 'keep-alive'})
TRACE [2020-01-02 12:02:02] httpx.dispatch.http11 - send_data data=Data(<0 bytes>)
TRACE:    send_data data=Data(<0 bytes>)
TRACE [2020-01-02 12:02:02] httpx.dispatch.http11 - receive_event event=NEED_DATA
TRACE:    receive_event event=NEED_DATA
TRACE [2020-01-02 12:02:02] httpx.dispatch.connection_pool - acquire_connection origin=Origin(scheme='https' host='websub.rocks' port=443)
TRACE:    acquire_connection origin=Origin(scheme='https' host='websub.rocks' port=443)
TRACE [2020-01-02 12:02:02] httpx.dispatch.connection_pool - new_connection connection=HTTPConnection(origin=Origin(scheme='https' host='websub.rocks' port=443))
TRACE:    new_connection connection=HTTPConnection(origin=Origin(scheme='https' host='websub.rocks' port=443))
TRACE [2020-01-02 12:02:02] httpx.config - load_ssl_context verify=True cert=None trust_env=True http2=False
TRACE:    load_ssl_context verify=True cert=None trust_env=True http2=False
TRACE [2020-01-02 12:02:02] httpx.config - load_verify_locations cafile=/home/lotso/.local/share/virtualenvs/rss2inbox-IweRxOhH/lib/python3.8/site-packages/certifi/cacert.pem
TRACE:    load_verify_locations cafile=/home/lotso/.local/share/virtualenvs/rss2inbox-IweRxOhH/lib/python3.8/site-packages/certifi/cacert.pem
TRACE [2020-01-02 12:02:02] httpx.dispatch.connection - start_connect tcp host='websub.rocks' port=443 timeout=Timeout(timeout=5.0)
TRACE:    start_connect tcp host='websub.rocks' port=443 timeout=Timeout(timeout=5.0)
TRACE [2020-01-02 12:02:03] httpx.dispatch.connection - connected http_version='HTTP/1.1'
TRACE:    connected http_version='HTTP/1.1'
TRACE [2020-01-02 12:02:03] httpx.dispatch.http11 - send_headers method='GET' target='/blog/100/WBj716u7rareKpjbfoh9' headers=Headers({'host': 'websub.rocks', 'user-agent': 'python-httpx/0.10.0', 'accept': '*/*', 'accept-encoding': 'gzip, deflate', 'connection': 'keep-alive'})
TRACE:    send_headers method='GET' target='/blog/100/WBj716u7rareKpjbfoh9' headers=Headers({'host': 'websub.rocks', 'user-agent': 'python-httpx/0.10.0', 'accept': '*/*', 'accept-encoding': 'gzip, deflate', 'connection': 'keep-alive'})
TRACE [2020-01-02 12:02:03] httpx.dispatch.http11 - send_data data=Data(<0 bytes>)
TRACE:    send_data data=Data(<0 bytes>)
TRACE [2020-01-02 12:02:03] httpx.dispatch.http11 - receive_event event=NEED_DATA
TRACE:    receive_event event=NEED_DATA
TRACE [2020-01-02 12:02:03] httpx.dispatch.http11 - receive_event event=Response(status_code=200, headers=[(b'server', b'nginx/1.14.2'), (b'date', b'Thu, 02 Jan 2020 11:02:03 GMT'), (b'content-type', b'text/html; charset=UTF-8'), (b'content-length', b'2238'), (b'connection', b'keep-alive'), (b'link', b'<https://websub.rocks/blog/100/WBj716u7rareKpjbfoh9>; rel="self"'), (b'link', b'<https://websub.rocks/blog/100/WBj716u7rareKpjbfoh9/hub>; rel="hub"')], http_version=b'1.1', reason=b'OK')
TRACE:    receive_event event=Response(status_code=200, headers=[(b'server', b'nginx/1.14.2'), (b'date', b'Thu, 02 Jan 2020 11:02:03 GMT'), (b'content-type', b'text/html; charset=UTF-8'), (b'content-length', b'2238'), (b'connection', b'keep-alive'), (b'link', b'<https://websub.rocks/blog/100/WBj716u7rareKpjbfoh9>; rel="self"'), (b'link', b'<https://websub.rocks/blog/100/WBj716u7rareKpjbfoh9/hub>; rel="hub"')], http_version=b'1.1', reason=b'OK')
DEBUG [2020-01-02 12:02:03] httpx.client - HTTP Request: GET https://websub.rocks/blog/100/WBj716u7rareKpjbfoh9 "HTTP/1.1 200 OK"
DEBUG:    HTTP Request: GET https://websub.rocks/blog/100/WBj716u7rareKpjbfoh9 "HTTP/1.1 200 OK"
TRACE [2020-01-02 12:02:03] httpx.dispatch.http11 - receive_event event=Data(<2238 bytes>)
TRACE:    receive_event event=Data(<2238 bytes>)
TRACE [2020-01-02 12:02:03] httpx.dispatch.http11 - receive_event event=EndOfMessage(headers=[])
TRACE:    receive_event event=EndOfMessage(headers=[])
TRACE [2020-01-02 12:02:03] httpx.dispatch.http11 - response_closed our_state=DONE their_state=DONE
TRACE:    response_closed our_state=DONE their_state=DONE
TRACE [2020-01-02 12:02:03] httpx.dispatch.connection_pool - release_connection connection=HTTPConnection(origin=Origin(scheme='https' host='websub.rocks' port=443))
TRACE:    release_connection connection=HTTPConnection(origin=Origin(scheme='https' host='websub.rocks' port=443))
TRACE [2020-01-02 12:02:03] httpx.dispatch.connection - close_connection
TRACE:    close_connection
TRACE [2020-01-02 12:02:03] httpx.dispatch.http11 - send_event event=ConnectionClosed()
TRACE:    send_event event=ConnectionClosed()
DEBUG:    hub_url='https://websub.rocks/blog/100/WBj716u7rareKpjbfoh9/hub' topic_url='https://websub.rocks/blog/100/WBj716u7rareKpjbfoh9'
ERROR:    An open stream object is being garbage collected; call "stream.close()" explicitly.
TRACE [2020-01-02 12:02:03] httpx.dispatch.connection_pool - acquire_connection origin=Origin(scheme='https' host='websub.rocks' port=443)
TRACE:    acquire_connection origin=Origin(scheme='https' host='websub.rocks' port=443)
TRACE [2020-01-02 12:02:03] httpx.dispatch.connection_pool - new_connection connection=HTTPConnection(origin=Origin(scheme='https' host='websub.rocks' port=443))
TRACE:    new_connection connection=HTTPConnection(origin=Origin(scheme='https' host='websub.rocks' port=443))
TRACE [2020-01-02 12:02:03] httpx.config - load_ssl_context verify=True cert=None trust_env=True http2=False
TRACE:    load_ssl_context verify=True cert=None trust_env=True http2=False
TRACE [2020-01-02 12:02:03] httpx.config - load_verify_locations cafile=/home/lotso/.local/share/virtualenvs/rss2inbox-IweRxOhH/lib/python3.8/site-packages/certifi/cacert.pem
TRACE:    load_verify_locations cafile=/home/lotso/.local/share/virtualenvs/rss2inbox-IweRxOhH/lib/python3.8/site-packages/certifi/cacert.pem
TRACE [2020-01-02 12:02:03] httpx.dispatch.connection - start_connect tcp host='websub.rocks' port=443 timeout=Timeout(timeout=5.0)
TRACE:    start_connect tcp host='websub.rocks' port=443 timeout=Timeout(timeout=5.0)
TRACE [2020-01-02 12:02:03] httpx.dispatch.connection - connected http_version='HTTP/1.1'
TRACE:    connected http_version='HTTP/1.1'
TRACE [2020-01-02 12:02:03] httpx.dispatch.http11 - send_headers method='POST' target='/blog/100/WBj716u7rareKpjbfoh9/hub' headers=Headers({'host': 'websub.rocks', 'user-agent': 'python-httpx/0.10.0', 'accept': '*/*', 'accept-encoding': 'gzip, deflate', 'connection': 'keep-alive', 'content-length': '235', 'content-type': 'application/x-www-form-urlencoded'})
TRACE:    send_headers method='POST' target='/blog/100/WBj716u7rareKpjbfoh9/hub' headers=Headers({'host': 'websub.rocks', 'user-agent': 'python-httpx/0.10.0', 'accept': '*/*', 'accept-encoding': 'gzip, deflate', 'connection': 'keep-alive', 'content-length': '235', 'content-type': 'application/x-www-form-urlencoded'})
TRACE [2020-01-02 12:02:03] httpx.dispatch.http11 - send_data data=Data(<235 bytes>)
TRACE:    send_data data=Data(<235 bytes>)
TRACE [2020-01-02 12:02:03] httpx.dispatch.http11 - receive_event event=NEED_DATA
TRACE:    receive_event event=NEED_DATA
INFO:     2600:3c01::f03c:91ff:fe94:80b2:0 - "GET /callbacks/6faf64d5-ab53-403e-8349-1a6889ac2ffb?hub.mode=subscribe&hub.topic=https%3A%2F%2Fwebsub.rocks%2Fblog%2F100%2FWBj716u7rareKpjbfoh9&hub.challenge=O9M7grqDIEoZTBkld6bX&hub.lease_seconds=86400 HTTP/1.1" 200 OK
TRACE [2020-01-02 12:02:04] httpx.dispatch.http11 - receive_event event=Response(status_code=202, headers=[(b'server', b'nginx/1.14.2'), (b'date', b'Thu, 02 Jan 2020 11:02:04 GMT'), (b'content-type', b'text/html; charset=UTF-8'), (b'content-length', b'0'), (b'connection', b'keep-alive')], http_version=b'1.1', reason=b'Accepted')
TRACE:    receive_event event=Response(status_code=202, headers=[(b'server', b'nginx/1.14.2'), (b'date', b'Thu, 02 Jan 2020 11:02:04 GMT'), (b'content-type', b'text/html; charset=UTF-8'), (b'content-length', b'0'), (b'connection', b'keep-alive')], http_version=b'1.1', reason=b'Accepted')
DEBUG [2020-01-02 12:02:04] httpx.client - HTTP Request: POST https://websub.rocks/blog/100/WBj716u7rareKpjbfoh9/hub "HTTP/1.1 202 Accepted"
DEBUG:    HTTP Request: POST https://websub.rocks/blog/100/WBj716u7rareKpjbfoh9/hub "HTTP/1.1 202 Accepted"
TRACE [2020-01-02 12:02:04] httpx.dispatch.http11 - receive_event event=EndOfMessage(headers=[])
TRACE:    receive_event event=EndOfMessage(headers=[])
TRACE [2020-01-02 12:02:04] httpx.dispatch.http11 - response_closed our_state=DONE their_state=DONE
TRACE:    response_closed our_state=DONE their_state=DONE
TRACE [2020-01-02 12:02:04] httpx.dispatch.connection_pool - release_connection connection=HTTPConnection(origin=Origin(scheme='https' host='websub.rocks' port=443))
TRACE:    release_connection connection=HTTPConnection(origin=Origin(scheme='https' host='websub.rocks' port=443))
TRACE [2020-01-02 12:02:04] httpx.dispatch.connection - close_connection
TRACE:    close_connection
TRACE [2020-01-02 12:02:04] httpx.dispatch.http11 - send_event event=ConnectionClosed()
TRACE:    send_event event=ConnectionClosed()
INFO:     93.31.210.155:0 - "GET /subscribe?subscribe_to_url=https%3A%2F%2Fwebsub.rocks%2Fblog%2F100%2FWBj716u7rareKpjbfoh9 HTTP/1.1" 200 OK
TRACE [2020-01-02 12:02:04] httpx.dispatch.http11 - receive_event event=Response(status_code=200, headers=[(b'date', b'Thu, 02 Jan 2020 11:02:02 GMT'), (b'server', b'uvicorn'), (b'content-length', b'38'), (b'content-type', b'application/json')], http_version=b'1.1', reason=b'OK')
TRACE:    receive_event event=Response(status_code=200, headers=[(b'date', b'Thu, 02 Jan 2020 11:02:02 GMT'), (b'server', b'uvicorn'), (b'content-length', b'38'), (b'content-type', b'application/json')], http_version=b'1.1', reason=b'OK')
DEBUG [2020-01-02 12:02:04] httpx.client - HTTP Request: GET https://75d55904.ngrok.io/subscribe?subscribe_to_url=https%3A%2F%2Fwebsub.rocks%2Fblog%2F100%2FWBj716u7rareKpjbfoh9 "HTTP/1.1 200 OK"
DEBUG:    HTTP Request: GET https://75d55904.ngrok.io/subscribe?subscribe_to_url=https%3A%2F%2Fwebsub.rocks%2Fblog%2F100%2FWBj716u7rareKpjbfoh9 "HTTP/1.1 200 OK"
TRACE [2020-01-02 12:02:04] httpx.dispatch.http11 - receive_event event=Data(<38 bytes>)
TRACE:    receive_event event=Data(<38 bytes>)
TRACE [2020-01-02 12:02:04] httpx.dispatch.http11 - receive_event event=EndOfMessage(headers=[])
TRACE:    receive_event event=EndOfMessage(headers=[])
TRACE [2020-01-02 12:02:04] httpx.dispatch.http11 - response_closed our_state=DONE their_state=DONE
TRACE:    response_closed our_state=DONE their_state=DONE
TRACE [2020-01-02 12:02:04] httpx.dispatch.connection_pool - release_connection connection=HTTPConnection(origin=Origin(scheme='https' host='75d55904.ngrok.io' port=443))
TRACE:    release_connection connection=HTTPConnection(origin=Origin(scheme='https' host='75d55904.ngrok.io' port=443))
TRACE [2020-01-02 12:02:04] httpx.dispatch.connection - close_connection
TRACE:    close_connection
TRACE [2020-01-02 12:02:04] httpx.dispatch.http11 - send_event event=ConnectionClosed()
TRACE:    send_event event=ConnectionClosed()
6faf64d5-ab53-403e-8349-1a6889ac2ffb
PASSED

the test that triggers it is below async def test100, I tried to copy most relevant code, I';m sorry it's that long....:

def get_url_from_page(content):
    htmlcontent = html.document_fromstring(content)
    link = htmlcontent.xpath("//input/@value")
    return link[0]


urls_100 = [
    "https://websub.rocks/subscriber/100",
    "https://websub.rocks/subscriber/101",
    "https://websub.rocks/subscriber/102",
    "https://websub.rocks/subscriber/103",
    "https://websub.rocks/subscriber/104",
]


@pytest.mark.asyncio
@pytest.mark.parametrize("websub_test_url", urls_100)
async def test100(websub_test_url, subscriber_server, ngrokurl_subscriber):
    response = await httpx.get(websub_test_url)
    subscribe_to_url = get_url_from_page(response.content)
    url = subscriber_app.url_path_for("subscribe")
    response = await httpx.get(
        ngrokurl_subscriber + url, params={"subscribe_to_url": subscribe_to_url}
    )
    print(response.json())
    assert response.status_code == 200

@pytest.fixture()
async def subscriber_server():
    config = Config(app=subscriber_app, port=5002)
    server = Server(config=config)
    task = asyncio.ensure_future(server.serve())
    try:
        while not server.started:
            await asyncio.sleep(0.0001)
        yield server
    finally:
        logger.debug("END SERVER")
        server.should_exit = True
        await task

class NgrokTunnel:
    def __init__(self, port: str):
        assert find_executable(
            "ngrok"
        ), "ngrok command must be installed, see https://ngrok.com/"
        self.port = port

    def start(self, ngrok_die_check_delay=0.5):
        logger.debug(f"Starting ngrok tunnel for port {self.port}")
        pattern = r"^.*addr=(?P<addr>http://localhost:(?P<port>\d+)) url=(?P<ngrokurl>https://(?P<ngroksubdomain>.*)\.ngrok\.io)$"

        self.ngrok = subprocess.Popen(
            [
                "ngrok",
                "http",
                self.port,
                "--log",
                "stdout",
            ],
            stdout=subprocess.PIPE,
            bufsize=1,
            universal_newlines=True,
        )
        found = False
        ngrokurl = None
        while not found:
            for line in self.ngrok.stdout:
                # logger.debug(line, end="")
                m = re.match(pattern, line)
                if m is not None:
                    # addr = m.group("addr")
                    # port = m.group("port")
                    ngrokurl = m.group("ngrokurl")
                    # ngroksubdomain = m.group("ngroksubdomain")
                    found = True
                    break
        # See that we don't instantly die
        time.sleep(ngrok_die_check_delay)
        assert self.ngrok.poll() is None, "ngrok terminated abrutly"
        return ngrokurl

    def stop(self):
        """Tell ngrok to tear down the tunnel.

        Stop the background tunneling process.
        """
        self.ngrok.terminate()


@pytest.fixture(scope="session", autouse=True)
def ngrokurl_subscriber():
    ngrok_subscriber = NgrokTunnel("5002")
    url = ngrok_subscriber.start()
    yield url
    ngrok_subscriber.stop()

Activity

  1. lovelydinosaur commented on Jan 2, 2020

    @lovelydinosaur
    Member

    Any idea if this replicates with trio as well, or if it is asyncio specfic?
    Could really do with reducing this down to a more replicable example. 😃

  2. florimondmanca commented on Jan 2, 2020

    @florimondmanca
    Contributor

    @tomchristie I believe this is an asyncio-specific error — really just a StreamReader or StreamWriter not being closed before getting GC’d.

    Agreed that the reproduction example could benefit from a drastic cut down... Probably you can setup a minimal test server with Uvicorn, and then work out what’s the minimal script that allows to reproduce the issue? Eg this might be: create a client without context manager syntax, make a request, and skip closing the client.

  3. florimondmanca commented on Jan 2, 2020

    @florimondmanca
    Contributor

    Eg this might be: create a client without context manager syntax, make a request, and skip closing the client.

    Of course this won’t be the reason of this bug, because your code is only using the high-level API. But there might be an issue with redirects, start-TLS, proxies or other places when we might be switching an asyncio stream for another one... At this point it depends on your exact setup, which you can try to replicate in a slimmed down locally maybe?

  4. euri10 commented on Jan 2, 2020

    @euri10
    ContributorAuthor

    I'd be happy to slim it down, but I'm not sure how I could: I'm implementing a websub subscriber with FastAPI, it works fine, and the error I describe is just in my tests so maybe it's the way I'm testing things, or the way httpx calls may be intereleaved.

    Maybe after you get the desrciption of the app and a picture of what it does you may help me slimming down the part that triggers the error.

    1. the test spins a uvicorn server, it serves a FastAPI app, that app has 2 routes /subscribe and /callback
    2. it spins also a ngrok instance (ngrokurl_subscriber fixture) that reverse proxies the localhost uvicorn server to a https://xxxxx.ngrok.io domain (which I'll need after in the FastAPI app to get /callback hit externally)
    3. the test sends a 1st httpx.get to https://websub.rocks/subscriber/100 that is parsed to reveal the "real" address to hit, https://websub.rocks/blog/100/WBj716u7rareKpjbfoh9
    4. Once we got the real adress another httpx.get to the /subscribe route of that FastAPI app is made, with a real address parameter https://websub.rocks/blog/100/XXX
    5. within the /subscribe route there is a :
      a. httpx.get to the https://websub.rocks/blog/100/XXX above, it reveals another url, the hub_url
      b. httpx.post call to the hub_url, it should return a 202 and if it's the case the /subscribe route returns 200
    6. in that case the hub_url will try to hit the /callback route

    1-3 is really test preparation
    4-6 is well represented in the image below, that's the 1st 3 horizontal arrows from the top

    subscriber

  5. euri10 commented on Jan 2, 2020

    @euri10
    ContributorAuthor

    note that the error happens between 5a and 5b

  6. added
    concurrencyIssues related to concurrency and usage of async libraries
    on Jan 8, 2020
  7. lovelydinosaur commented on Jan 15, 2020

    @lovelydinosaur
    Member

    I happened to be able to replicate this when testing an ASGI proxy... https://gist.github.com/tomchristie/5765e10a90a41c7e57470e2dc700f9db

    Turns out it only replicates on Python 3.8.0. Does not replicate on 3.7.1 or 3.8.1.

    It's a Python bug - here: https://bugs.python.org/issue38529

    I think we can assume that we wouldn't be seeing this issue if we were using .wait_closed() on stream closing, but we can't because there's a different asyncio Python bug there. #634

    We probably ought to file an asyncio issue based on the content in #634.

    In the meantime - upgrade to 3.8.1. At least our exposure here is only on 3.8.0.

  8. lovelydinosaur commented on Jan 15, 2020

    @lovelydinosaur
    Member

    Options here:

    • Do nothing. Close this issue. Say "It's a Python bug".
    • Issue a warning when using AsyncClient under asyncio on 3.8.0.
    • Monkey patch the problem when using AsyncClient under asyncio on 3.8.0.
  9. florimondmanca commented on Jan 15, 2020

    @florimondmanca
    Contributor

    Well, if 3.8.0 is broken on this, then I guess there's not much we can do? We can't even catch this since it's issued by asyncio on interpreter exit… So… "It's a Python bug" + "See this issue for reference"? 🤷‍♂

  10. lovelydinosaur commented on Jan 30, 2020

    @lovelydinosaur
    Member

    Going to close this off for now as it's a Python bug, not us.

    If we find we're getting the same issue re-raised, then we could consider if we want to add some more explicit warnings under 3.8.0 + asyncio.

  11. added
    concurrency/asyncioIssues related to concurrency (asyncio-only)
    externalRoot cause pending resolution in an external dependency
    and removed
    concurrencyIssues related to concurrency and usage of async libraries
    on Jul 30, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    concurrency/asyncioIssues related to concurrency (asyncio-only)externalRoot cause pending resolution in an external dependency

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions