Skip to content

cmd/go: GOCACHEPROG asks to overwrite existing output ID with open file #71059

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
klauspost opened this issue Dec 29, 2024 · 11 comments
Closed
Labels
GoCommand cmd/go NeedsFix The path to resolution is known, but the work has not been done. release-blocker
Milestone

Comments

@klauspost
Copy link
Contributor

klauspost commented Dec 29, 2024

Go version

go version go1.24rc1 windows/amd64

Output of go env in your module/workspace:

set AR=ar
set CC=gcc
set CGO_CFLAGS=-O2 -g
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-O2 -g
set CGO_ENABLED=1
set CGO_FFLAGS=-O2 -g
set CGO_LDFLAGS=-O2 -g
set CXX=g++
set GCCGO=gccgo
set GO111MODULE=
set GOAMD64=v1
set GOARCH=amd64
set GOAUTH=netrc
set GOBIN=
set GOCACHE=e:\temp\gocache
set GODEBUG=
set GOENV=C:\Users\klaus\AppData\Roaming\go\env
set GOEXE=.exe
set GOEXPERIMENT=
set GOFIPS140=off
set GOFLAGS=
set GOGCCFLAGS=-m64 -mthreads -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=e:\temp\wintemp\go-build3327516323=/tmp/go-build -gno-record-gcc-switches
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOINSECURE=
set GOMOD=e:\gopath\src\github.com\minio\minio\go.mod
set GOMODCACHE=e:\gopath\pkg\mod
set GONOPROXY=
set GONOSUMDB=
set GOOS=windows
set GOPATH=e:\gopath
set GOPRIVATE=
set GOPROXY=https://goproxy.io,https://proxy.golang.org,direct
set GOROOT=C:\go
set GOSUMDB=sum.golang.org
set GOTELEMETRY=local
set GOTELEMETRYDIR=C:\Users\klaus\AppData\Roaming\go\telemetry
set GOTMPDIR=
set GOTOOLCHAIN=auto
set GOTOOLDIR=C:\go\pkg\tool\windows_amd64
set GOVCS=
set GOVERSION=go1.24rc1
set GOWORK=
set PKG_CONFIG=pkg-config

Also set GOCACHEPROG=go-cacher --verbose (seems to be missing from go.env)

What did you do?

λ go build -v . with above cache enabled.

What did you see happen?

It seems like the go builder keeps the output file open in some cases while asking the cache to update it... This is a problem on Windows, since it is unable to write to an open file.

Request 1, asks for an output id {"ID":1,"Command":"get","ActionID":"pBAlwHUzJSDNnEKHSTg/axJSDSKXGl8g+SZj8jK6m58="}

We respond with the output id: {"ID":1,"OutputID":"/Rw93YonOb5fieSCtjYgTXP1E/KTrUN78aVuoX9f++A=","Size":283,"Time":"2024-12-29T15:19:59.2800213+01:00","DiskPath":"C:\\Users\\klaus\\AppData\\Local\\go-cacher\\o-fd1c3ddd8a2739be5f89e482b636204d73f513f293ad437bf1a56ea17f5ffbe0"}

go file then opens this file (grabbed through process monitor)

16.13.53,1788842	go.exe	8448	CreateFile	C:\Users\klaus\AppData\Local\go-cacher\o-fd1c3ddd8a2739be5f89e482b636204d73f513f293ad437bf1a56ea17f5ffbe0	SUCCESS	Desired Access: Generic Read, Disposition: Open, Options: Synchronous IO Non-Alert, Attributes: R, ShareMode: Read, Write, AllocationSize: n/a, OpenResult: Opened
16.13.53,1789196	go.exe	8448	QueryInformationVolume	C:\Users\klaus\AppData\Local\go-cacher\o-fd1c3ddd8a2739be5f89e482b636204d73f513f293ad437bf1a56ea17f5ffbe0	SUCCESS	VolumeCreationTime: 19/07/2019 03.28.03, VolumeSerialNumber: 1897-A658, SupportsObjects: True, VolumeLabel: 
16.13.53,1789285	go.exe	8448	QueryAllInformationFile	C:\Users\klaus\AppData\Local\go-cacher\o-fd1c3ddd8a2739be5f89e482b636204d73f513f293ad437bf1a56ea17f5ffbe0	BUFFER OVERFLOW	CreationTime: 29/12/2024 14.59.58, LastAccessTime: 29/12/2024 16.12.12, LastWriteTime: 29/12/2024 15.19.59, ChangeTime: 29/12/2024 15.19.59, FileAttributes: A, AllocationSize: 288, EndOfFile: 283, NumberOfLinks: 1, DeletePending: False, Directory: False, IndexNumber: 0x1f0000000c8656, EaSize: 0, Access: Generic Read, Position: 0, Mode: Synchronous IO Non-Alert, AlignmentRequirement: Long
16.13.53,1789446	go.exe	8448	CreateFileMapping	C:\Users\klaus\AppData\Local\go-cacher\o-fd1c3ddd8a2739be5f89e482b636204d73f513f293ad437bf1a56ea17f5ffbe0	FILE LOCKED WITH ONLY READERS	SyncType: SyncTypeCreateSection, PageProtection: |PAGE_NOCACHE
16.13.53,1789542	go.exe	8448	QueryStandardInformationFile	C:\Users\klaus\AppData\Local\go-cacher\o-fd1c3ddd8a2739be5f89e482b636204d73f513f293ad437bf1a56ea17f5ffbe0	SUCCESS	AllocationSize: 288, EndOfFile: 283, NumberOfLinks: 1, DeletePending: False, Directory: False
16.13.53,1789704	go.exe	8448	CreateFileMapping	C:\Users\klaus\AppData\Local\go-cacher\o-fd1c3ddd8a2739be5f89e482b636204d73f513f293ad437bf1a56ea17f5ffbe0	SUCCESS	SyncType: SyncTypeOther

