...
Text file
src/cmd/go/testdata/script/mod_download_concurrent_read.txt
1# This test simulates a process watching for changes and reading files in
2# module cache as a module is extracted.
3#
4# Before Go 1.16, we extracted each module zip to a temporary directory with
5# a random name, then renamed that into place with os.Rename. On Windows,
6# this failed with ERROR_ACCESS_DENIED when another process (usually an
7# anti-virus scanner) opened files in the temporary directory. This test
8# simulates that behavior, verifying golang.org/issue/36568.
9#
10# Since 1.16, we extract to the final directory, but we create a .partial file
11# so that if we crash, other processes know the directory is incomplete.
12
13[!GOOS:windows] skip
14[short] skip
15
16go run downloader.go
17
18-- go.mod --
19module example.com/m
20
21go 1.14
22
23-- downloader.go --
24package main
25
26import (
27 "fmt"
28 "log"
29 "os"
30 "os/exec"
31 "path/filepath"
32)
33
34func main() {
35 if err := run(); err != nil {
36 log.Fatal(err)
37 }
38}
39
40// run repeatedly downloads a module while opening files in the module cache
41// in a background goroutine.
42//
43// run uses a different temporary module cache in each iteration so that we
44// don't need to clean the cache or synchronize closing files after each
45// iteration.
46func run() (err error) {
47 tmpDir, err := os.MkdirTemp("", "")
48 if err != nil {
49 return err
50 }
51 defer func() {
52 if rmErr := os.RemoveAll(tmpDir); err == nil && rmErr != nil {
53 err = rmErr
54 }
55 }()
56 for i := 0; i < 10; i++ {
57 gopath := filepath.Join(tmpDir, fmt.Sprintf("gopath%d", i))
58 var err error
59 done := make(chan struct{})
60 go func() {
61 err = download(gopath)
62 close(done)
63 }()
64 readCache(gopath, done)
65 if err != nil {
66 return err
67 }
68 }
69 return nil
70}
71
72// download downloads a module into the given cache using 'go mod download'.
73func download(gopath string) error {
74 cmd := exec.Command("go", "mod", "download", "-modcacherw", "rsc.io/quote@v1.5.2")
75 cmd.Stderr = os.Stderr
76 cmd.Env = append(os.Environ(), "GOPATH="+gopath)
77 return cmd.Run()
78}
79
80// readCache repeatedly globs for go.mod files in the given cache, then opens
81// those files for reading. When the done chan is closed, readCache closes
82// files and returns.
83func readCache(gopath string, done <-chan struct{}) {
84 files := make(map[string]*os.File)
85 defer func() {
86 for _, f := range files {
87 f.Close()
88 }
89 }()
90
91 pattern := filepath.Join(gopath, "pkg/mod/rsc.io/quote@v1.5.2*/go.mod")
92 for {
93 select {
94 case <-done:
95 return
96 default:
97 }
98
99 names, _ := filepath.Glob(pattern)
100 for _, name := range names {
101 if files[name] != nil {
102 continue
103 }
104 f, _ := os.Open(name)
105 if f != nil {
106 files[name] = f
107 }
108 }
109 }
110}
View as plain text