Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 62 additions & 1 deletion src/cmd/dist/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -678,10 +678,46 @@ var gentab = []struct {
var installed = make(map[string]chan struct{})
var installedMu sync.Mutex

// pkgDeps tracks the packages each package is waiting on.
// It's used to detect import cycles that would cause deadlocks.
var pkgDeps = make(map[string][]string)

func install(dir string) {
<-startInstall(dir)
}

// checkInstallCycle checks if waiting for dep from pkg would create a cycle.
// It returns the cycle path if a cycle is found, or nil otherwise.
// installedMu must be held when calling this function.
func checkInstallCycle(pkg, dep string) []string {
// DFS to find if dep transitively depends on pkg
visited := make(map[string]bool)
var path []string
var dfs func(p string) bool
dfs = func(p string) bool {
if p == pkg {
return true // Found cycle back to pkg
}
if visited[p] {
return false
}
visited[p] = true
path = append(path, p)
for _, d := range pkgDeps[p] {
if dfs(d) {
return true
}
}
path = path[:len(path)-1]
return false
}
if dfs(dep) {
// Return the cycle: pkg -> dep -> ... -> pkg
return append([]string{pkg}, path...)
}
return nil
}

func startInstall(dir string) chan struct{} {
installedMu.Lock()
ch := installed[dir]
Expand All @@ -702,6 +738,13 @@ func runInstall(pkg string, ch chan struct{}) {
}

defer close(ch)
defer func() {
// Clean up pkgDeps when done to avoid holding references
// and to keep the cycle detection data structure clean.
installedMu.Lock()
delete(pkgDeps, pkg)
installedMu.Unlock()
}()

if pkg == "unsafe" {
return
Expand Down Expand Up @@ -883,13 +926,31 @@ func runInstall(pkg string, ch chan struct{}) {
}
sort.Strings(sortedImports)

// Start all dependency installations and collect the list of deps.
deps := make([]string, 0, len(importMap))
for _, dep := range importMap {
if dep == "C" {
fatalf("%s imports C", pkg)
}
deps = append(deps, dep)
startInstall(dep)
}
for _, dep := range importMap {

// Check for import cycles before blocking on dependencies.
// We record that this package depends on deps, then check if any dep
// transitively depends on us, which would cause a deadlock.
installedMu.Lock()
pkgDeps[pkg] = deps
for _, dep := range deps {
if cycle := checkInstallCycle(pkg, dep); cycle != nil {
installedMu.Unlock()
fatalf("import cycle: %s", strings.Join(append(cycle, pkg), " -> "))
}
}
installedMu.Unlock()

// Wait for all dependencies to be installed.
for _, dep := range deps {
install(dep)
}

Expand Down