Skip to content

Can't close fetchEventSource with AbortController in React #84

@LachlanMarnham

Description

@LachlanMarnham

Hello. I'm aware this topic has been raised many times here already, and I have read all of those issues with no success. Indeed, in most cases those issues have comments from people saying the proposed solution didn't work for them either. I am running my app in React.StrictMode, which causes each component to mount/unmount/remount to help identify unexpected side-effects and so on. In my case this is causing two fetchEventSource requests to be fired, which is expected. What's unexpected, is that using an AbortController does not close the first connection on unmount. This is my hook:

const useStreamTradingNotifications = () => {
  const [ abortController, setAbortController ] = useState(new AbortController());
  useEffect(() => {
    console.log("useEffect called");
    const stream = async () => {
        await fetchEventSource("http://127.0.0.1:8000/v1/notifications", {
          signal: abortController.signal,
          onmessage(msg) {
            if (abortController.signal.aborted){
              console.log("still getting messages from aborted signal")
            }
            console.log(`event: ${msg.event}`)
            if (msg.event == "update") {
              console.log(JSON.parse(msg.data))
            }
          },
        });
    };
    stream();
    return () => {
      console.log("abort called")
      
      abortController.abort();
      setAbortController(new AbortController());
    }
  }, []);
};

When I run my app, I see the following logs:
image

As you can see:

  1. useEffect is called twice, as the component is mounted/unmounted/remounted [expected]
  2. abortController.abort is called when the first component unmounts [expected]
  3. Subsequently I receive two of each message (the logs above are truncated, and just includes the first duplicate). For one of the two streams, I can confirm that the messages are being received on an already-aborted connection (still getting messages from aborted signal).

Furthermore, in my backend logs, when I close a browser window with an active connection I see a log like:

[2024-07-09T11:13:37.409247Z][sse_starlette.sse][DEBUG] Got event: http.disconnect. Stop streaming.

But I never see this message as the result of AbortController.abort being called. I have tried assigning a new instance of AbortController after abort (as suggested here), which had no affect. I have tried doing a similar thing with setState (as mentioned here) which made the problem much worse (kept creating connections until the browser crashed). I tried to use useRef to manage the controller (suggested here, which also doesn't work.

The fact that I can see abort being called, but never see a http.disconnect on the backend, convinces me this must be a bug. But if anybody has a suspicion that I've screwed something up I'd really appreciate their suggestion.

Many thanks

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions