fix(proxmox): prevent goroutine leaks by closing idle HTTP connections

Added a function to close idle HTTP connections in the LXCCommand method. This addresses potential goroutine leaks caused by the go-proxmox library's TermWebSocket not closing underlying HTTP/2 connections. The websocket closer is now wrapped to ensure proper cleanup of transport connections when the command execution is finished.
This commit is contained in:
yusing
2026-01-25 12:28:51 +08:00
parent 9adeb3e3dd
commit 55e09c02b1

View File

@@ -5,12 +5,22 @@ import (
"context"
"fmt"
"io"
"net/http"
"github.com/gorilla/websocket"
)
var ErrNoSession = fmt.Errorf("no session found, make sure username and password are set")
// closeTransportConnections forces close idle HTTP connections to prevent goroutine leaks.
// This is needed because the go-proxmox library's TermWebSocket closer doesn't close
// the underlying HTTP/2 connections, leaving goroutines stuck in writeLoop/readLoop.
func closeTransportConnections(httpClient *http.Client) {
if tr, ok := httpClient.Transport.(*http.Transport); ok {
tr.CloseIdleConnections()
}
}
// LXCCommand connects to the Proxmox VNC websocket and streams command output.
// It returns an io.ReadCloser that streams the command output.
func (n *Node) LXCCommand(ctx context.Context, vmid int, command string) (io.ReadCloser, error) {
@@ -37,11 +47,19 @@ func (n *Node) LXCCommand(ctx context.Context, vmid int, command string) (io.Rea
return nil, fmt.Errorf("failed to get term proxy: %w", err)
}
send, recv, errs, close, err := node.TermWebSocket(term)
send, recv, errs, closeWS, err := node.TermWebSocket(term)
if err != nil {
return nil, fmt.Errorf("failed to connect to term websocket: %w", err)
}
// Wrap the websocket closer to also close HTTP transport connections.
// This prevents goroutine leaks when streaming connections are interrupted.
httpClient := n.client.GetHTTPClient()
closeFn := func() error {
closeTransportConnections(httpClient)
return closeWS()
}
handleSend := func(data []byte) error {
select {
case <-ctx.Done():
@@ -67,7 +85,7 @@ func (n *Node) LXCCommand(ctx context.Context, vmid int, command string) (io.Rea
// Start a goroutine to read from websocket and write to pipe
go func() {
defer close()
defer closeFn()
defer pw.Close()
seenCommand := false