Skip to content

Commit 0785c72

Browse files
committed
Fixed race condition at the edge of builds. Using queue channel.
The last loop collecting the remaining objectFiles may be run before the last jobs completes. This commit replaces the two channels used to fill objectFiles and to signal error with direct variable access guarded by mutex, this avoids race conditions at the end and streamlines the whole process. Also added a 'queue' channel to feed the goroutines, this is not strictly part of the fix, but helps to fairly distribute the workload.
1 parent 764fa09 commit 0785c72

File tree

1 file changed

+43
-43
lines changed

1 file changed

+43
-43
lines changed

Diff for: legacy/builder/builder_utils/utils.go

+43-43
Original file line numberDiff line numberDiff line change
@@ -169,57 +169,57 @@ func compileFilesWithRecipe(ctx *types.Context, sourcePath *paths.Path, sources
169169
if len(sources) == 0 {
170170
return objectFiles, nil
171171
}
172-
objectFilesChan := make(chan *paths.Path)
173-
errorsChan := make(chan error)
174-
doneChan := make(chan struct{})
172+
var objectFilesMux sync.Mutex
173+
var errors []error
174+
var errorsMux sync.Mutex
175175

176176
ctx.Progress.Steps = ctx.Progress.Steps / float64(len(sources))
177-
var wg sync.WaitGroup
178177

179-
// Split jobs into batches of N jobs each; wait for the completion of a batch to start the next
180-
par := ctx.Jobs
181-
182-
go func() {
183-
for total := 0; total < len(sources); total += par {
184-
for i := total; i < total+par && i < len(sources); i++ {
185-
wg.Add(1)
186-
go func(source *paths.Path) {
187-
defer wg.Done()
188-
PrintProgressIfProgressEnabledAndMachineLogger(ctx)
189-
objectFile, err := compileFileWithRecipe(ctx, sourcePath, source, buildPath, buildProperties, includes, recipe)
190-
if err != nil {
191-
errorsChan <- err
192-
} else {
193-
objectFilesChan <- objectFile
194-
}
195-
}(sources[i])
196-
}
197-
wg.Wait()
178+
queue := make(chan *paths.Path)
179+
job := func(source *paths.Path) {
180+
PrintProgressIfProgressEnabledAndMachineLogger(ctx)
181+
objectFile, err := compileFileWithRecipe(ctx, sourcePath, source, buildPath, buildProperties, includes, recipe)
182+
if err != nil {
183+
errorsMux.Lock()
184+
errors = append(errors, err)
185+
errorsMux.Unlock()
186+
} else {
187+
objectFilesMux.Lock()
188+
objectFiles.Add(objectFile)
189+
objectFilesMux.Unlock()
198190
}
191+
}
199192

200-
doneChan <- struct{}{}
201-
}()
202-
203-
go func() {
204-
wg.Wait()
205-
doneChan <- struct{}{}
206-
}()
207-
208-
for {
209-
select {
210-
case objectFile := <-objectFilesChan:
211-
objectFiles.Add(objectFile)
212-
case err := <-errorsChan:
213-
return nil, i18n.WrapError(err)
214-
case <-doneChan:
215-
close(objectFilesChan)
216-
for objectFile := range objectFilesChan {
217-
objectFiles.Add(objectFile)
193+
// Spawn jobs runners
194+
var wg sync.WaitGroup
195+
for i := 0; i < ctx.Jobs; i++ {
196+
wg.Add(1)
197+
go func() {
198+
for source := range queue {
199+
job(source)
218200
}
219-
objectFiles.Sort()
220-
return objectFiles, nil
201+
wg.Done()
202+
}()
203+
}
204+
205+
// Feed jobs until error or done
206+
for _, source := range sources {
207+
errorsMux.Lock()
208+
gotError := len(errors) > 0
209+
errorsMux.Unlock()
210+
if gotError {
211+
break
221212
}
213+
queue <- source
214+
}
215+
close(queue)
216+
wg.Wait()
217+
if len(errors) > 0 {
218+
// output the first error
219+
return nil, i18n.WrapError(errors[0])
222220
}
221+
objectFiles.Sort()
222+
return objectFiles, nil
223223
}
224224

225225
func compileFileWithRecipe(ctx *types.Context, sourcePath *paths.Path, source *paths.Path, buildPath *paths.Path, buildProperties *properties.Map, includes []string, recipe string) (*paths.Path, error) {

0 commit comments

Comments
 (0)