[tools] gopls/internal/cmd: use localhost:0 to avoid port race

1 view
Skip to first unread message

Hongxiang Jiang (Gerrit)

unread,
Jul 18, 2026, 5:43:32 PM (3 days ago) Jul 18
to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

Hongxiang Jiang has uploaded the change for review

Commit message

gopls/internal/cmd: use localhost:0 to avoid port race

Previously, TestMCPCommandHTTP executed in the following order:
- grab a random port using net.Listen("tcp", "localhost:0")
- record the port number
- close the listener socket
- launch gopls with -listen=localhost:<port>

A race condition could occur between closing the listener and gopls
starting up, allowing another process to claim the port and causing
gopls to fail with "address already in use".

This CL shifts port selection from the test function to gopls by
passing -listen=localhost:0 to gopls. Since net.Listen guarantees
atomic port allocation, gopls selects and binds the port directly,
and the test reads the allocated listening address from gopls stderr
logs.

Fixes golang/go#79983
Change-Id: Ic5be1881c74d8722a9661773984ad4e0ac0bf352

Change diff

diff --git a/gopls/internal/cmd/mcp_test.go b/gopls/internal/cmd/mcp_test.go
index b19df82..3576d42 100644
--- a/gopls/internal/cmd/mcp_test.go
+++ b/gopls/internal/cmd/mcp_test.go
@@ -10,13 +10,11 @@
"context"
"encoding/json"
"fmt"
- "net"
"os"
"os/exec"
"path/filepath"
"runtime"
"slices"
- "strconv"
"strings"
"testing"
"time"
@@ -205,9 +203,7 @@

