Issues with asyncio and Streamlit: Event bound to a different event loop

We have a Python package, which uses asyncio, to connect to our GraphQL API. (It’s a code-gen SDK, using ariadne-codegen.)

Running this locally in browser, via streamlit run app.py. Version 1.33.0. (Will eventually be deployed on Streamlit Cloud when it’s working.)

I’ve been trying various approaches to call the client package, and generally it works with async/await.

But I’m finding an issue where if I submit multiple forms on the same page, the second submit gives the error below.

The most reliable method, so far, is calling my top-level async function with:
loop = asyncio.new_event_loop()
loop.run_until_complete(main())

Just calling with asyncio.run(main()) didn’t work as well.

It reliably happens when I submit one form (such as clear_data_form), and then click the submit button in another form on same page (data_content_form).

Would splitting our one-page app into multiple pages help this situation, so there’s only one form per page?

Any help would be much appreciated!

Error:

RuntimeError: <asyncio.locks.Event object at 0x7f37f4d302e0 [unset]> is bound to a different event loop
Traceback:
File "/home/kirk/.local/lib/python3.10/site-packages/streamlit/runtime/scriptrunner/script_runner.py", line 584, in _run_script
    exec(code, module.__dict__)
File "/mnt/c/projects/Graphlit/graphlit-samples-kirk/python/streamlit-extract-website-topics/app.py", line 90, in <module>
    loop.run_until_complete(main())
File "/usr/lib/python3.10/asyncio/base_events.py", line 649, in run_until_complete
    return future.result()
File "/mnt/c/projects/Graphlit/graphlit-samples-kirk/python/streamlit-extract-website-topics/app.py", line 55, in main
    await feed.handle_feed(uri)
File "/mnt/c/projects/Graphlit/graphlit-samples-kirk/python/streamlit-extract-website-topics/components/feed.py", line 10, in handle_feed
    error_message = await client.create_workflow()
File "/mnt/c/projects/Graphlit/graphlit-samples-kirk/python/streamlit-extract-website-topics/other/client.py", line 64, in create_workflow
    response = await graphlit.client.create_workflow(input)