It then sends a request to update the same output id:

{"ID":2,"Command":"put","ActionID":"pBAlwHUzJSDNnEKHSTg/axJSDSKXGl8g+SZj8jK6m58=","OutputID":"/Rw93YonOb5fieSCtjYgTXP1E/KTrUN78aVuoX9f++A=","BodySize":283}

With deterministic files, this fails since go.exe never closed the handle on the file:

16.13.53,1803634	go-cacher.exe	10208	SetRenameInformationFile	C:\Users\klaus\AppData\Local\go-cacher\o-fd1c3ddd8a2739be5f89e482b636204d73f513f293ad437bf1a56ea17f5ffbe0.2969869016	ACCESS DENIED	ReplaceIfExists: True, FileName: C:\Users\klaus\AppData\Local\go-cacher\o-fd1c3ddd8a2739be5f89e482b636204d73f513f293ad437bf1a56ea17f5ffbe0

We return an error:

{"ID":2,"Err":"rename C:\\Users\\klaus\\AppData\\Local\\go-cacher\\o-fd1c3ddd8a2739be5f89e482b636204d73f513f293ad437bf1a56ea17f5ffbe0.3796057084 C:\\Users\\klaus\\AppData\\Local\\go-cacher\\o-fd1c3ddd8a2739be5f89e482b636204d73f513f293ad437bf1a56ea17f5ffbe0: Access is denied."}

... and go.exe now closes the file:

16.13.53,1842822	go.exe	8448	CloseFile	C:\Users\klaus\AppData\Local\go-cacher\o-fd1c3ddd8a2739be5f89e482b636204d73f513f293ad437bf1a56ea17f5ffbe0	SUCCESS	

It seems to only happen on non-clean setup. Building from scratch doesn't trigger the issue.

What did you expect to see?

Go closes the file before it asks cache to update it.

@klauspost
Copy link
Contributor Author

It seems like cache.GetMmap doesn't return the file handle, so it is effectively leaked, AFAICT.

Furthermore, it seems like mmap.Mmap leaks the file handle if an error occurs. It is calling mmapFile, which doesn't close the file, so the file remains open. The handle isn't returned on any error path, so callers cannot do the cleanup either.

@klauspost
Copy link
Contributor Author

klauspost commented Dec 29, 2024

FWIW replacing both cache.GetMmap with cache.GetBytes seems to fix it. So if you are looking at a quick fix disabling mmap on Windows seems to be it.

@dmitshur dmitshur added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Dec 30, 2024
@dmitshur dmitshur added this to the Go1.24 milestone Dec 30, 2024
@dmitshur
Copy link
Contributor

dmitshur commented Dec 30, 2024

Thanks for the report. Marking as a release blocker since this feature is new(ly promoted out of experimental phase) in Go 1.24, so this needs to be understood and either fixed or explicitly decided to handle it another way.

CC @bradfitz, @matloob, @samthanawalla.

@matloob
Copy link
Contributor

matloob commented Jan 3, 2025

There seem to be a couple of problems here. The unfortunate part is that affects opening module index files on Windows, even when not using a GOCACHEPROG. We'd (pretty much) always fail to open the module index file using GetMmap, and then silently fail to write a new file.

We should not be trying to write a new file unless the error we get from GetMmap is a IsNotExist error. If there was a problem mmapping the file (or there was another problem but the file exists on disk), we should just give up then.

