sysid/sse-starlette

generator stays active after calling EventSource.close() and closing the browser

Closed this issue · 2 comments

Hi, I think I am misunderstanding something or doing something wrong, but I am completely stuck on using this package due to event generators never stopping when the connection originates from an EventSource creation in the browser. I've tried my best to capture what I'm experiencing below. I really appreciate any help or guidance.

I have a very simple test setup using basic JavaScript creating an EventSource and a setTimeout that closes it 5 seconds later. I have a simple event generator that is sending a message every second.

When I test this with cURL, everything works as expected.

When I use the browser, the generator continues to run even after calling close() and closing the browser completely.

Here is the stream handler in Python

    def new_messages():
        # Add logic here to check for new messages
        yield "Hello World"

    async def event_generator():
        while True:
            print("stream")
            # If client closes connection, stop sending events
            disconnected = await request.is_disconnected()
            if disconnected:
                break

            # Checks for new messages and return them to client if any
            if new_messages():
                yield {
                    "event": "new_message",
                    "id": "message_id",
                    "retry": RETRY_TIMEOUT,
                    "data": "message_content",
                }

            await asyncio.sleep(STREAM_DELAY)

    return EventSourceResponse(event_generator())

Here is the JavaScript

        console.log("Creating eventSource");
        this.eventSource = new EventSource(endpoint);

        this.eventSource.addEventListener(
          "message",
          (ev): any => {
            console.log(ev);
          }
        );

        setTimeout(() => {
          console.log("Calling eventSource.close()");
          this.eventSource.close();
        }, 5000);

Using cURL I can call this endpoint and when I Ctrl-C, I see the disconnect as expected.
curl-works

Using a very simple JavaScript client in Chrome, after calling close OR closing the browser completely, the generator task stays active.
browser-keeps-running

After Ctrl-C on the uvicorn server, you can see all the tasks shutting down.

^CINFO:     Shutting down
INFO:     Waiting for connections to close. (CTRL+C to force quit)
cancelled
TRACE:    127.0.0.1:37810 - ASGI [5] Send {'type': 'http.response.body', 'body': '<0 bytes>', 'more_body': False}
TRACE:    127.0.0.1:37810 - HTTP connection lost
TRACE:    127.0.0.1:37810 - ASGI [5] Completed
cancelled
TRACE:    127.0.0.1:37804 - ASGI [3] Send {'type': 'http.response.body', 'body': '<0 bytes>', 'more_body': False}
TRACE:    127.0.0.1:37804 - HTTP connection lost
TRACE:    127.0.0.1:37804 - ASGI [3] Completed
cancelled
TRACE:    127.0.0.1:37860 - ASGI [7] Send {'type': 'http.response.body', 'body': '<0 bytes>', 'more_body': False}
TRACE:    127.0.0.1:37860 - HTTP connection lost
TRACE:    127.0.0.1:37860 - ASGI [7] Completed
INFO:     Waiting for application shutdown.
TRACE:    ASGI [1] Receive {'type': 'lifespan.shutdown'}
TRACE:    ASGI [1] Send {'type': 'lifespan.shutdown.complete'}
TRACE:    ASGI [1] Completed
INFO:     Application shutdown complete.
INFO:     Finished server process [9310]
INFO:     Stopping reloader process [9299]

I was working on making a reproducer repo and was unable to reproduce using python -m http.server. This got me digging in to things a bit more and it turns out the Vite proxy was holding the connection open. I wasn't able to get the Vite configuration to work correctly, so I added an .env.local for CORS origins so I could bypass the Vite proxy completely.

sysid commented

@wwitzel3 , glad you could sort it out. Kind regards.