Go version
go version go1.26.4 netbsd/amd64
Output of go env in your module/workspace:
AR='ar'
CC='gcc'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='g++'
GCCGO='gccgo'
GO111MODULE=''
GOAMD64='v1'
GOARCH='amd64'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/home/jnml/.cache/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/home/jnml/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build347595682=/tmp/go-build -gno-record-gcc-switches'
GOHOSTARCH='amd64'
GOHOSTOS='netbsd'
GOINSECURE=''
GOMOD='/home/jnml/src/modernc.org/builder/.exclude/modernc.org/file/go.mod'
GOMODCACHE='/home/jnml/pkg/mod'
GONOPROXY='gitlab.schleibinger.com/*,modernc.org/knuth*'
GONOSUMDB='gitlab.schleibinger.com/*,modernc.org/knuth*'
GOOS='netbsd'
GOPATH='/home/jnml'
GOPRIVATE='gitlab.schleibinger.com/*,modernc.org/knuth*'
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/local/go'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/home/jnml/.config/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/local/go/pkg/tool/netbsd_amd64'
GOVCS=''
GOVERSION='go1.26.4'
GOWORK=''
PKG_CONFIG='pkg-config'
What did you do?
Run the self-contained program below. It drives a deterministic, fixed-seed stream of writes/truncates against three independent in-memory files from modernc.org/file (which keep 256 KiB "pages" - large heap objects - in a sync.Pool-backed buffer pool) and, after every write, compares two views that must be byte-identical.
go.mod:
module walcorrupt
go 1.24
require (
modernc.org/file v1.0.20
modernc.org/mathutil v1.7.1
)
main.go: (The body of modernc.org/file's TestWALMem lifted out of the test framework so it runs as go run . -n 40)
What did you see happen?
On netbsd/amd64 ~50–80% of runs fail with a byte mismatch, e.g.:
FAIL (iteration 0): round 0 i 231 after WriteAt(off=0x3a9727 nw=0x2592): files differ at off 0xa40000 (size 0xd55342): got ff want 00
The mismatch offset is always 256 KiB-aligned (the memory-file page size, a large heap object) and varies run to run; the corrupt side sometimes holds stale/foreign bytes the program never wrote there, sometimes zero where data was written - i.e. a live pooled buffer's bytes change underneath its owner. No crash, no fatal error; purely silent data corruption.
Diagnosis / what was ruled out
Instrumenting the buffer pool (modernc.org/internal/slice) and mem backing:
- Requires buffer reuse. Forcing the pool to always allocate fresh (
make(), never recycle) -> 0 failures. This is the strongest signal: a pure allocation-strategy change with no logic change eliminates it.
- Not logical aliasing / use-after-free. Zero double-hand-outs and zero double-Puts. A per-page generation check (was this live buffer recycled to another owner?) never fires, yet the bytes still corrupt -> physical corruption of a live, still-owned buffer, not a data race in the Go code.
- Not GC (
GOGC=off still fails), not goroutine concurrency (GOMAXPROCS=1 still fails), not the madvise mode (GODEBUG=madvdontneed=1 still fails).
- Reproduces on both go1.26.3 and go1.26.4, so not a recent regression; never observed on linux/amd64.
This looks like a netbsd/amd64-specific runtime (or NetBSD 10.1 kernel VM) memory-management bug affecting reused large heap objects. I understand netbsd/amd64 is a second-class port; filing for the record with a reproducer.
What did you expect to see?
Because the operation stream is fully deterministic, the two views are always byte-identical. On linux/amd64 (go1.26.3 and go1.26.4) it prints ok on every run.
Full disclosure: No humans were harmed in making of this issue report.
Go version
go version go1.26.4 netbsd/amd64
Output of
go envin your module/workspace:What did you do?
Run the self-contained program below. It drives a deterministic, fixed-seed stream of writes/truncates against three independent in-memory files from
modernc.org/file(which keep 256 KiB "pages" - large heap objects - in async.Pool-backed buffer pool) and, after every write, compares two views that must be byte-identical.go.mod:main.go: (The body ofmodernc.org/file'sTestWALMemlifted out of the test framework so it runs asgo run . -n 40)What did you see happen?
On netbsd/amd64 ~50–80% of runs fail with a byte mismatch, e.g.:
The mismatch offset is always 256 KiB-aligned (the memory-file page size, a large heap object) and varies run to run; the corrupt side sometimes holds stale/foreign bytes the program never wrote there, sometimes zero where data was written - i.e. a live pooled buffer's bytes change underneath its owner. No crash, no
fatal error; purely silent data corruption.Diagnosis / what was ruled out
Instrumenting the buffer pool (
modernc.org/internal/slice) and mem backing:make(), never recycle) -> 0 failures. This is the strongest signal: a pure allocation-strategy change with no logic change eliminates it.GOGC=offstill fails), not goroutine concurrency (GOMAXPROCS=1still fails), not the madvise mode (GODEBUG=madvdontneed=1still fails).This looks like a netbsd/amd64-specific runtime (or NetBSD 10.1 kernel VM) memory-management bug affecting reused large heap objects. I understand netbsd/amd64 is a second-class port; filing for the record with a reproducer.
What did you expect to see?
Because the operation stream is fully deterministic, the two views are always byte-identical. On linux/amd64 (go1.26.3 and go1.26.4) it prints
okon every run.Full disclosure: No humans were harmed in making of this issue report.