File "/home/kirk/.local/lib/python3.10/site-packages/graphlit_api/client.py", line 1102, in create_workflow
    response = await self.execute(
File "/home/kirk/.local/lib/python3.10/site-packages/graphlit_api/async_base_client.py", line 114, in execute
    return await self._execute_json(
File "/home/kirk/.local/lib/python3.10/site-packages/graphlit_api/async_base_client.py", line 294, in _execute_json
    return await self.http_client.post(
File "/usr/local/lib/python3.10/dist-packages/httpx/_client.py", line 1892, in post
    return await self.request(
File "/usr/local/lib/python3.10/dist-packages/httpx/_client.py", line 1574, in request
    return await self.send(request, auth=auth, follow_redirects=follow_redirects)
File "/usr/local/lib/python3.10/dist-packages/httpx/_client.py", line 1661, in send
    response = await self._send_handling_auth(
File "/usr/local/lib/python3.10/dist-packages/httpx/_client.py", line 1689, in _send_handling_auth
    response = await self._send_handling_redirects(
File "/usr/local/lib/python3.10/dist-packages/httpx/_client.py", line 1726, in _send_handling_redirects
    response = await self._send_single_request(request)
File "/usr/local/lib/python3.10/dist-packages/httpx/_client.py", line 1763, in _send_single_request
    response = await transport.handle_async_request(request)
File "/usr/local/lib/python3.10/dist-packages/httpx/_transports/default.py", line 373, in handle_async_request
    resp = await self._pool.handle_async_request(req)
File "/usr/local/lib/python3.10/dist-packages/httpcore/_async/connection_pool.py", line 216, in handle_async_request
    raise exc from None
File "/usr/local/lib/python3.10/dist-packages/httpcore/_async/connection_pool.py", line 196, in handle_async_request
    response = await connection.handle_async_request(
File "/usr/local/lib/python3.10/dist-packages/httpcore/_async/connection.py", line 101, in handle_async_request
    return await self._connection.handle_async_request(request)
File "/usr/local/lib/python3.10/dist-packages/httpcore/_async/http11.py", line 143, in handle_async_request
    raise exc
File "/usr/local/lib/python3.10/dist-packages/httpcore/_async/http11.py", line 113, in handle_async_request
    ) = await self._receive_response_headers(**kwargs)
File "/usr/local/lib/python3.10/dist-packages/httpcore/_async/http11.py", line 186, in _receive_response_headers
    event = await self._receive_event(timeout=timeout)
File "/usr/local/lib/python3.10/dist-packages/httpcore/_async/http11.py", line 224, in _receive_event
    data = await self._network_stream.read(
File "/usr/local/lib/python3.10/dist-packages/httpcore/_backends/anyio.py", line 35, in read
    return await self._stream.receive(max_bytes=max_bytes)
File "/usr/local/lib/python3.10/dist-packages/anyio/streams/tls.py", line 205, in receive
    data = await self._call_sslobject_method(self._ssl_object.read, max_bytes)
File "/usr/local/lib/python3.10/dist-packages/anyio/streams/tls.py", line 147, in _call_sslobject_method
    data = await self.transport_stream.receive()
File "/usr/local/lib/python3.10/dist-packages/anyio/_backends/_asyncio.py", line 1133, in receive
    await self._protocol.read_event.wait()
File "/usr/lib/python3.10/asyncio/locks.py", line 211, in wait
    fut = self._get_loop().create_future()
File "/usr/lib/python3.10/asyncio/mixins.py", line 30, in _get_loop
    raise RuntimeError(f'{self!r} is bound to a different event loop')

Here’s the code for my async function, and anywhere it shows ‘await’, it’s calling another async function.

async def main():
    st.image("https://graphlitplatform.blob.core.windows.net/samples/graphlit-logo.svg", width=128)
    st.title("Graphlit Platform")
    st.markdown("Summarize any PDF, DOCX, or PPTX file. Summary generated with the [Anthropic](https://www.anthropic.com) Claude 3 Haiku LLM.")

    if st.session_state['token'] is None:
        st.info("💡 To get started, generate a token in the side panel to connect to your Graphlit project.")
    
    with st.form("data_content_form"):
        selected_pdf = st.selectbox("Select a PDF:", options=list(pdfs.keys()))
        
        document_uri = st.text_input("Or enter your own URL to a file (i.e. PDF, DOCX, PPTX):", key='pdf_uri')

        uri = document_uri if document_uri else pdfs[selected_pdf]

        submit_content = st.form_submit_button("Submit")

        if submit_content and uri and st.session_state['token']:
            await upload.handle_upload(uri)

    with st.form("summarize_data_form"):
        if st.session_state['content_done'] == True:
            await summarize.handle_summarize()

    with st.form("clear_data_form"):
        st.markdown("If you run into any problems, or exceeded your Free Tier project quota, you can delete all your contents to start over.  Be aware, this deletes *all* the contents in your project.")

        submit_reset = st.form_submit_button("Reset project")

        if submit_reset:
            if st.session_state['token']:
                with st.spinner('Deleting contents... Please wait.'):
                    await client.delete_all_contents()

    sidebar.create_sidebar("""
                ### Demo Instructions
                - [Sign up for Graphlit](https://docs.graphlit.dev/getting-started/signup) 🆓  
                - **Step 1:** Generate Graphlit project token.
                - **Step 2:** Select a PDF, or fill in your own document URL.
                - **Step 3:** Click to generate summary of file using [Anthropic](https://www.anthropic.com) Claude 3 Haiku LLM.     
                """)
        
if __name__ == "__main__":
    loop = asyncio.new_event_loop()
    loop.run_until_complete(main())

Some more context, I’ve tried splitting our app into a multipage app, and can still get this to happen. Each page has a single async function it runs, and I’ve tried both asyncio.run and creating a new event loop each time. Neither is reliable, and I’ve still seen this ‘bound to a different event loop’ error.

Looks like I figured it out. Somewhat of a hack, but it works more reliably now.

Need to run the async functions with this:

It’ll catch the weird event loop error and then rerun it, and avoids the problem.

import asyncio

def run_async_task(async_func, *args):
    """
    Run an asynchronous function in a new event loop.

    Args:
    async_func (coroutine): The asynchronous function to execute.
    *args: Arguments to pass to the asynchronous function.

    Returns:
    None
    """
    
    loop = None

    try:
        loop = asyncio.new_event_loop()
        loop.run_until_complete(async_func(*args))
    except:
        # Close the existing loop if open
        if loop is not None:
            loop.close()

        # Create a new loop for retry
        loop = asyncio.new_event_loop()

        loop.run_until_complete(async_func(*args))
    finally:
        if loop is not None:
            loop.close()

Glad that last fix worked, Kirk!

I also wanted to say that I love graphlit.com! :hugs:

Best,
Charly

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.