But the bigger problem, and why things were broken on Windows is the check in GetMmap that the size of the slice returned by mmap is the same as the expected size of the file. Currently on Windows, the size that the Mmap function that GetMmap uses is actually the size of the mapping, which seems to be a multiple of the page size. If I understand this correctly, I think the solution here is to change the Mmap function we use to re-slice the slice we use for the mapping on Windowsto the size of the file before we do the check. Alternatively, we could avoid the size check when we return from the mmap function.

@gopherbot
Copy link
Contributor

Change https://go.dev/cl/640155 mentions this issue: cmd/go/internal/mmap: truncate returned slice to file size on Windows

@klauspost
Copy link
Contributor Author

I can confirm this also fixes the issue. Tried to see if modifying the on-disk content would bring it back in this state, but it just seems to break the build.

I personally think this takes it off the "release blocker" status, but seems like some hardening could be a good idea.

@matloob
Copy link
Contributor

matloob commented Jan 6, 2025

I agree. I'm trying to figure out where that should be done. We need to distinguish the error case where the file isn't cached (in which case we haven't opened the file) vs. the case where the file is the wrong size (in which case we have opened the file, and we won't be able to open it to write the replacement).

@gopherbot
Copy link
Contributor

Change https://go.dev/cl/640577 mentions this issue: cmd/go/internal/modindex: don't write index entry if file open

@gopherbot
Copy link
Contributor

Change https://go.dev/cl/640755 mentions this issue: cmd/go/internal/env: add GOCACHEPROG to go env output

gopherbot pushed a commit that referenced this issue Jan 7, 2025
For #71059

Change-Id: I4bbdd14d416dc2e6dae3549a84c16dbef9d4e645
Reviewed-on: https://go-review.googlesource.com/c/go/+/640755
Reviewed-by: Sam Thanawalla <[email protected]>
Reviewed-by: Austin Clements <[email protected]>
LUCI-TryBot-Result: Go LUCI <[email protected]>
gopherbot pushed a commit that referenced this issue Jan 7, 2025
On Windows, we can't open a file that's already been opened. Before this
change, we'd try to write an index entry if mmapping the entry failed.
But that could happen either if the file doesn't exist or if there was a
problem mmapping an already opened file. Pass through information about
whether the file was actually opened so that we don't try to write to an
already opened file.

For #71059

Change-Id: I6adabe1093fed9ec37e7fafb13384c102786cbce
Reviewed-on: https://go-review.googlesource.com/c/go/+/640577
Reviewed-by: Sam Thanawalla <[email protected]>
LUCI-TryBot-Result: Go LUCI <[email protected]>
@gopherbot
Copy link
Contributor

Change https://go.dev/cl/641375 mentions this issue: cmd/go/testdata/script: fix TestScript/env_gocacheprog on Windows

gopherbot pushed a commit that referenced this issue Jan 8, 2025
The backslashes on the windows paths will be escaped, so when checking
for them in the regular expression we'd have to have quadruple
backslashes '\\\\'. Since it's difficult to escape $GOCACHEPROG properly
for both json and regexp, just check for a string that ends in
cacheprog$GOEXE. We already check that the proper value is reported in
go env and go env -changed, and the json test case is mostly useful to
verify that GOCACHEPROG shows up in the json output.

For #71059

Change-Id: I52d49de61f2309a139f84c4d232b4cd94546ec8c
Cq-Include-Trybots: luci.golang.try:gotip-windows-amd64-longtest,gotip-linux-amd64-longtest
Reviewed-on: https://go-review.googlesource.com/c/go/+/641375
Reviewed-by: Dmitri Shuralyov <[email protected]>
LUCI-TryBot-Result: Go LUCI <[email protected]>
Reviewed-by: Sam Thanawalla <[email protected]>
Reviewed-by: Dmitri Shuralyov <[email protected]>
gopherbot pushed a commit that referenced this issue Jan 9, 2025
The Mmap function returns a Data struct containing a slice with the
mapped contents of the file. Before this change, on Windows, the slice
contained the contents of all the pages of the mapping, including past
the end of the file. Re-slice the slice to the length of the file (if
if the slice is longer) so that the slice contains only the data in the
file.

For #71059

Change-Id: I389b752505b6fa1252b5c6d836a37bc7e662a45d
Reviewed-on: https://go-review.googlesource.com/c/go/+/640155
Reviewed-by: Russ Cox <[email protected]>
LUCI-TryBot-Result: Go LUCI <[email protected]>
@matloob matloob closed this as completed Jan 9, 2025
@dmitshur dmitshur added NeedsFix The path to resolution is known, but the work has not been done. and removed NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. labels Jan 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
GoCommand cmd/go NeedsFix The path to resolution is known, but the work has not been done. release-blocker
Projects
None yet
Development

No branches or pull requests

5 participants