Connection leak with a Socket.IO component in React

112 Views Asked by At

I have a React application that uses Socket.IO. The Socket instance is in a React component.

I have been noticing that the action of logging out and logging into my application, which should unmount the component and close the connection and then remount and reopen the connection leads to a socket leak/creation of duplicate socket connection. I have also managed to get the application into a state where it quickly spews off new connections leading to starvation, but have not been able to replicate. This hit production once.

Here is the client code:

const Socket = React.memo(() => {
  const [isLoadingSocket, setIsLoadingSocket] = useState<boolean>(false)
  const socketRef = useRef<SocketIO<ServerToClientEvents, ClientToServerEvents> | null>(null)
  const socketNeedsRestart = isFocused ? !isLoadingSocket : false

  async function initializeSocket() {
    const token = await getToken()
    setIsLoadingSocket(true)
    if (socketRef.current) {
      socketRef.current.disconnect()
    }

    socketRef.current = io(`${SOCKET_HOST}`, {
      secure: true,
      reconnectionDelay: 5000,
      transports: ['websocket', 'polling'],
      path: ENVIRONMENT !== Environment.local ? '/api/socket.io/' : '',
      auth: {
        token,
      },
    })
    console.log(`socket initialized`)
  }

  useEffect(() => {
    if (socketNeedsRestart) {
      initializeSocket()
    }
  }, [socketNeedsRestart]) //eslint-disable-line

  useEffect(() => {
    if (socketRef.current) {
      socketRef.current.on(SocketLifecycleEvent.Connect, () => {
        console.log('socket connected')
        setIsLoadingSocket(false)
      })

      socketRef.current.on(SocketMessage.UsersOnline, (message) => {
        updateOnlineUsers(message.onlineUserIDs)
      })
    }

    return () => {
      if (socketRef.current) {
        socketRef.current.off(SocketLifecycleEvent.Connect)
        socketRef.current.off(SocketLifecycleEvent.ConnectionError)
        socketRef.current.off(SocketLifecycleEvent.Disconnect)
      }
    }
  }, [isLoadingSocket])

  useEffect(() => {
    socketRef.current?.disconnect()
  }, [])

  return <></>
})

export default Socket

The component is used once in the page that a user gets to after login. I can provide server code but it doesn't do anything except notify all users every time someone connects. What's causing the connection leak? How can I re-create the rapid-fire leak?

1

There are 1 best solutions below

2
dermeck On

Is your last useEffect doing what you expect it to do? It looks like this is supposed to be the cleanup on unmount but you are not returning a function there.

Did you try something like this?:

useEffect( () => () => socketRef.current?.disconnect(), [] );