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