| # 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. |
| |
| [!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 |
| } |
| } |
| } |
| } |