Skip to content

Commit f8ce7a7

Browse files
committed
rpctest: scope shared state to the current process
Two pieces of rpctest's global state silently aliased across concurrent test processes (which is what `go test ./...` does by default, so any `make unit` that exercises -tags=rpctest hit this): - btcdExecutablePath compiled to a fixed path /tmp/btcd/rpctest/btcd. Two `go build` invocations would race on the same file, occasionally yielding a truncated or stale binary and downstream "tls: certificate signed by unknown authority" failures when the harness tried to talk to the resulting node. - lastPort started at the same defaultNodePort in every process. The bind-test in NextAvailablePort closes the listener before returning, so two processes climbing from the same base would frequently hand out the same port and one harness would die with "connection refused" when btcd failed to bind. Suffix the executable with a random uint32 and seed lastPort with a random offset into a 50k-port window so each process climbs through its own range.
1 parent bfb36e5 commit f8ce7a7

2 files changed

Lines changed: 17 additions & 3 deletions

File tree

integration/rpctest/btcd.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package rpctest
66

77
import (
88
"fmt"
9+
"math/rand/v2"
910
"os/exec"
1011
"path/filepath"
1112
"runtime"
@@ -43,8 +44,12 @@ func btcdExecutablePath() (string, error) {
4344
return "", err
4445
}
4546

46-
// Build btcd and output an executable in a static temp path.
47-
outputPath := filepath.Join(testDir, "btcd")
47+
// Build btcd to a random path so concurrent `go test` processes
48+
// (e.g. when test packages run in parallel under `make unit`) do
49+
// not race on the same output file. Each test process pays a
50+
// one-time compile cost; within a process the compileMtx-guarded
51+
// cache keeps it to one build.
52+
outputPath := filepath.Join(testDir, fmt.Sprintf("btcd-%d", rand.Uint32()))
4853
if runtime.GOOS == "windows" {
4954
outputPath += ".exe"
5055
}

integration/rpctest/rpc_harness.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package rpctest
66

77
import (
88
"fmt"
9+
"math/rand/v2"
910
"net"
1011
"os"
1112
"path/filepath"
@@ -76,7 +77,15 @@ var (
7677

7778
// lastPort is the last port determined to be free for use by a new
7879
// node. It should be used atomically.
79-
lastPort uint32 = defaultNodePort
80+
//
81+
// Seed with a random offset so concurrent `go test` processes
82+
// (e.g. when integration/ and integration/rpctest/ run in parallel
83+
// under `make unit`) do not race on the same port range. The
84+
// bind-test in NextAvailablePort closes the listener before
85+
// returning, leaving a window where another process could grab the
86+
// same port; staggering each process's starting point avoids the
87+
// collision. The 50k-port window leaves headroom below 65535.
88+
lastPort uint32 = defaultNodePort + rand.Uint32N(50000)
8089
)
8190

8291
// HarnessTestCase represents a test-case which utilizes an instance of the

0 commit comments

Comments
 (0)