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  [!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