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
16 go run downloader.go
17
18 -- go.mod --
19 module example.com/m
20
21 go 1.14
22
23 -- downloader.go --
24 package main
25
26 import (
27 "fmt"
28 "log"
29 "os"
30 "os/exec"
31 "path/filepath"
32 )
33
34 func 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.
46 func 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'.
73 func 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.
83 func 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 }
111
View as plain text