Skip to content

Commit

Permalink
remove recursion from moduleInfo.load()
Browse files Browse the repository at this point in the history
moduleInfo.load() used to load modules and depdencies recursively,
and due to some unfortunate circumstances it would usually create a
very deep call stack where each load() called another load() to load
the next one.

The terraform parsing uses enough stack space that this can become a
problem, so for a slightly big project this sometimes caused atlantis
to die from a stack overflow:

runtime: goroutine stack exceeds 1000000000-byte limit
runtime: sp=0xc021478380 stack=[0xc021478000, 0xc041478000]
fatal error: stack overflow
[...]
github.com/runatlantis/atlantis/server/events.moduleInfo.load(...)
	...atlantis/server/events/modules.go:108 +0x46b fp=0xc021478750 sp=0xc021478570 pc=0xfeaa6b
github.com/runatlantis/atlantis/server/events.moduleInfo.load(...)
	...atlantis/server/events/modules.go:108 +0x46b fp=0xc021478930 sp=0xc021478750 pc=0xfeaa6b
github.com/runatlantis/atlantis/server/events.moduleInfo.load(...)
	...atlantis/server/events/modules.go:108 +0x46b fp=0xc021478b10 sp=0xc021478930 pc=0xfeaa6b

... and so on, several hundred times.
  • Loading branch information
finnag committed Apr 25, 2024
1 parent 5aaf8ad commit 69b5c6a
Showing 1 changed file with 38 additions and 26 deletions.
64 changes: 38 additions & 26 deletions server/events/modules.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,39 +81,51 @@ func (t tfFs) ReadDir(dirname string) ([]os.FileInfo, error) {
var _ tfconfig.FS = tfFs{}

func (m moduleInfo) load(files fs.FS, dir string, projects ...string) (_ *module, diags tfconfig.Diagnostics) {
if _, set := m[dir]; !set {
tfFiles := tfFs{files}
var mod *tfconfig.Module
mod, diags = tfconfig.LoadModuleFromFilesystem(tfFiles, dir)

deps := make(map[string]bool)
if mod != nil {
for _, c := range mod.ModuleCalls {
mPath := path.Join(dir, c.Source)
if !tfconfig.IsModuleDirOnFilesystem(tfFiles, mPath) {
continue
dependenciesHandled := map[string]struct{}{dir: {}} // Only look at each dependency once
todo := []string{dir}

for len(todo) > 0 {
dir := todo[len(todo)-1] // order of processing list not important, pick last for efficiency
todo = todo[:len(todo)-1]

if _, set := m[dir]; !set {
tfFiles := tfFs{files}
var mod *tfconfig.Module
mod, diag := tfconfig.LoadModuleFromFilesystem(tfFiles, dir)
if diag != nil {
diags = append(diags, diag...)
}

deps := make(map[string]bool)
if mod != nil {
for _, c := range mod.ModuleCalls {
mPath := path.Join(dir, c.Source)
if !tfconfig.IsModuleDirOnFilesystem(tfFiles, mPath) {
continue
}
deps[mPath] = true
}
deps[mPath] = true
}

m[dir] = &module{
path: dir,
dependencies: deps,
projects: make(map[string]bool),
}
}

m[dir] = &module{
path: dir,
dependencies: deps,
projects: make(map[string]bool),
// set projects on my dependencies
for dep := range m[dir].dependencies {
if _, exists := dependenciesHandled[dep]; !exists {
dependenciesHandled[dep] = struct{}{}
todo = append(todo, dep)
}
}
}
// set projects on my dependencies
for dep := range m[dir].dependencies {
_, err := m.load(files, dep, projects...)
if err != nil {
diags = append(diags, err...)
// add projects to the list of dependant projects
for _, p := range projects {
m[dir].projects[p] = true
}
}
// add projects to the list of dependant projects
for _, p := range projects {
m[dir].projects[p] = true
}
return m[dir], diags
}

Expand Down

0 comments on commit 69b5c6a

Please sign in to comment.