gopkg.in/alecthomas/gometalinter.v3@v3.0.0/_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  }