podman
116 строк · 2.7 Кб
1//go:build go1.16
2// +build go1.16
3
4package pwalkdir
5
6import (
7"fmt"
8"io/fs"
9"path/filepath"
10"runtime"
11"sync"
12)
13
14// Walk is a wrapper for filepath.WalkDir which can call multiple walkFn
15// in parallel, allowing to handle each item concurrently. A maximum of
16// twice the runtime.NumCPU() walkFn will be called at any one time.
17// If you want to change the maximum, use WalkN instead.
18//
19// The order of calls is non-deterministic.
20//
21// Note that this implementation only supports primitive error handling:
22//
23// - no errors are ever passed to walkFn;
24//
25// - once a walkFn returns any error, all further processing stops
26// and the error is returned to the caller of Walk;
27//
28// - filepath.SkipDir is not supported;
29//
30// - if more than one walkFn instance will return an error, only one
31// of such errors will be propagated and returned by Walk, others
32// will be silently discarded.
33func Walk(root string, walkFn fs.WalkDirFunc) error {
34return WalkN(root, walkFn, runtime.NumCPU()*2)
35}
36
37// WalkN is a wrapper for filepath.WalkDir which can call multiple walkFn
38// in parallel, allowing to handle each item concurrently. A maximum of
39// num walkFn will be called at any one time.
40//
41// Please see Walk documentation for caveats of using this function.
42func WalkN(root string, walkFn fs.WalkDirFunc, num int) error {
43// make sure limit is sensible
44if num < 1 {
45return fmt.Errorf("walk(%q): num must be > 0", root)
46}
47
48files := make(chan *walkArgs, 2*num)
49errCh := make(chan error, 1) // Get the first error, ignore others.
50
51// Start walking a tree asap.
52var (
53err error
54wg sync.WaitGroup
55
56rootLen = len(root)
57rootEntry *walkArgs
58)
59wg.Add(1)
60go func() {
61err = filepath.WalkDir(root, func(p string, entry fs.DirEntry, err error) error {
62if err != nil {
63close(files)
64return err
65}
66if len(p) == rootLen {
67// Root entry is processed separately below.
68rootEntry = &walkArgs{path: p, entry: entry}
69return nil
70}
71// Add a file to the queue unless a callback sent an error.
72select {
73case e := <-errCh:
74close(files)
75return e
76default:
77files <- &walkArgs{path: p, entry: entry}
78return nil
79}
80})
81if err == nil {
82close(files)
83}
84wg.Done()
85}()
86
87wg.Add(num)
88for i := 0; i < num; i++ {
89go func() {
90for file := range files {
91if e := walkFn(file.path, file.entry, nil); e != nil {
92select {
93case errCh <- e: // sent ok
94default: // buffer full
95}
96}
97}
98wg.Done()
99}()
100}
101
102wg.Wait()
103
104if err == nil {
105err = walkFn(rootEntry.path, rootEntry.entry, nil)
106}
107
108return err
109}
110
111// walkArgs holds the arguments that were passed to the Walk or WalkN
112// functions.
113type walkArgs struct {
114entry fs.DirEntry
115path string
116}
117