# This test simulates a process watching for changes and reading files in # module cache as a module is extracted. # # Before Go 1.16, we extracted each module zip to a temporary directory with # a random name, then renamed that into place with os.Rename. On Windows, # this failed with ERROR_ACCESS_DENIED when another process (usually an # anti-virus scanner) opened files in the temporary directory. This test # simulates that behavior, verifying golang.org/issue/36568. # # Since 1.16, we extract to the final directory, but we create a .partial file # so that if we crash, other processes know the directory is incomplete. [!GOOS:windows] skip [short] skip go run downloader.go -- go.mod -- module example.com/m go 1.14 -- downloader.go -- package main import ( "fmt" "log" "os" "os/exec" "path/filepath" ) func main() { if err := run(); err != nil { log.Fatal(err) } } // run repeatedly downloads a module while opening files in the module cache // in a background goroutine. // // run uses a different temporary module cache in each iteration so that we // don't need to clean the cache or synchronize closing files after each // iteration. func run() (err error) { tmpDir, err := os.MkdirTemp("", "") if err != nil { return err } defer func() { if rmErr := os.RemoveAll(tmpDir); err == nil && rmErr != nil { err = rmErr } }() for i := 0; i < 10; i++ { gopath := filepath.Join(tmpDir, fmt.Sprintf("gopath%d", i)) var err error done := make(chan struct{}) go func() { err = download(gopath) close(done) }() readCache(gopath, done) if err != nil { return err } } return nil } // download downloads a module into the given cache using 'go mod download'. func download(gopath string) error { cmd := exec.Command("go", "mod", "download", "-modcacherw", "rsc.io/quote@v1.5.2") cmd.Stderr = os.Stderr cmd.Env = append(os.Environ(), "GOPATH="+gopath) return cmd.Run() } // readCache repeatedly globs for go.mod files in the given cache, then opens // those files for reading. When the done chan is closed, readCache closes // files and returns. func readCache(gopath string, done <-chan struct{}) { files := make(map[string]*os.File) defer func() { for _, f := range files { f.Close() } }() pattern := filepath.Join(gopath, "pkg/mod/rsc.io/quote@v1.5.2*/go.mod") for { select { case <-done: return default: } names, _ := filepath.Glob(pattern) for _, name := range names { if files[name] != nil { continue } f, _ := os.Open(name) if f != nil { files[name] = f } } } }