Skip to content

Commit 764fa09

Browse files
facchinmcmaglie
authored andcommitted
Use jobs flag to really control number of concurrent compilations
The issue was due to the peculiar way concurrency and parallelism are handled in go. We used to set GOMAXPROC to 1 to avoid parallelizing the WaitGroup execution. This would work, in theory, unless the goroutines sleep. In that case, another goroutine is allowed to start concurrently (only 1 goroutine running in parallel, so GOMAXPROC is happy). Since our goroutines sleep (wait) after calling gcc, another task is started, without any hard limit, till the WaitGroup is completely spawned. On systems with limited resources (as RaspberryPi) and cores with lots of files this can trigger an out of memory condition.
1 parent f034233 commit 764fa09

File tree

3 files changed

+32
-12
lines changed

3 files changed

+32
-12
lines changed

Diff for: commands/compile/compile.go

+7
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"fmt"
2424
"io"
2525
"path/filepath"
26+
"runtime"
2627
"sort"
2728
"strings"
2829

@@ -115,6 +116,12 @@ func Compile(ctx context.Context, req *rpc.CompileReq, outStream, errStream io.W
115116

116117
builderCtx.CoreBuildCachePath = paths.TempDir().Join("arduino-core-cache")
117118

119+
jobs := runtime.NumCPU()
120+
if req.GetJobs() > 0 {
121+
jobs = int(req.GetJobs())
122+
}
123+
builderCtx.Jobs = jobs
124+
118125
builderCtx.USBVidPid = req.GetVidPid()
119126
builderCtx.WarningsLevel = req.GetWarnings()
120127

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

+22-12
Original file line numberDiff line numberDiff line change
@@ -175,20 +175,30 @@ func compileFilesWithRecipe(ctx *types.Context, sourcePath *paths.Path, sources
175175

176176
ctx.Progress.Steps = ctx.Progress.Steps / float64(len(sources))
177177
var wg sync.WaitGroup
178-
wg.Add(len(sources))
179178

180-
for _, source := range sources {
181-
go func(source *paths.Path) {
182-
defer wg.Done()
183-
PrintProgressIfProgressEnabledAndMachineLogger(ctx)
184-
objectFile, err := compileFileWithRecipe(ctx, sourcePath, source, buildPath, buildProperties, includes, recipe)
185-
if err != nil {
186-
errorsChan <- err
187-
} else {
188-
objectFilesChan <- objectFile
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])
189196
}
190-
}(source)
191-
}
197+
wg.Wait()
198+
}
199+
200+
doneChan <- struct{}{}
201+
}()
192202

193203
go func() {
194204
wg.Wait()

Diff for: legacy/builder/types/context.go

+3
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ type Context struct {
111111
// Experimental: use arduino-preprocessor to create prototypes
112112
UseArduinoPreprocessor bool
113113

114+
// Parallel processes
115+
Jobs int
116+
114117
// Out and Err stream to redirect all Exec commands
115118
ExecStdout io.Writer
116119
ExecStderr io.Writer

0 commit comments

Comments
 (0)