github.com/shulhan/golangci-lint@v1.10.1/pkg/lint/runner.go (about) 1 package lint 2 3 import ( 4 "context" 5 "fmt" 6 "runtime/debug" 7 "sort" 8 "strings" 9 "sync" 10 "time" 11 12 "github.com/golangci/golangci-lint/pkg/config" 13 "github.com/golangci/golangci-lint/pkg/lint/astcache" 14 "github.com/golangci/golangci-lint/pkg/lint/linter" 15 "github.com/golangci/golangci-lint/pkg/logutils" 16 "github.com/golangci/golangci-lint/pkg/result" 17 "github.com/golangci/golangci-lint/pkg/result/processors" 18 "github.com/golangci/golangci-lint/pkg/timeutils" 19 ) 20 21 type Runner struct { 22 Processors []processors.Processor 23 Log logutils.Log 24 } 25 26 func NewRunner(astCache *astcache.Cache, cfg *config.Config, log logutils.Log) (*Runner, error) { 27 icfg := cfg.Issues 28 excludePatterns := icfg.ExcludePatterns 29 if icfg.UseDefaultExcludes { 30 excludePatterns = append(excludePatterns, config.GetDefaultExcludePatternsStrings()...) 31 } 32 33 var excludeTotalPattern string 34 if len(excludePatterns) != 0 { 35 excludeTotalPattern = fmt.Sprintf("(%s)", strings.Join(excludePatterns, "|")) 36 } 37 38 skipFilesProcessor, err := processors.NewSkipFiles(cfg.Run.SkipFiles) 39 if err != nil { 40 return nil, err 41 } 42 43 return &Runner{ 44 Processors: []processors.Processor{ 45 processors.NewPathPrettifier(), // must be before diff, nolint and exclude autogenerated processor at least 46 processors.NewCgo(), 47 skipFilesProcessor, 48 49 processors.NewAutogeneratedExclude(astCache), 50 processors.NewExclude(excludeTotalPattern), 51 processors.NewNolint(astCache), 52 53 processors.NewUniqByLine(), 54 processors.NewDiff(icfg.Diff, icfg.DiffFromRevision, icfg.DiffPatchFilePath), 55 processors.NewMaxPerFileFromLinter(), 56 processors.NewMaxSameIssues(icfg.MaxSameIssues, log.Child("max_same_issues")), 57 processors.NewMaxFromLinter(icfg.MaxIssuesPerLinter, log.Child("max_from_linter")), 58 processors.NewSourceCode(log.Child("source_code")), 59 }, 60 Log: log, 61 }, nil 62 } 63 64 type lintRes struct { 65 linter linter.Config 66 err error 67 issues []result.Issue 68 } 69 70 func (r Runner) runLinterSafe(ctx context.Context, lintCtx *linter.Context, 71 lc linter.Config) (ret []result.Issue, err error) { 72 73 defer func() { 74 if panicData := recover(); panicData != nil { 75 err = fmt.Errorf("panic occurred: %s", panicData) 76 r.Log.Warnf("Panic stack trace: %s", debug.Stack()) 77 } 78 }() 79 80 specificLintCtx := *lintCtx 81 specificLintCtx.Log = r.Log.Child(lc.Linter.Name()) 82 issues, err := lc.Linter.Run(ctx, &specificLintCtx) 83 if err != nil { 84 return nil, err 85 } 86 87 for _, i := range issues { 88 i.FromLinter = lc.Linter.Name() 89 } 90 91 return issues, nil 92 } 93 94 func (r Runner) runWorker(ctx context.Context, lintCtx *linter.Context, 95 tasksCh <-chan linter.Config, lintResultsCh chan<- lintRes, name string) { 96 97 sw := timeutils.NewStopwatch(name, r.Log) 98 defer sw.Print() 99 100 for { 101 select { 102 case <-ctx.Done(): 103 return 104 case lc, ok := <-tasksCh: 105 if !ok { 106 return 107 } 108 if ctx.Err() != nil { 109 // XXX: if check it in only int a select 110 // it's possible to not enter to this case until tasksCh is empty. 111 return 112 } 113 var issues []result.Issue 114 var err error 115 sw.TrackStage(lc.Linter.Name(), func() { 116 issues, err = r.runLinterSafe(ctx, lintCtx, lc) 117 }) 118 lintResultsCh <- lintRes{ 119 linter: lc, 120 err: err, 121 issues: issues, 122 } 123 } 124 } 125 } 126 127 func (r Runner) logWorkersStat(workersFinishTimes []time.Time) { 128 lastFinishTime := workersFinishTimes[0] 129 for _, t := range workersFinishTimes { 130 if t.After(lastFinishTime) { 131 lastFinishTime = t 132 } 133 } 134 135 logStrings := []string{} 136 for i, t := range workersFinishTimes { 137 if t.Equal(lastFinishTime) { 138 continue 139 } 140 141 logStrings = append(logStrings, fmt.Sprintf("#%d: %s", i+1, lastFinishTime.Sub(t))) 142 } 143 144 r.Log.Infof("Workers idle times: %s", strings.Join(logStrings, ", ")) 145 } 146 147 func getSortedLintersConfigs(linters []linter.Config) []linter.Config { 148 ret := make([]linter.Config, len(linters)) 149 copy(ret, linters) 150 151 sort.Slice(ret, func(i, j int) bool { 152 return ret[i].GetSpeed() < ret[j].GetSpeed() 153 }) 154 155 return ret 156 } 157 158 func (r *Runner) runWorkers(ctx context.Context, lintCtx *linter.Context, linters []linter.Config) <-chan lintRes { 159 tasksCh := make(chan linter.Config, len(linters)) 160 lintResultsCh := make(chan lintRes, len(linters)) 161 var wg sync.WaitGroup 162 163 workersFinishTimes := make([]time.Time, lintCtx.Cfg.Run.Concurrency) 164 165 for i := 0; i < lintCtx.Cfg.Run.Concurrency; i++ { 166 wg.Add(1) 167 go func(i int) { 168 defer wg.Done() 169 name := fmt.Sprintf("worker.%d", i+1) 170 r.runWorker(ctx, lintCtx, tasksCh, lintResultsCh, name) 171 workersFinishTimes[i] = time.Now() 172 }(i) 173 } 174 175 lcs := getSortedLintersConfigs(linters) 176 for _, lc := range lcs { 177 tasksCh <- lc 178 } 179 close(tasksCh) 180 181 go func() { 182 wg.Wait() 183 close(lintResultsCh) 184 185 r.logWorkersStat(workersFinishTimes) 186 }() 187 188 return lintResultsCh 189 } 190 191 func (r Runner) processLintResults(inCh <-chan lintRes) <-chan lintRes { 192 outCh := make(chan lintRes, 64) 193 194 go func() { 195 sw := timeutils.NewStopwatch("processing", r.Log) 196 197 defer close(outCh) 198 199 for res := range inCh { 200 if res.err != nil { 201 r.Log.Warnf("Can't run linter %s: %s", res.linter.Linter.Name(), res.err) 202 continue 203 } 204 205 if len(res.issues) != 0 { 206 res.issues = r.processIssues(res.issues, sw) 207 outCh <- res 208 } 209 } 210 211 // finalize processors: logging, clearing, no heavy work here 212 213 for _, p := range r.Processors { 214 sw.TrackStage(p.Name(), func() { 215 p.Finish() 216 }) 217 } 218 219 sw.PrintStages() 220 }() 221 222 return outCh 223 } 224 225 func collectIssues(resCh <-chan lintRes) <-chan result.Issue { 226 retIssues := make(chan result.Issue, 1024) 227 go func() { 228 defer close(retIssues) 229 230 for res := range resCh { 231 if len(res.issues) == 0 { 232 continue 233 } 234 235 for _, i := range res.issues { 236 retIssues <- i 237 } 238 } 239 }() 240 241 return retIssues 242 } 243 244 func (r Runner) Run(ctx context.Context, linters []linter.Config, lintCtx *linter.Context) <-chan result.Issue { 245 lintResultsCh := r.runWorkers(ctx, lintCtx, linters) 246 processedLintResultsCh := r.processLintResults(lintResultsCh) 247 if ctx.Err() != nil { 248 // XXX: always process issues, even if timeout occurred 249 finishedLintersN := 0 250 for range processedLintResultsCh { 251 finishedLintersN++ 252 } 253 254 r.Log.Errorf("%d/%d linters finished: deadline exceeded: try increase it by passing --deadline option", 255 finishedLintersN, len(linters)) 256 } 257 258 return collectIssues(processedLintResultsCh) 259 } 260 261 func (r *Runner) processIssues(issues []result.Issue, sw *timeutils.Stopwatch) []result.Issue { 262 for _, p := range r.Processors { 263 var newIssues []result.Issue 264 var err error 265 sw.TrackStage(p.Name(), func() { 266 newIssues, err = p.Process(issues) 267 }) 268 269 if err != nil { 270 r.Log.Warnf("Can't process result by %s processor: %s", p.Name(), err) 271 } else { 272 issues = newIssues 273 } 274 275 if issues == nil { 276 issues = []result.Issue{} 277 } 278 } 279 280 return issues 281 }