func MyFun() {}
`)
- port := strconv.Itoa(getRandomPort())
- addr := "localhost:" + port
- goplsCmd := exec.Command(os.Args[0], "-v", "mcp", "-listen="+addr)
+ goplsCmd := exec.Command(os.Args[0], "-v", "mcp", "-listen=localhost:0")
goplsCmd.Env = append(os.Environ(), "ENTRYPOINT=goplsMain")
goplsCmd.Dir = tree
goplsCmd.Stdout = os.Stderr
@@ -232,19 +228,19 @@
goplsCmd.Wait()
}()

- // Wait for the MCP server to start listening. The referenced log occurs
- // after the connection is opened via net.Listen and the HTTP handlers are
- // set up.
- ready := make(chan bool, 1)
+ // Wait for the MCP server to start listening and send the address through
+ // the channel. The referenced log occurs after the connection is opened via
+ // net.Listen and the HTTP handlers are set up.
+ ready := make(chan string, 1)
go func() {
defer close(ready)
- // Copy from the pipe to stderr, keeping an eye out for the "mcp http
- // server listening" string.
+ // Copy from the pipe to stderr, keeping an eye out for the "Gopls MCP
+ // server: listening on <addr>" string.
scan := bufio.NewScanner(stderr)
for scan.Scan() {
line := scan.Text()
- if strings.Contains(line, "mcp http server listening") {
- ready <- true
+ if _, after, ok := strings.Cut(line, "Gopls MCP server: listening on "); ok {
+ ready <- strings.TrimSpace(after)
}
fmt.Fprintln(os.Stderr, line)
}
@@ -253,9 +249,10 @@
}
}()

+ var addr string
select {
- case ok := <-ready:
- if !ok {
+ case addr = <-ready:
+ if addr == "" {
t.Fatalf("gopls mcp server exited without starting")
}
case <-time.After(30 * time.Minute):
@@ -430,18 +427,6 @@
return buf.String()
}

-// getRandomPort returns the number of a random available port. Inherently racy:
-// nothing stops another process from listening on it - but this should be fine
-// for testing purposes.
-func getRandomPort() int {
- listener, err := net.Listen("tcp", "localhost:0")
- if err != nil {
- panic(err)
- }
- defer listener.Close()
- return listener.Addr().(*net.TCPAddr).Port
-}
-
// supportsFsnotify returns true if fsnotify supports the os.
func supportsFsnotify(os string) bool {
return os == "darwin" || os == "linux" || os == "windows"
diff --git a/gopls/internal/mcp/mcp.go b/gopls/internal/mcp/mcp.go
index 988e206..b2f017b 100644
--- a/gopls/internal/mcp/mcp.go
+++ b/gopls/internal/mcp/mcp.go
@@ -56,18 +56,22 @@
if strings.HasPrefix(address, ":") {
return fmt.Errorf("address %s implicitly binds all network interfaces; please use an explicit host such as 0.0.0.0 (all interfaces) or localhost (safer)", address)
}
- log.Printf("Gopls MCP server: starting up on http")
+
listener, err := net.Listen("tcp", address)
if err != nil {
return err
}
defer listener.Close()

- // TODO(hxjiang): expose the MCP server address to the LSP client.
- if isDaemon {
- log.Printf("Gopls MCP daemon: listening on address %s...", listener.Addr())
+ {
+ kind := "server"
+ if isDaemon {
+ kind = "daemon"
+ }
+
+ log.Printf("Gopls MCP %s: listening on %s", kind, listener.Addr())
+ defer log.Printf("Gopls MCP %s: exiting", kind)
}
- defer log.Printf("Gopls MCP server: exiting")

svr := http.Server{
Handler: HTTPHandler(sessions, isDaemon, rootsHandler),
@@ -81,7 +85,6 @@
<-ctx.Done()
svr.Close() // ignore error
}()
- log.Printf("mcp http server listening")
return svr.Serve(listener)
}

Change information

Files:
  • M gopls/internal/cmd/mcp_test.go
  • M gopls/internal/mcp/mcp.go
Change size: M
Delta: 2 files changed, 21 insertions(+), 33 deletions(-)
Open in Gerrit

Related details

Attention set is empty
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: newchange
Gerrit-Project: tools
Gerrit-Branch: master
Gerrit-Change-Id: Ic5be1881c74d8722a9661773984ad4e0ac0bf352
Gerrit-Change-Number: 802761
Gerrit-PatchSet: 1
Gerrit-Owner: Hongxiang Jiang <hxj...@golang.org>
unsatisfied_requirement
satisfied_requirement
open
diffy

Hongxiang Jiang (Gerrit)

unread,
Jul 18, 2026, 5:45:45 PM (3 days ago) Jul 18
to goph...@pubsubhelper.golang.org, Madeline Kalil, golang-co...@googlegroups.com
Attention needed from Madeline Kalil

Hongxiang Jiang voted Commit-Queue+1

Commit-Queue+1
Open in Gerrit

Related details

Attention is currently required from:
  • Madeline Kalil
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: tools
Gerrit-Branch: master
Gerrit-Change-Id: Ic5be1881c74d8722a9661773984ad4e0ac0bf352
Gerrit-Change-Number: 802761
Gerrit-PatchSet: 1
Gerrit-Owner: Hongxiang Jiang <hxj...@golang.org>
Gerrit-Reviewer: Hongxiang Jiang <hxj...@golang.org>
Gerrit-Reviewer: Madeline Kalil <mka...@google.com>
Gerrit-Attention: Madeline Kalil <mka...@google.com>
Gerrit-Comment-Date: Sat, 18 Jul 2026 21:45:42 +0000
Gerrit-HasComments: No
Gerrit-Has-Labels: Yes
unsatisfied_requirement
satisfied_requirement
open
diffy

Madeline Kalil (Gerrit)

unread,
6:03 PM (5 hours ago) 6:03 PM
to Hongxiang Jiang, goph...@pubsubhelper.golang.org, golang...@luci-project-accounts.iam.gserviceaccount.com, golang-co...@googlegroups.com
Attention needed from Hongxiang Jiang

Madeline Kalil voted Code-Review+2

Code-Review+2
Open in Gerrit

Related details

Attention is currently required from:
  • Hongxiang Jiang
Submit Requirements:
  • requirement satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement satisfiedReview-Enforcement
  • requirement satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: tools
Gerrit-Branch: master
Gerrit-Change-Id: Ic5be1881c74d8722a9661773984ad4e0ac0bf352
Gerrit-Change-Number: 802761
Gerrit-PatchSet: 1
Gerrit-Owner: Hongxiang Jiang <hxj...@golang.org>
Gerrit-Reviewer: Hongxiang Jiang <hxj...@golang.org>
Gerrit-Reviewer: Madeline Kalil <mka...@google.com>
Gerrit-Attention: Hongxiang Jiang <hxj...@golang.org>
Gerrit-Comment-Date: Tue, 21 Jul 2026 22:03:45 +0000
Gerrit-HasComments: No
Gerrit-Has-Labels: Yes
satisfied_requirement
open
diffy

Madeline Kalil (Gerrit)

unread,
6:03 PM (5 hours ago) 6:03 PM
to Hongxiang Jiang, goph...@pubsubhelper.golang.org, golang...@luci-project-accounts.iam.gserviceaccount.com, golang-co...@googlegroups.com
Attention needed from Hongxiang Jiang

Madeline Kalil added 1 comment

Patchset-level comments
File-level comment, Patchset 1 (Latest):
Madeline Kalil . resolved

Thanks!

Open in Gerrit

Related details

Attention is currently required from:
  • Hongxiang Jiang
Submit Requirements:
  • requirement satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement satisfiedReview-Enforcement
  • requirement satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: tools
Gerrit-Branch: master
Gerrit-Change-Id: Ic5be1881c74d8722a9661773984ad4e0ac0bf352
Gerrit-Change-Number: 802761
Gerrit-PatchSet: 1
Gerrit-Owner: Hongxiang Jiang <hxj...@golang.org>
Gerrit-Reviewer: Hongxiang Jiang <hxj...@golang.org>
Gerrit-Reviewer: Madeline Kalil <mka...@google.com>
Gerrit-Attention: Hongxiang Jiang <hxj...@golang.org>
Gerrit-Comment-Date: Tue, 21 Jul 2026 22:03:52 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
satisfied_requirement
open
diffy
Reply all
Reply to author
Forward
0 new messages