#7051·kubeedge

Bug: Goroutine leak and spurious reconnects in EdgeHub on connection drop

Author: vishalmore90Created Jul 17, 2026Updated Sep 12, 2026
Labelskind/bug

What happened: In EdgeHub, the routeToCloud and keepalive goroutines can permanently leak upon reconnects. Furthermore, because reconnectChan is unbuffered, delayed errors from these leaked goroutines can cause spurious reconnects by sending a signal that tears down a newly established, healthy connection.

The old routeToCloud goroutine never exits because it is blocked on beehiveContext.Receive(). When it finally receives a message, it uses the shared eh.chClient (which has been updated to the new connection) and successfully sends the message, continuing its infinite loop. After N reconnects, there are N+1 routeToCloud goroutines leaked and competing to read from the channel. Additionally, if the old goroutine encounters a send error before the new client is ready, it sends to the unbuffered eh.reconnectChan. This blocks the goroutine and will later trigger a spurious disconnect for the next healthy connection.

What you expected to happen: Previous routeToCloud and keepalive goroutines should exit when the connection drops, ensuring only one active instance of each goroutine per active connection.

How to reproduce it (as minimally and precisely as possible):

  1. Start KubeEdge (edgecore and cloudcore).
  2. Disconnect the network between edge and cloud (e.g., stop cloudcore or block port).
  3. The routeToEdge goroutine detects the connection failure, sends to eh.reconnectChan, and exits.
  4. EdgeHub.Start() loop handles the reconnect, calls UnInit(), and sleeps.
  5. Meanwhile, the routeToCloud goroutine is blocked on beehiveContext.Receive(modules.EdgeHubModuleName) and does not exit.
  6. Once the network is restored, Start() creates a new connection, assigns it to eh.chClient, and spawns a NEW set of routeToCloud, routeToEdge, and keepalive goroutines.
  7. Repeat the disconnect/reconnect process multiple times.

Anything else we need to know?: Root cause analysis:

  1. EdgeHub.Start() spawns routeToCloud on every reconnect but provides no cancellation context to stop them.
  2. routeToCloud only exits if sendToCloud returns an error. However, if it is blocked on beehiveContext.Receive() during the disconnect, it will not hit the error path.
  3. eh.chClient is shared and updated on reconnect, so when the leaked routeToCloud eventually receives a message, it uses the new connection, succeeds, and stays alive.
  4. eh.reconnectChan <- struct{}{} blocks if no receiver is ready. A delayed error will satisfy the <-eh.reconnectChan read of a subsequent healthy connection, causing a spurious reconnect cycle.

Possible fix direction:

  • Pass a context.Context to routeToCloud, routeToEdge, and keepalive that gets cancelled when reconnectChan is triggered or when UnInit is called.
  • Make reconnectChan buffered, or use a non-blocking send (select { case eh.reconnectChan <- struct{}{}: default: }) to prevent blocking leaked goroutines.
  • Ensure old goroutines exit by checking the context instead of relying solely on network write errors.

Environment:

  • Kubernetes version (use kubectl version): N/A

  • KubeEdge version(e.g. cloudcore --version and edgecore --version): Latest master branch

  • Cloud nodes Environment:
    • Hardware configuration (e.g. lscpu): Any
    • OS (e.g. cat /etc/os-release): Any
    • Kernel (e.g. uname -a): Any
    • Go version (e.g. go version): Go 1.22+
    • Others:
  • Edge nodes Environment:
    • edgecore version (e.g. edgecore --version): Latest master branch
    • Hardware configuration (e.g. lscpu): Any
    • OS (e.g. cat /etc/os-release): Any
    • Kernel (e.g. uname -a): Any
    • Go version (e.g. go version): Go 1.22+
    • Others: