github.com/pankona/gometalinter@v2.0.11+incompatible/_linters/src/golang.org/x/tools/imports/fastwalk.go (about) 1 // Copyright 2016 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // A faster implementation of filepath.Walk. 6 // 7 // filepath.Walk's design necessarily calls os.Lstat on each file, 8 // even if the caller needs less info. And goimports only need to know 9 // the type of each file. The kernel interface provides the type in 10 // the Readdir call but the standard library ignored it. 11 // fastwalk_unix.go contains a fork of the syscall routines. 12 // 13 // See golang.org/issue/16399 14 15 package imports 16 17 import ( 18 "errors" 19 "os" 20 "path/filepath" 21 "runtime" 22 "sync" 23 ) 24 25 // traverseLink is a sentinel error for fastWalk, similar to filepath.SkipDir. 26 var traverseLink = errors.New("traverse symlink, assuming target is a directory") 27 28 // fastWalk walks the file tree rooted at root, calling walkFn for 29 // each file or directory in the tree, including root. 30 // 31 // If fastWalk returns filepath.SkipDir, the directory is skipped. 32 // 33 // Unlike filepath.Walk: 34 // * file stat calls must be done by the user. 35 // The only provided metadata is the file type, which does not include 36 // any permission bits. 37 // * multiple goroutines stat the filesystem concurrently. The provided 38 // walkFn must be safe for concurrent use. 39 // * fastWalk can follow symlinks if walkFn returns the traverseLink 40 // sentinel error. It is the walkFn's responsibility to prevent 41 // fastWalk from going into symlink cycles. 42 func fastWalk(root string, walkFn func(path string, typ os.FileMode) error) error { 43 // TODO(bradfitz): make numWorkers configurable? We used a 44 // minimum of 4 to give the kernel more info about multiple 45 // things we want, in hopes its I/O scheduling can take 46 // advantage of that. Hopefully most are in cache. Maybe 4 is 47 // even too low of a minimum. Profile more. 48 numWorkers := 4 49 if n := runtime.NumCPU(); n > numWorkers { 50 numWorkers = n 51 } 52 53 // Make sure to wait for all workers to finish, otherwise 54 // walkFn could still be called after returning. This Wait call 55 // runs after close(e.donec) below. 56 var wg sync.WaitGroup 57 defer wg.Wait() 58 59 w := &walker{ 60 fn: walkFn, 61 enqueuec: make(chan walkItem, numWorkers), // buffered for performance 62 workc: make(chan walkItem, numWorkers), // buffered for performance 63 donec: make(chan struct{}), 64 65 // buffered for correctness & not leaking goroutines: 66 resc: make(chan error, numWorkers), 67 } 68 defer close(w.donec) 69 70 for i := 0; i < numWorkers; i++ { 71 wg.Add(1) 72 go w.doWork(&wg) 73 } 74 todo := []walkItem{{dir: root}} 75 out := 0 76 for { 77 workc := w.workc 78 var workItem walkItem 79 if len(todo) == 0 { 80 workc = nil 81 } else { 82 workItem = todo[len(todo)-1] 83 } 84 select { 85 case workc <- workItem: 86 todo = todo[:len(todo)-1] 87 out++ 88 case it := <-w.enqueuec: 89 todo = append(todo, it) 90 case err := <-w.resc: 91 out-- 92 if err != nil { 93 return err 94 } 95 if out == 0 && len(todo) == 0 { 96 // It's safe to quit here, as long as the buffered 97 // enqueue channel isn't also readable, which might 98 // happen if the worker sends both another unit of 99 // work and its result before the other select was 100 // scheduled and both w.resc and w.enqueuec were 101 // readable. 102 select { 103 case it := <-w.enqueuec: 104 todo = append(todo, it) 105 default: 106 return nil 107 } 108 } 109 } 110 } 111 } 112 113 // doWork reads directories as instructed (via workc) and runs the 114 // user's callback function. 115 func (w *walker) doWork(wg *sync.WaitGroup) { 116 defer wg.Done() 117 for { 118 select { 119 case <-w.donec: 120 return 121 case it := <-w.workc: 122 select { 123 case <-w.donec: 124 return 125 case w.resc <- w.walk(it.dir, !it.callbackDone): 126 } 127 } 128 } 129 } 130 131 type walker struct { 132 fn func(path string, typ os.FileMode) error 133 134 donec chan struct{} // closed on fastWalk's return 135 workc chan walkItem // to workers 136 enqueuec chan walkItem // from workers 137 resc chan error // from workers 138 } 139 140 type walkItem struct { 141 dir string 142 callbackDone bool // callback already called; don't do it again 143 } 144 145 func (w *walker) enqueue(it walkItem) { 146 select { 147 case w.enqueuec <- it: 148 case <-w.donec: 149 } 150 } 151 152 func (w *walker) onDirEnt(dirName, baseName string, typ os.FileMode) error { 153 joined := dirName + string(os.PathSeparator) + baseName 154 if typ == os.ModeDir { 155 w.enqueue(walkItem{dir: joined}) 156 return nil 157 } 158 159 err := w.fn(joined, typ) 160 if typ == os.ModeSymlink { 161 if err == traverseLink { 162 // Set callbackDone so we don't call it twice for both the 163 // symlink-as-symlink and the symlink-as-directory later: 164 w.enqueue(walkItem{dir: joined, callbackDone: true}) 165 return nil 166 } 167 if err == filepath.SkipDir { 168 // Permit SkipDir on symlinks too. 169 return nil 170 } 171 } 172 return err 173 } 174 175 func (w *walker) walk(root string, runUserCallback bool) error { 176 if runUserCallback { 177 err := w.fn(root, os.ModeDir) 178 if err == filepath.SkipDir { 179 return nil 180 } 181 if err != nil { 182 return err 183 } 184 } 185 186 return readDir(root, w.onDirEnt) 187 }